Firefox 的 MediaRecorder 接口仅每两秒提供一次新的视频数据
Posted
技术标签:
【中文标题】Firefox 的 MediaRecorder 接口仅每两秒提供一次新的视频数据【英文标题】:Firefox's MediaRecorder interface only providing new video data every two seconds 【发布时间】:2014-08-31 20:32:13 【问题描述】:我正在尝试将通过 WebRTC 的 getUserMedia() 方法获得的视频流发送到服务器以进行额外处理。延迟很重要,因为我希望检测视频流的变化并立即更新客户端。对于这种特殊用途,仅 Firefox 的解决方案是可以接受的,因此我正在研究MediaRecorder 接口。
我整理了一个简单的测试用例,如下所示。没有错误,并且按预期每 500 毫秒调用一次 ondataavailable 回调。但是,对于四分之三的调用,提供的数据大小为零。这向我表明,数据被分组为大约两秒的块(可能是由于所使用的视频编码的限制)。
是否可以让 MediaRecorder 以更精细的粒度提供数据?如果不是,那么以低延迟将视频数据从 userMedia 流获取到服务器的最佳方法是什么?一个特定于 Chrome 或 Firefox 的界面会很好,但同时在这两种情况下都可以使用的界面会更好。
<html>
<body>
<h1>MediaRecorder Test</h1>
<video id="video" style="border: 1px solid black"></video>
</body>
</html>
<script>
// The variable that holds the video stream
var mediastream = null;
// Start video capture (and provide a way to stop it)
navigator.mozGetUserMedia ( video: true, audio: false ,
function(stream_arg)
mediastream = stream_arg;
var vendorURL = window.URL || window.webkitURL;
video.src = vendorURL.createObjectURL(mediastream);
video.play();
recordStream();
,
function(err) console.log("Error starting video stream: " + err);
);
// Record the stream
var recorder = null;
function recordStream()
recorder = new MediaRecorder(mediastream);
recorder.ondataavailable = function(ev)
console.log("Got: "+ev.data.size);
;
recorder.start(500);
</script>
【问题讨论】:
我应该在原始问题中提到,我们当前的方法是使用画布捕获图像并使用 XHR 将这些图像发送到服务器(如下面 CuriousGuy 的回答中详细描述的)。使用 MediaRecorder 的目的是减少传输的数据量,从而使我们能够支持更高的帧速率。我也在考虑在 javascript 中做某种视频编码,但是这个问题主要是为了看看我们是否可以利用浏览器现有的编码。 【参考方案1】:您传递给 MediaRecorder 的 500 毫秒间隔是建议性的。使用的编解码器可能需要更大的数据块/时间才能使用。它可能会尽可能快地为您提供数据。
如果您需要低延迟,MediaRecorder 是不适合这项工作的工具。常规 WebRTC 调用将使用优化延迟而不是质量的编解码器设置。我听说有人在服务器端录制 WebRTC,但我不知道有什么开源可以做到这一点。
也许在未来的某一天,MediaRecorder API 将允许我们选择编解码器参数,这不会成为问题。
【讨论】:
谢谢;这证实了我的想法。运行 WebRTC 端点是可能的,但据我所见,很难将各个部分分开——尤其是路由部分,它不适合我们的应用程序。从好的方面来说,我们发现捕获和发送单个帧(如 Curious 的回答和我的 cmets 中所述)足以满足我们的需要。 @RobHague 我可能会建议通过二进制 websocket 发送您的数据,而不是通过 base64 编码的方式发送数据。 BinaryJS 提供了一种很好的方法来获取可以写入的常规 Node.js 样式流。我已经将它与其他一些 NPM 模块一起使用,以这种方式多路复用多个流,取得了良好的成功和常规二进制数据的效率。 我们目前使用 Blob 和 FormData 以二进制形式发送数据(JPEG 编码)。但是,如果我们提高帧速率,我们可能会切换到 websocket 以减少开销。【参考方案2】:您可以使用另一种方法:每 N 毫秒将视频发送到(可选隐藏)画布,该画布允许获取图像的 base64 表示。因此,您将获得一个 base64 帧数组。现在你有两个选择:
以 base64 格式将每个帧发送到服务器。由于 base64 是常规字符串,这是最简单的方法; 将每个base64帧转换为Blob并通过FormData发送到服务器。就我而言,以这种方式上传速度是第一种方式的两倍。您可以在下面看到我的示例(执行第二个选项)。这个例子很大,但是每个部分都很重要。
index.html:
<!DOCTYPE html>
<html>
<head>
<script src="record-test.js"></script>
</head>
<body>
<video id="video"></video>
<canvas id="canvas" style="display:none;"></canvas>
<input type="button" id="stopRecordBtn" value="Stop recording">
</body>
</html>
record-test.js:
(function()
'use strict';
//you can play with these settings
var FRAME_INTERVAL_MS = 500; //take snapshot each 500 ms
var FRAME_WIDTH = 320; //width and
var FRAME_HEIGHT = 240; //height of resulting frame
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
window.URL = window.URL || window.webkitURL;
var video, canvas, ctx;
var mediaStream;
var videoRecordItv;
var base64Frames = [];
var init = function()
video = document.getElementById('video');
canvas = document.getElementById('canvas'); //use canvas to capture a frame and convert it to base64 data
canvas.width = FRAME_WIDTH;
canvas.height = FRAME_HEIGHT;
ctx = canvas.getContext('2d');
var stopBtn = document.getElementById('stopRecordBtn');
stopBtn.addEventListener('click', stopRecording);
navigator.getUserMedia(video: true, onGotStream, function(e) console.log(e););
var onGotStream = function(stream)
mediaStream = stream;
video.src = URL.createObjectURL(mediaStream);
video.play();
videoRecordItv = setInterval(function() //capture a frame each FRAME_INTERVAL_MS milliseconds
var frame = getBase64FrameFromVideo();
base64Frames.push(frame);
, FRAME_INTERVAL_MS);
var getBase64FrameFromVideo = function()
ctx.drawImage(video, 0, 0, FRAME_WIDTH, FRAME_HEIGHT);
//a canvas snapshot looks like data:image/jpeg;base64,ACTUAL_DATA_HERE
//we need to cut out first 22 characters:
var base64PrefixLength = 'data:image/jpeg;base64,'.length;
return canvas.toDataURL('image/jpeg').slice(base64PrefixLength);
var stopRecording = function()
mediaStream && mediaStream.stop && mediaStream.stop();
mediaStream = null;
clearInterval(videoRecordItv); //stop capturing video
uploadFramesToServer();
var uploadFramesToServer = function()
var sid = Math.random(); //generate unique id
var curFrameIdx = 0; //current frame index
(function postFrame()
console.log('post frame #' + curFrameIdx);
var base64Frame = base64Frames[curFrameIdx];
var blobFrame = base64ToBlob(base64Frame, 'image/jpeg');
var formData = new FormData;
formData.append('frame', blobFrame, 'upload.jpg');
formData.append('sid', sid);
var xhr = new XMLHttpRequest();
//post a single frame to /postFrame url with multipart/form-data enctype
//on the server you get "sid" param and "frame" file as you would post a file with regular html form
xhr.open('POST', '/postFrame', true);
xhr.onload = function(e)
console.log(this.response);
if (base64Frames[++curFrameIdx])
postFrame(); //post next frame
else
//DONE!
console.log('finish post frames');
;
xhr.send(formData);
)();
var base64ToBlob = function(base64Data, contentType, sliceSize)
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(base64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize)
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++)
byteNumbers[i] = slice.charCodeAt(i);
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
return new Blob(byteArrays, type: contentType);
document.addEventListener('DOMContentLoaded', init);
)();
在服务器端,您仍然需要执行一些操作,例如,使用 FFmpeg 从这些帧创建视频。
这种方法适用于 Chrome 和 Firefox。
希望这会有所帮助。对不起我的英语,祝你好运!
【讨论】:
感谢您的广泛回复。正如我应该在最初的问题中提到的那样,这基本上就是我们现在正在做的事情。但是,当以视频帧速率完成时,这会占用大量带宽。使用 MediaRecorder 或类似工具的好处是能够在给定带宽下支持更高的帧速率。以上是关于Firefox 的 MediaRecorder 接口仅每两秒提供一次新的视频数据的主要内容,如果未能解决你的问题,请参考以下文章
锚链接内的按钮在 Firefox 中有效,但在 Internet Explorer 中无效?
Android 10 源码MediaRecorder 录像流程:MediaRecorder 配置
Android-MediaRecorder-音频录制-警告-W/MediaRecorder(13811): mediarecorder went away with unhandled events(