【发布时间】:2014-03-22 05:18:57
【问题描述】:
我真的很难理解使用 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) 我使用节点 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 视频标签。
我对 HTML5 客户端的流式播放(使用 fs.createReadStream 和 206 HTTP 部分内容)没有问题,之前使用上述 FFMPEG 命令行录制的视频文件(但保存到文件而不是 STDOUT),所以我知道FFMPEG流是正确的,连接HTTP节点服务器时,我什至可以在VLC中正确看到视频直播。
但是,尝试通过节点 HTTP 从 FFMPEG 实时流式传输似乎要困难得多,因为客户端将显示一帧然后停止。我怀疑问题是我没有将 HTTP 连接设置为与 HTML5 视频客户端兼容。我尝试了各种方法,例如使用 HTTP 206(部分内容)和 200 响应,将数据放入缓冲区然后流式传输没有运气,所以我需要回到第一原则以确保我设置正确方式。
这是我对它应该如何工作的理解,如果我错了,请纠正我:
1) 应设置 FFMPEG 以分段输出并使用空 moov(FFMPEG frag_keyframe 和 empty_moov mov 标志)。这意味着客户端不使用通常位于文件末尾的 moov 原子,这在流式传输时不相关(没有文件结尾),但意味着不可能进行搜索,这对我的用例来说很好。
2) 即使我使用 MP4 片段和空 MOOV,我仍然必须使用 HTTP 部分内容,因为 HTML5 播放器会等到整个流下载后才能播放,而直播流永远不会结束,所以是行不通的。
3) 我不明白为什么在实时流式传输时将 STDOUT 流传输到 HTTP 响应不起作用但是如果我保存到一个文件,我可以使用类似的代码轻松地将该文件流式传输到 HTML5 客户端。也许这是一个时间问题,因为 FFMPEG 生成开始、连接到 IP 摄像机并将块发送到节点需要一秒钟,并且节点数据事件也是不规则的。但是,字节流应该与保存到文件完全相同,并且 HTTP 应该能够满足延迟。
4) 当从摄像机流式传输由 FFMPEG 创建的 MP4 文件时,从 HTTP 客户端检查网络日志时,我看到有 3 个客户端请求: 视频的一般 GET 请求,HTTP 服务器返回约 40Kb,然后是文件最后 10K 字节范围的部分内容请求,然后是对中间未加载位的最终请求。也许 HTML5 客户端收到第一个响应后会要求文件的最后一部分加载 MP4 MOOV atom?如果是这种情况,它将不适用于流式传输,因为没有 MOOV 文件,也没有文件结尾。
5) 在尝试实时流式传输时检查网络日志时,我收到一个中止的初始请求,仅收到大约 200 个字节,然后重新请求再次中止,收到 200 个字节,第三个请求只有 2K 长。我不明白为什么 HTML5 客户端会中止请求,因为字节流与从记录文件流式传输时可以成功使用的字节流完全相同。似乎节点没有将其余的 FFMPEG 流发送到客户端,但我可以在 .on 事件例程中看到 FFMPEG 数据,因此它正在到达 FFMPEG 节点 HTTP 服务器。
6) 尽管我认为将 STDOUT 流通过管道传输到 HTTP 响应缓冲区应该可以工作,但我是否必须构建一个中间缓冲区和流,以允许 HTTP 部分内容客户端请求像它(成功)时一样正常工作读取文件?我认为这是我的问题的主要原因,但是我不确定在 Node 中如何最好地设置它。而且我不知道如何处理客户端对文件末尾数据的请求,因为没有文件结尾。
7) 我在尝试处理 206 个部分内容请求时是否走错了路,这是否适用于正常的 200 个 HTTP 响应? HTTP 200 响应适用于 VLC,所以我怀疑 HTML5 视频客户端只能处理部分内容请求?
由于我仍在学习这些东西,因此很难解决这个问题的各个层面(FFMPEG、节点、流式传输、HTTP、HTML5 视频),因此任何指针都将不胜感激。我花了几个小时在这个网站和网络上进行研究,我还没有遇到任何能够在节点中进行实时流式传输的人,但我不能成为第一个,我认为这应该能够工作(不知何故!)。
【问题讨论】:
-
感谢您的回复。是的,内容类型是“视频/mp4”,此代码适用于流式视频文件。不幸的是 MediaSource 只是 chrome,我必须支持其他浏览器。是否有关于 HTML5 视频客户端如何与 HTTP 流服务器交互的规范?我确定我想做什么,只是不确定如何(使用 node.js,但如果更容易可以使用 C# 或 C++)
-
问题不在您的后端。您正在流式传输视频就好了。问题出在您的前端/客户端,您需要自己实现流式传输。 HTML5 根本不处理流。您最有可能需要探索每个浏览器的选项。阅读视频标签和媒体 API 的 w3 标准将是一个很好的起点。
-
似乎应该可以完成这项工作。我没有提供明确的答案,但我怀疑这个问题与浏览器期望 mp4 容器标头/原子的其余部分在开头而不是 而不是视频流。如果您为很长的视频(以便播放器不断请求)以及其他预期的标头发送 MOOV atom,然后开始从 ffmpeg 复制,这可能会起作用。您还必须在浏览器中使用 js 隐藏擦洗栏,以便它们无法向前扫描。
-
我建议考虑WebRTC,它每天都在获得更好的跨浏览器支持。
标签: html node.js ffmpeg streaming