无需MediaSource,如何从ArrayBuffer数组每500ms构建WebM视频?
嘿,这个问题我熟!要每500ms生成一段可独立播放的完整WebM视频(不用MediaSource),核心是要保证每段视频都包含WebM文件的完整结构:文件头(EBML标识、轨道信息等)+ 这段时间内的帧数据。下面是具体的实现方案:
关键原理
webm-wasm在初始化完成后,会先发送一段WebM文件头(包含EBML格式定义、视频轨道信息),后续发送的ArrayBuffer都是对应时间段的Cluster数据(WebM中存储帧的分段单元)。因为你的视频参数(宽高、帧率、码率)全程不变,所以文件头可以复用,每次只需要把“头+这段时间的Cluster数据”拼接起来,就是一个完整的可播放WebM文件。
具体代码修改与实现
1. 保存WebM文件头
首先修改worker的消息监听器,把初始化阶段的头数据单独保存下来:
let queue = []; let webmHeader = null; // 用于复用的WebM文件头 worker.onmessage = ev => { if (!ev.data) { console.log('End of stream'); return; } if (ev.data instanceof ArrayBuffer && ev.data.byteLength !== undefined) { const arrayBuffer = ev.data; // 第一次收到的ArrayBuffer是WebM文件头,后续都是Cluster数据 if (!webmHeader) { webmHeader = arrayBuffer; console.log('WebM header saved'); } else { queue.push(arrayBuffer); } } };
2. 实现完整WebM构建函数
接下来编写buildWebmVideoFromArrayOfBuffer函数,把文件头和这段时间的Cluster数据拼接成完整的WebM:
function buildWebmVideoFromArrayOfBuffer() { // 没有头数据或帧数据时返回null if (!webmHeader || queue.length === 0) { return null; } // 计算总字节长度:头长度 + 所有Cluster数据的长度之和 let totalByteLength = webmHeader.byteLength; queue.forEach(buffer => totalByteLength += buffer.byteLength); // 创建一个足够大的ArrayBuffer来容纳完整WebM const fullWebmBuffer = new ArrayBuffer(totalByteLength); const uint8View = new Uint8Array(fullWebmBuffer); // 先写入WebM文件头 uint8View.set(new Uint8Array(webmHeader), 0); let offset = webmHeader.byteLength; // 依次写入所有Cluster数据 queue.forEach(buffer => { uint8View.set(new Uint8Array(buffer), offset); offset += buffer.byteLength; }); return fullWebmBuffer; }
3. 确保头数据加载完成后再开始编码
为了避免第一段视频没有头,修改captureWebm函数,等待头数据返回后再启动帧编码:
async function captureWebm() { console.log("Started webm capture"); worker.postMessage("./webm-wasm.wasm"); await nextEvent(worker, "message"); console.log('webm worker loaded'); // 发送初始化参数,等待WebM头返回 worker.postMessage({ width: w, height: h, timebaseNum: 1, timebaseDen: 30, bitrate: 1500, realtime: true }); // 等待头数据接收完成 await new Promise(resolve => { const headerHandler = ev => { if (ev.data instanceof ArrayBuffer && !webmHeader) { webmHeader = ev.data; worker.removeEventListener('message', headerHandler); resolve(); } }; worker.addEventListener('message', headerHandler); }); console.log('WebM header ready, starting frame encoding'); let encodeWebm = () => { mCtx.drawImage(player, 0, 0, w, h); const imageData = mCtx.getImageData(0, 0, w, h); const buffer = imageData.data.buffer; worker.postMessage(buffer, [buffer]); requestAnimationFrame(encodeWebm); }; requestAnimationFrame(encodeWebm); }
注意事项
- 因为你设置了
realtime: true,webm-wasm会自动生成适合实时传输的小Cluster,每500ms收集的Cluster数据拼接后,加上头就是一段流畅的短WebM视频。 - 所有生成的WebM片段都使用同一个文件头,因为视频参数没有变化,这样既高效又能保证每个片段都可独立播放。
- 如果后续修改了视频参数(比如宽高、帧率),需要重新获取新的文件头。
内容的提问来源于stack exchange,提问作者quarks
相关产品推荐
相关产品推荐

