如何将h264编码的MediaRecorder流传递给Chrome中的MediaSource?

问题描述 投票:1回答:1

我们的屏幕记录镶边扩展程序允许用户使用getDisplayMedia API记录其屏幕,该API返回返回到MediaRecorder API中的流。

通常,我们会使用带有更新的vp9编解码器的webm视频容器来录制此流,如下所示:

const mediaRecorder = new MediaRecorder(mediaStream, {
    mimeType: "video/webm; codecs=vp9"
  });

但是,Safari不支持webm容器,也不支持对vp9编解码器进行解码。由于Chrome中的MediaRecorder API仅支持在webm容器中进行记录,但不支持h264编码(Safari可以对其进行解码),因此我们改为使用h264编解码器在webm容器中进行记录:

const mediaRecorder = new MediaRecorder(mediaStream, {
    mimeType: "video/webm; codecs=h264"
  });

这很有效,有两个原因:

  1. 由于我们的录制应用是Chrome扩展程序,因此我们不介意它只能在Chrome中录制

  2. 由于视频数据被编码为h264,我们现在几乎可以立即将视频数据移动到.mp4容器中,从而使Safari浏览器查看器可以观看这些录制的视频,而不必等待昂贵的转码过程(请注意,您可以在常规的网络应用中查看没有chrome扩展名的视频)

但是,由于媒体记录器API没有方法获取到目前为止记录的视频流的持续时间,并且用performance.now手动测量它是不精确的(误差为25ms至150ms),因此我们不得不更改为将记录器数据馈送到MediaSource中,以便我们可以使用mediaSourceBuffer.buffered.end(sourceBuffer.buffered.length - 1) * 1000 API来100%准确地读取到目前为止记录的视频流持续时间(以毫秒为单位)。

问题是由于某种原因,当我们使用“ video / webm; codecs = h264” mime类型时,MediaSource无法实例化。

执行此操作:

mediaSourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs=h264");

结果:

Failed to execute 'addSourceBuffer' on 'MediaSource': The type provided ('video/webm; codecs=h264') is unsupported.

为什么MediaRecorder支持mime类型而不支持MediaSource?由于它们属于相同的API系列,难道它们不支持相同的mime类型吗?当使用addSourceBuffer将数据传递到MediaSource时,我们如何在h264编解码器中进行录制?

[到目前为止,我们唯一想到的解决方案是创建2个媒体记录器,其中一个在vp9中记录,以便我们使用buffered.end API读取到目前为止所记录视频的准确时长,一个在h264中记录,以便我们能够能够立即将视频数据移动到mp4容器,而无需将Safari用户的编解码器从vp9转换为h264。但是,这将非常低效,因为它将有效地在RAM中保存两倍的数据。

复制案例/代码和盒子示例

  1. vp9 example(均为工作)
  2. [h264 example(媒体记录器有效,媒体源无效)
javascript mediarecorder getusermedia mediaelement media-source
1个回答
0
投票

解码器和编码器完全是不同的野兽。例如,Webkit(Safari)可以解码几种格式,但不能编码任何格式。

[此外,MediaSource API要求传递给它的媒体必须是分段的,因此不能读取浏览器可以解码的所有媒体,例如,如果某个浏览器有朝一日支持生成标准(非分段)mp4文件,那么他们仍将无法将其传递给MediaSource API。

我无法确定他们是否[[可以支持此特定编解码器(我想是的),但是您甚至根本不需要所有解决方法。

如果您的扩展程序能够生成DOM元素,则可以使用<video>描述的技巧,简单地使用in this answer元素来告诉您录制的视频的持续时间。

将视频的currentTime设置为非常大的数字,等待seeked事件,您将获得正确的duration

const canvas_stream = getCanvasStream(); const rec = new MediaRecorder( canvas_stream.stream ); const chunks = []; rec.ondataavailable = (evt) => chunks.push( evt.data ); rec.onstop = async (evt) => { canvas_stream.stop(); console.log( "duration:", await measureDuration( chunks ) ); }; rec.start(); setTimeout( () => rec.stop(), 5000 ); console.log( 'Recording 5s' ); function measureDuration( chunks ) { const blob = new Blob( chunks, { type: "video/webm" } ); const vid = document.createElement( 'video' ); return new Promise( (res, rej) => { vid.onerror = rej; vid.onseeked = (evt) => res( vid.duration ); vid.onloadedmetadata = (evt) => { URL.revokeObjectURL( vid.src ); // for demo only, to show it's Infinity in Chrome console.log( 'before seek', vid.duration ); }; vid.src = URL.createObjectURL( blob ); vid.currentTime = 1e10; } ); } // just so we can have a MediaStream in StackSnippet function getCanvasStream() { const canvas = document.createElement( 'canvas' ); const ctx = canvas.getContext( '2d' ); let stopped = false; function draw() { ctx.fillRect( 0,0,1,1 ); if( !stopped ) { requestAnimationFrame( draw ); } } draw(); return { stream: canvas.captureStream(), stop: () => stopped = true }; }
© www.soinside.com 2019 - 2024. All rights reserved.