如何播放由MediaRecorder创建的WEBM文件?

10

在记录音频和视频时,我正在使用MediaRecorder API的ondataavailable下创建webm文件。 我必须逐个播放每个创建的webm文件。

Mediarecorder api仅将头信息插入第一个块(webm文件),因此没有头信息的其余块不能独立播放。

链接1链接2所建议的,我已经从第一个块中提取了头信息。

// for the most regular webm files, the header information exists
// between 0 to 189 Uint8 array elements

const headerIinformation = arrayBufferFirstChunk.slice(0, 189); 

我将首部信息插入到第二个数据块中,但是第二个数据块仍无法播放。此时浏览器会显示视频的海报(单帧)和两个数据块之和的持续时间,例如:10秒;每个数据块的持续时间为5秒。

我也用十六进制编辑器完成了同样的头部信息操作。我在编辑器中打开了webm文件,并从第一个webm文件中复制了前190个元素并将其放入第二个文件中,类似于下面的图片,即使这次第二个webm文件仍然无法播放,结果与上一次相同。

红色显示了头部信息:

webm hex

这次我从第一个webm文件中复制了头部和簇信息,将其放入第二个文件中,类似于下面的图片,但没有成功。

webm hex

问题

我在这里做错了什么?

有没有办法可以单独播放webm文件/数据块?

注意:我不能使用MediaSource来播放这些数据块。

编辑1

如@Brad建议的那样,我想将第一个簇之前的所有内容插入到后面的一个簇中。我有一些每个持续5秒的webm文件。经过查看文件,我发现几乎每隔一个文件没有簇点(没有0x1F43B675)。

在这里,我很困惑,是将头部信息(初始化数据)插入到每个文件的开头还是每个第一个簇的开头?如果我选择后者,那么没有任何簇的webm文件将如何播放?

或者,首先我需要使每个webm文件以具有簇的方式开始,以便我可以在这些文件中的簇之前插入头部信息吗?

编辑2

经过一些挖掘和阅读这里,我得出结论,每个webm文件都需要头部信息、簇和实际数据。


为什么你必须逐个播放这些块?为什么不能每个“块”都创建一个新的MediaRecorder?你仍然可以让主要的MediaRecorder并行运行,以便将整个内容记录为单个文件,甚至只需使用服务器端后处理将所有这些文件连接成一个文件。 - Kaiido
2个回答

7
// 对于最普通的webm文件,头信息存在于Uint8数组元素的0到189之间
没有看到实际的文件数据很难说,但这可能是错误的。 "头信息"需要是从文件开头到第一个Cluster element之前的所有数据。也就是说,您要保留从文件开头到看到0x1F43B675之前的所有数据,并将其视为初始化数据。这可以/将因文件而异。在我的测试文件中,这发生在1 KB后面一点。
// 并将此头信息预先放入第二个块中,仍然无法播放第二个块,但这次浏览器显示视频的海报(单帧)和两个块的持续时间,例如:10秒;每个块的持续时间为5秒。
从MediaRecorder输出的块对于分段不相关,并且可能在不同时间发生。实际上,您需要在Cluster元素上进行拆分。这意味着您需要解析此WebM文件,至少要在其标识符0x1F43B675出现时拆分出Clusters。
“我们有没有办法单独播放webm文件/块?”
您走在正确的道路上,只需将第一个Cluster之前的所有内容添加到稍后的Cluster之前即可。
一旦您完成了这项工作,您可能会遇到的下一个问题是,您无法仅使用任何Cluster执行此操作。第一个Cluster必须以关键帧开头,否则浏览器将无法解码它。 Chrome将跳转到下一个cluster,到某个程度上,但这并不可靠。不幸的是,没有办法配置MediaRecorder的关键帧位置。如果您足够幸运能够在服务器端处理此视频,则可以使用FFmpeg进行处理:https://dev59.com/_GAf5IYBdhLWcg3w52Ll#45172617

1
嗨,Brad,这是否意味着要能够实时流式传输WEBM,仍需要服务器端处理而不是纯客户端方法? - quarks
1
@quarks 你可以在客户端重新混合。我没有为此提供任何特定的代码...我曾经走过这条路,但最终选择了服务器端,因为我需要转码到其他比特率。你也可以找出一种方法来确定哪些群集元素以关键帧开头,并且只允许寻找它们。也许这就像找到大型群集一样简单。关键帧必须从群集的开头开始,但并不是所有群集都以关键帧开头。 - Brad
你们实际上在生产环境中使用这种方法吗?你们的传输方式是TCP还是UDP? - quarks
@Brad,非常感谢您的回复。我没有在服务器端进行视频处理的特权。我有一些WebM文件,每个文件的持续时间为5秒。经过挖掘这些文件,我发现几乎每个交替的文件都没有群集点(没有0x1F43B675)。在这里,我很困惑,我需要在每个文件的开头还是每个第一个群集的开头插入?如果我选择后者选项,那么没有任何群集的WebM文件将如何播放? - Suman Bogati
@Brad,我完全卡住了,你能否就更新后的问题(编辑1)提供建议? - Suman Bogati
@Brad,你想看看我的新问题吗?https://stackoverflow.com/questions/62302652/webm-file-could-not-play-when-mediarecorder-on-chrome-does-not-provide-the-clust - Suman Bogati

0

看起来这并不容易,因为你必须扫描整个 blob 才能找到魔法值。

let offset = -1;
let value = 0;
const magicNumber = parseInt("0x1F43B675".match(/[a-fA-F0-9]{2}/g).reverse().join(''), 16)

while(value !== magicNumber) {
  offset = offset + 1;

  try {
    const arr = await firstChunk.slice(offset, offset + 4).arrayBuffer().then(buffer => new Int32Array(buffer));
    value = arr[0];
  }
  catch(error) {
    return;
  }
}

offset = offset + 4;

答案是193199

const header = firstChunk.slice(0, offset);
const blobType = firstChunk.type;
const blob = new Blob([header, chunk], { type: blobType });

就是这样。现在的问题是我是怎么得到这个数字的?为什么它不是 42 的倍数呢?

暴力破解

逻辑很简单,录制视频,收集块,切割第一块,计算新的 blob 并尝试使用 HTMLVideoElement 播放它。如果失败了,增加偏移量。

(async() => {

    const microphoneAudioStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

    const mediaRecorder = new MediaRecorder(microphoneAudioStream);
    let chunks = [];

    mediaRecorder.addEventListener('dataavailable', (event) => {
        const blob = event.data;
        chunks = [...chunks, blob];
    });

    mediaRecorder.addEventListener("stop", async () => {
        const [firstChunk, ...restofChunks] = chunks;
        const [secondBlob] = restofChunks;
        const blobType = firstChunk.type;

        let index = 0;
        const video = document.createElement("video");

        while(index < 1000) {
            const header = firstChunk.slice(0, index);
            const blob = new Blob([header, secondBlob], { type: blobType });
            const url = window.URL.createObjectURL(blob);

            try {
                video.setAttribute("src", url);
                await video.play();
                console.log(index);
                break;
            }
            catch(error) {

            }

            window.URL.revokeObjectURL(url);

            index++;
        }
    })

    mediaRecorder.start(200);

    const stop = () => {
        mediaRecorder.stop();
    }

    setTimeout(stop, 400)

})();

我注意到在MediaRecorder.start的较小的timeslice参数和setTimeout的超时参数中,标题偏移量变为1。不幸的是仍然不是42。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接