2

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

我的假设是,在开始时接收到的视频需要初始化标头来启动,并且由于流是在中途读取的,所以它永远不会得到所说的初始化标头。我不确定如何将此类标头添加到 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 有关。

4

1 回答 1

2

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

正确的!

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

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

EBMLViewer 示例

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

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

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

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

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

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

播放时:

  • 使用先前保存的“初始化数据”作为您加载的第一件事。
  • Cluster之后开始在 s 中加载,从流中的任何位置开始。

现在,集群需要关键帧位的人很重要。据我所知,没有办法配置 MediaRecorder 来做到这一点,浏览器对此特别挑剔。至少,您必须重新混合服务器端......您甚至可能需要重新编码。另请参阅: 将 FFMPEG 编码为 MPEG-DASH - 或带有关键帧集群的 WebM - 用于 MediaSource API

使用带有 socket.io 的媒体源

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

于 2019-05-09T15:15:07.370 回答