我真的很想了解使用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);
我还使用了stream事件将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部分内容)到HTML5客户端,该视频文件以前是使用上述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,您可以阅读他们希望找到的文件的开头。
现在让我们深入探讨。
平台:
编解码器:
浏览器中直播视频的常见交付方式:
浏览器中VOD的常见投放方式:
html5视频标签:
让我们看看哪些浏览器支持什么格式
Safari:
Firefox
IE
Chrome
MP4不能用于实时视频(注意:DASH是MP4的超集,因此请不要对此感到困惑)。MP4分为两部分:moov和mdat。mdat包含原始音频视频数据。但是它没有被索引,因此没有moov,它是无用的。moov包含mdat中所有数据的索引。但是由于其格式,在知道每个帧的时间戳和大小之前,无法将其“展平”。可能有可能构建一个“使帧大小变小”的模型,但在带宽方面却非常浪费。
因此,如果您想在任何地方交付,我们需要找到最小公分母。您将看到这里没有LCD而不使用Flash示例:
最接近LCD的是使用HLS来吸引您的iOS用户,并为其他所有人闪烁。我个人最喜欢的是对HLS进行编码,然后使用Flash为其他所有人播放HLS。您可以通过JW播放器6在Flash中播放HLS(或像我一样将自己的HLS写入AS3中的FLV)
很快,最常见的方式将是在iOS/Mac上使用HLS,并在其他任何地方通过MSE通过DASH(这是Netflix即将采取的措施)。但是我们仍在等待所有人升级他们的浏览器。您可能还需要为Firefox使用单独的DASH/VP9(我知道open264;它很烂。它不能以主要或高姿态播放视频。因此,它目前无用)。