我在尝试理解使用node.js将ffmpeg的实时输出流式传输到HTML5客户端的最佳方法时遇到了困难,由于有很多变量需要考虑,并且我在这个领域没有太多经验,因此我花费了很多时间尝试不同的组合。
我的使用情况是:
1)IP视频摄像机RTSP H.264流通过FFMPEG进行拾取并使用以下FFMPEG设置在节点中重新混合为mp4容器,并将其输出到STDOUT。这仅在初始客户端连接上运行,因此部分内容请求不会再次尝试生成FFMPEG。
liveFFMPEG = child_process.spawn("ffmpeg", [
"-i", "rtsp://admin:12345@192.168.1.234:554" , "-vcodec", "copy", "-f",
"mp4", "-reset_timestamps", "1", "-movflags", "frag_keyframe+empty_moov",
"-" // output to stdout
], {detached: false});
2)我使用Node.js的HTTP服务器捕获STDOUT,并在客户端请求时将其流式传输回客户端。当客户端首次连接时,我会生成上述FFMPEG命令行,然后将STDOUT流式传输到HTTP响应。
liveFFMPEG.stdout.pipe(resp);
我也使用了流事件将FFMPEG数据写入HTTP响应,但没有任何区别。
xliveFFMPEG.stdout.on("data",function(data) {
resp.write(data);
}
我使用以下HTTP头部(在流式预录制文件时也使用且有效)
var total = 999999999 // fake a large file
var partialstart = 0
var partialend = total - 1
if (range !== undefined) {
var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
}
var start = parseInt(partialstart, 10);
var end = partialend ? parseInt(partialend, 10) : total; // fake a large file if no range reques
var chunksize = (end-start)+1;
resp.writeHead(206, {
'Transfer-Encoding': 'chunked'
, 'Content-Type': 'video/mp4'
, 'Content-Length': chunksize // large size to fake a file
, 'Accept-Ranges': 'bytes ' + start + "-" + end + "/" + total
});
3) 客户端必须使用 HTML5 视频标签。
我对使用 fs.createReadStream 和 206 HTTP 部分内容将上述 FFMPEG 命令行录制的视频文件(而不是保存到 STDOUT)流式传输到 HTML5 客户端没有问题,所以我知道 FFMPEG 流是正确的,当连接到 HTTP 节点服务器时,我甚至可以在 VLC 中实时正确地看到视频流。
然而,尝试从 FFMPEG 通过 node HTTP 实时流式传输似乎要困难得多,因为客户端将显示一个帧然后停止。我怀疑问题在于我没有设置与 HTML5 视频客户端兼容的 HTTP 连接。我尝试了各种方法,例如使用 HTTP 206(部分内容)和 200 响应,将数据放入缓冲区然后进行流式传输,但都没有成功,因此我需要回归基本原理以确保我正在正确设置此项工作。
以下是我的理解,如果我错了,请纠正我:
1) FFMPEG 应该设置输出片段并使用空 moov(FFMPEG frag_keyframe 和 empty_moov mov 标志)。这意味着客户端不使用通常在文件末尾的 moov 原子,这在流式传输时不相关(没有文件结尾),但意味着不能进行寻址,这对我的用例来说没问题。
2)即使我使用 MP4 片段和空 MOOV,我仍然需要使用 HTTP 部分内容,因为 HTML5 播放器将等待整个流下载完成后再播放,但实时流永远不会结束,所以无法使用。
3)我不明白为什么将 STDOUT 流传输到 HTTP 响应时,在实时流式传输中不起作用,而如果保存到文件,则可以使用类似的代码轻松地将该文件流式传输到 HTML5 客户端。也许这是一个时间问题,因为需要一秒钟才能启动 FFMPEG spawn,连接到 IP 摄像头并向节点发送块,并且节点数据事件也是不规则的。但是字节流应该与保存到文件完全相同,并且 HTTP 应该能够处理延迟。
4) 当使用由FFMPEG从摄像机创建的MP4文件进行流式传输时,检查HTTP客户端的网络日志,我看到有3个客户端请求: 一个常规的视频GET请求,HTTP服务器返回约40Kb,然后是一个带有文件最后10K字节范围的部分内容请求,最后是位于中间且未加载的位。也许HTML5客户端在接收到第一个响应后正在请求文件的最后一部分来加载MP4 MOOV原子?如果是这种情况,它不适用于流媒体,因为没有MOOV文件和文件结尾。
5) 当尝试进行直播流式传输时,检查网络日志,发现初始请求被中止,只收到了大约200字节,然后重新请求再次中止,收到了200字节,并且第三个请求只有2K长。我不理解为什么HTML5客户端会中止请求,因为字节流与我成功从录制文件流式传输时使用的完全相同。它还似乎node没有将剩余的FFMPEG流发送给客户端,但我可以看到.on事件例程中的FFMPEG数据,因此它到达了FFMPEG节点HTTP服务器。
6) 虽然我认为将STDOUT流导向HTTP响应缓冲区应该有效,但我是否必须构建一个中间缓冲区并将其流式传输以使HTTP部分内容客户端请求像从文件中读取一样正常工作?我认为这是我的问题的主要原因,但在Node中如何最好地设置它,我不确定。而且我不知道如何处理对文件末尾数据的客户端请求,因为没有文件结尾。
7) 我是否在尝试处理206部分内容请求时犯了错,这应该使用正常的200 HTTP响应吗?VLC对HTTP 200响应正常工作,所以我怀疑HTML5视频客户端只能使用部分内容请求进行工作?
由于我仍在学习这方面的知识,因此很难理解这个问题的各个层面(FFMPEG、节点、流、HTTP、HTML5视频),因此非常感激任何指点。我已经在这个网站和网络上花了数小时进行研究,但我没有找到任何能够在节点中进行实时流传输的人,但我不能是第一个,我认为这应该能够工作(某种方式)!
Content-Type
吗?你正在使用分块编码吗?这是我开始的地方。此外,HTML5并不一定提供流式传输的功能,你可以在这里阅读更多内容。你很可能需要实现一种缓冲和使用自己的方法播放视频流的方式,参见这里,尽管这可能得到的支持不好。还可以搜索MediaSource API。 - tsturzl