【问题标题】:Unable jump into stream from media recorder using media source with socket.io无法使用带有 socket.io 的媒体源从媒体记录器跳转到流
【发布时间】:2019-09-26 20:15:55
【问题描述】:

以下代码在首先加载视频观察客户端然后加载网络摄像头客户端时工作正常,但是,如果切换顺序或以任何方式中断流,例如通过刷新任一客户端,流将失败,媒体源将其就绪状态更改为关闭。

我的假设是,在开始时接收到的视频需要初始化标头才能开始,并且由于流是在中途读取的,所以它永远不会得到所说的初始化标头。我不确定如何将此类标头添加到 webm 文件中。

我试图更改源缓冲区的序列模式,但什么也没做。我已经尝试重新启动录像机并且可以正常工作,但我的最终计划是拥有多个观察客户端,并且在每次重新连接时重新启动录像机并不是最佳的。

相机客户端

main();
function main() {
    if (hasGetUserMedia()) {
        const constraints = {
            video: {
                facingMode: 'environment',
                frameRate: {
                    ideal: 10,
                    max: 15
                }
            },
            audio: true
        };

        navigator.mediaDevices.getUserMedia(constraints).
        then(stream => {
            setupRecorder(stream);
        });
    }
}

function setupRecorder(stream) {
    let mediaRecorder = new MediaRecorder(stream, {
        mimeType: 'video/webm; codecs="opus, vp9"'
    });

    mediaRecorder.ondataavailable = e => {
        var blob = e.data;
        socket.emit('video', blob);
    }

    mediaRecorder.start(500);
}

服务器只是广播接收到的任何内容

观察客户

var sourceBuffer;
var queue = [];
var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', sourceOpen, false);
main();

socket.on('stream', data => {
    if (mediaSource.readyState == "open") {
        if (sourceBuffer.updating || queue.length > 0) {
            queue.push(data.video);
        } else {
            sourceBuffer.appendBuffer(data.video);
        }
    }
});

function main() {
    videoElement = document.querySelector('#video');
    videoElement.src = URL.createObjectURL(mediaSource);
}

function sourceOpen(e) {
    console.log('open');
    sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="opus, vp9"');
    sourceBuffer.addEventListener('updateend', () => {
        console.log(sourceBuffer.updating, mediaSource.readyState);

        if (queue.length > 0 && !sourceBuffer.updating) {
            sourceBuffer.appendBuffer(queue.shift());
        }
    });
}

所以代码实际上只是以一种不正确的方式工作,因此套接字发送服务器没有任何问题。它要么与 MediaRecorder 或 MediaSource 有关。

【问题讨论】:

    标签: javascript socket.io video-streaming media-source web-mediarecorder


    【解决方案1】:

    我的假设是,在开始时接收到的视频需要初始化标头才能启动,并且由于流是在中途读取的,所以它永远不会得到所说的初始化标头。

    正确!

    要解决这个问题,您需要对 WebM 格式有所了解。 WebM 只是 Matroska (MKV) 的一个子集。 Matroska 是用于在 EBML 中存储媒体的模式规范。 EBML 是一种二进制文件格式,可以有任意块。把它想象成一个二进制 XML。

    这意味着您可以使用 EBML Viewer 之类的工具来检查 WebM 文件,并参考 Matroska 规范以了解正在发生的事情。例如:

    这是对预先录制的 WebM 文件的检查。它会在浏览器中正常播放。您会注意到有些元素是嵌套的。

    每个 WebM 文件中都有两个顶级元素。 EBML,定义了这个二进制文件,Segment 包含后面的所有内容。

    Segment 中,有几个元素对您很重要。其中之一是Tracks。您会注意到该文件有两个轨道,一个用于 Opus 中的音频,一个用于 VP9 中的视频。另一个重要的块是Info,它包含有关时间刻度的信息和有关复用器的一些元数据。

    在所有这些元数据之后,您会找到 ClusterClusterCluster 等。这些是您可以剪切 WebM 流的地方,前提是每个 @987654336 @ 以关键帧开头。

    换句话说,您的代码应该执行以下操作:

    • 将第一个Cluster之前的所有数据保存为“初始化数据”。​​
    • 之后在Cluster 上拆分。

    播放时:

    • 使用之前保存的“初始化数据”作为您首先加载的内容。
    • 之后开始在Clusters 中加载,从流中任何您想要的位置开始。

    现在,集群需要关键帧位的人很重要。据我所知,没有办法配置 MediaRecorder 来做到这一点,浏览器对此特别挑剔。至少,您必须重新混合服务器端......您甚至可能需要重新编码。另见:Encoding FFMPEG to MPEG-DASH – or WebM with Keyframe Clusters – for MediaSource API

    通过 socket.io 使用媒体源

    我应该指出,您甚至不需要 MediaSource。你绝对不需要 Socket.IO。它可以像通过普通 HTTP 流输出这些数据一样简单。这可以直接在<video> 元素中加载。 (无论如何,如果您想要额外的控制,请使用 MediaSource,但这不是必需的。)

    【讨论】:

    • 感谢您的回复,布拉德,这非常鼓舞人心。我确实尝试将我的视频直接加载到一个视频元素中,但它不是无缝的,这导致我使用 MediaSource,但我会尝试你所推荐的。另外,一个问题是我什至如何去提取元数据来解析集群?
    • @JustinFernald 您必须在代码中解析 WebM。这在 JavaScript 中是完全可行的。 WebM 很容易解码。我想上次我这样做时,我最终编写了自己的解复用器,但看起来 NPM 上至少有一个包可能是你的起点:github.com/mafintosh/webm-cluster-stream 真的,花一个下午的时间学习 Matroska 规范和一个十六进制编辑器。一旦你得到它就非常简单!
    • 老实说,我一直在研究 FFMPEG、Dash 和 Matrosta,但离我的目标还很远。我对如何将视频发送到 FFMPEG 然后接收输出并将其发送给观察者感到非常迷茫。我最初认为如果我只发送初始化段然后发送数据它会起作用,但是,经过一些测试,流中的任何中断都会破坏流。我知道这有点奇怪,但布拉德我很想有机会和你聊一聊,然后挑选你的大脑,因为我对流媒体很陌生。文档没有提供足够的帮助。
    • @JustinFernald 当然,请在您有空时给我发送电子邮件至 brad@audiopump.co。我可以聘请咨询,但很高兴也可以免费简单聊聊,看看我能否为您指明正确的方向。
    • @AdamMarsh 我不知道任何现成的解复用器。 EBML 查看器向您提供该错误消息,因为您的文件用于流式传输,这意味着它的长度不确定。上面的示例只是为了显示这些类型的文件中的内容。您仍然需要编写解复用器并使用十六进制编辑器进行调试。你真的只需要在第一个集群上分裂......所以0x1f43b675......当你看到它时,你就有了一个集群的开始。之前的一切本质上都是初始化数据。
    猜你喜欢
    • 1970-01-01
    • 2015-12-19
    • 2016-10-19
    • 2017-04-19
    • 1970-01-01
    • 2014-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多