我真的很难理解使用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 原子?如果是这种情况,它将不适用于流式传输,因为没有 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 视频),因此任何指针都将不胜感激。我花了几个小时在这个网站和网络上进行研究,我还没有遇到任何能够在节点中进行实时流式传输的人,但我不能成为第一个,我认为这应该能够工作(不知何故!)。
编辑 3:从 IOS 10 开始,HLS 将支持分段的 mp4 文件。现在的答案是使用 DASH 和 HLS 清单创建碎片化的 mp4 资产。> 假装flash,iOS9及以下和IE 10及以下不存在。
编辑2:正如评论中的人指出的那样,事情发生了变化。几乎所有浏览器都将支持 AVC/AAC 编解码器。iOS 仍然需要 HLS。但是通过 hls.js 之类的适配器,您可以在 MSE 中播放 HLS。如果您需要 iOS,新的答案是 HLS+hls.js。或者只是碎片化的 MP4(即 DASH),如果你不这样做的话
视频,特别是直播视频非常困难的原因有很多。(请注意,原始问题指定 HTML5 视频是必需的,但提问者在评论中说 Flash 是可能的。所以立即,这个问题具有误导性)
首先我要重申: 没有官方支持通过 HTML5 进行直播。有黑客,但你的里程可能会有所不同。
编辑:自从我写了这个答案以来,媒体源扩展已经成熟,现在非常接近成为一个可行的选择。大多数主要浏览器都支持它们。IOS 仍然是一个坚持。
接下来,您需要了解视频点播 (VOD) 和视频直播是非常不同的。是的,它们都是视频,但问题不同,因此格式不同。例如,如果您计算机中的时钟运行速度比应有的快 1%,您将不会注意到 VOD。使用实时视频,您将尝试在视频发生之前播放视频。如果要加入正在进行的实时视频流,则需要初始化解码器所需的数据,因此必须在流中重复,或带外发送。使用 VOD,您可以阅读他们寻找的文件的开头到您希望的任何位置。
现在让我们深入研究一下。
Platforms:
Codecs:
Common Delivery methods for live video in browsers:
Common Delivery methods for VOD in browsers:
html5 video tag:
Lets look at which browsers support what formats
Safari:
Firefox
IE
Chrome
MP4 不能用于实时视频(注意:DASH 是 MP4 的超集,所以不要混淆)。MP4 分为两部分:moov 和 mdat。mdat 包含原始音频视频数据。但是它没有被索引,所以没有moov,它是没用的。moov 包含 mdat 中所有数据的索引。但是由于它的格式,在每个帧的时间戳和大小已知之前,它不能被“展平”。可以构建一个“fibs”帧大小的moov,但在带宽方面非常浪费。
因此,如果您想在任何地方交付,我们需要找到最小公分母。如果不借助 flash 示例,您将看到这里没有 LCD:
最接近 LCD 的是使用 HLS 来获取您的 iOS 用户,并为其他所有人使用 flash。我个人最喜欢的是对 HLS 进行编码,然后使用 flash 为其他人播放 HLS。您可以通过 JW player 6 在 flash 中播放 HLS,(或者像我一样在 AS3 中将自己的 HLS 写入 FLV)
很快,最常见的方法将是 iOS/Mac 上的 HLS 和其他任何地方通过 MSE 的 DASH(这就是 Netflix 即将做的事情)。但我们仍在等待大家升级浏览器。您可能还需要一个单独的 DASH/VP9 用于 Firefox(我知道 open264;它很烂。它不能在主要或高配置中播放视频。所以它目前没用)。