You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 11:37:46