我真的很难理解使用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视频标签 .
流式播放(使用带有206 HTTP部分内容的fs.createReadStream)到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视频),所以任何指针都将非常感激 . 我花了几个小时研究这个网站和网络,我没有遇到任何能够在节点中进行实时流媒体的人,但我不能成为第一个,我认为这应该可以工作(不知何故!) .
9 回答
此行以下的所有内容都已过期 . 为后代留在这里 .
视频,特别是直播视频非常困难的原因有很多 . (请注意,原始问题指出HTML5视频是一项要求,但提问者在评论中说明Flash是可能的 . 所以立即,这个问题具有误导性)
首先我要重述: THERE IS NO OFFICIAL SUPPORT FOR LIVE STREAMING OVER HTML5 . 有黑客,但你的里程可能会有所不同 .
接下来,您需要了解视频点播(VOD)和实时视频是非常不同的 . 是的,它们都是视频,但问题不同,因此格式不同 . 例如,如果计算机中的时钟运行速度比应有的速度快1%,则您不会注意到VOD . 使用实时视频,您将尝试播放视频 . 如果要加入正在进行的实时视频流,则需要初始化解码器所需的数据,因此必须在流中重复,或者在带外发送 . 使用VOD,您可以阅读文件的开头,搜索到您想要的任何点 .
现在让我们深入挖掘一下 .
平台:
iOS
PC
Mac
Android
编解码器:
vp8 / 9
h.264
thora(vp3)
浏览器中实时视频的常用交付方法:
DASH(HTTP)
HLS(HTTP)
flash(RTMP)
flash(HDS)
浏览器中VOD的常用交付方式:
DASH(HTTP流)
HLS(HTTP流)
flash(RTMP)
flash(HTTP Streaming)
MP4(HTTP伪流媒体)
我不打算谈论MKV和OOG,因为我不太了解它们 .
html5视频标签:
MP4
webm
ogg
让我们看看哪些浏览器支持哪些格式
苹果浏览器:
HLS(仅适用于iOS和Mac)
h.264
MP4
火狐
DASH(通过MSE但没有h.264)
h.264仅限Flash!
VP9
MP4
OGG
Webm
IE
Flash
DASH(仅限MSE IE 11)
h.264
MP4
铬
Flash
DASH(通过MSE)
h.264
VP9
MP4
webm
ogg
MP4不能用于实时视频(注意:DASH是MP4的超集,所以不要混淆) . MP4分为两部分:moov和mdat . mdat包含原始音频视频数据 . 但它没有索引,所以没有moov,它就没用了 . moov包含mdat中所有数据的索引 . 但由于其格式,在每个帧的时间戳和大小已知之前,它不能被“展平” . 有可能构造一个“纤维化”框架尺寸的moov,但带宽非常浪费 .
因此,如果您想在任何地方提供服务,我们需要找到最小的共同点 . 你会看到这里没有LCD而不采用flash示例:
iOS仅支持h.264视频 . 它只支持HLS for live .
Firefox除非你使用闪光灯,否则根本不支持h.264
Flash在iOS中不起作用
与LCD最接近的是使用HLS来吸引iOS用户,并为其他人使用闪存 . 我个人最喜欢的是对HLS进行编码,然后使用flash为其他人播放HLS . 您可以通过JW播放器6在闪存中播放HLS,(或者像我一样在AS3中将您自己的HLS写入FLV)
很快,最常见的方法是在iOS / Mac上使用HLS,在其他地方通过MSE使用DASH(这就是Netflix即将推出的功能) . 但我们仍在等待所有人升级他们的浏览器 . 您可能还需要一个单独的DASH / VP9 for Firefox(我知道open264;它很糟糕 . 它无法在主要或高调中播放视频 . 因此它目前无用) .
感谢大家特别是szatmary,因为这是一个复杂的问题,并且有很多层,所有这些都必须在流媒体视频流之前工作 . 为了澄清我的原始问题和HTML5视频使用vs flash - 我的用例非常偏爱HTML5,因为它是通用的,易于在客户端和未来实现 . Flash是第二好的,所以我们坚持使用HTML5来解决这个问题 .
我通过这个练习学到了很多,并且同意实时流式传输比VOD(与HTML5视频配合良好)相比要困难得多 . 但我确实让我的用例令人满意,并且在追逐更复杂的选项如MSE,flash,精心设计的Node缓冲方案后,解决方案变得非常简单 . 问题是FFMPEG正在破坏碎片化的MP4,我不得不调整FFMPEG参数,并且我最初使用的http标准节点流管道重定向就是所需要的 .
在MP4中有一个'碎片'选项,可以将mp4分成更小的片段,这些片段有自己的索引并使mp4实时流媒体选项可行 . 但是不可能回到流中(对我的用例来说没问题),以及更高版本的FFMPEG支持碎片 .
注意时间可能是一个问题,并且在我的解决方案中,由于重新组合(有效的FFMPEG必须接收实时流,然后将其发送到节点以通过HTTP服务)而导致滞后2到6秒 . 关于这一点可以做的不多,但是在Chrome中,视频确实试图尽可能多地追赶,这使得视频有点跳跃但比IE11(我的首选客户端)更新 .
不是在这篇文章中解释代码的工作方式,而是使用注释查看GIST(不包括客户端代码,它是带有节点http服务器地址的标准HTML5视频标签) . GIST在这里:https://gist.github.com/deandob/9240090
我无法找到这个用例的类似例子,所以我希望上面的解释和代码可以帮助其他人,特别是因为我从这个网站学到了很多,并且仍然认为自己是初学者!
虽然这是我的具体问题的答案,但我选择了szatmary的答案作为被接受的答案,因为它是最全面的 .
看看JSMPEG项目 . 在那里实现了一个很棒的想法 - 使用JavaScript在浏览器中解码MPEG . 例如,编码器(例如,FFMPEG)的字节可以使用WebSockets或Flash传输到浏览器 . 如果社区能赶超,我想,它将是目前最好的HTML5直播视频流解决方案 .
我在broadway h264编解码器(emscripten)上编写了一个HTML5视频播放器,可以在所有浏览器(桌面,iOS,...)上播放实时(无延迟)h264视频 .
视频流通过websocket发送到客户端,每帧解码帧并显示在canva中(使用webgl进行加速)
在github上查看https://github.com/131/h264-live-player .
将基于RTSP的网络摄像头直播到HTML5客户端的一种方法(涉及重新编码,因此期望质量损失并需要一些CPU功率):
设置一个icecast服务器(可以在你的网络服务器所在的同一台机器上,或者在从凸轮接收RTSP流的机器上)
在从相机接收流的机器上,不要使用FFMPEG而是使用gstreamer . 它能够接收和解码RTSP流,对其进行重新编码并将其流式传输到icecast服务器 . 示例管道(仅视频,无音频):
=>然后,您可以将<video>标记与icecast-stream的URL(http://127.0.0.1:12000/cam.webm)一起使用,它将在支持webm的每个浏览器和设备中都有效
看看this solution . 据我所知,Flashphoner允许在纯HTML5页面中播放实时音频视频流 .
他们使用 MPEG1 和 G.711 编解码器进行播放 . 黑客将解码视频渲染为HTML5画布元素,并通过HTML5音频上下文播放解码后的音频 .
如何使用jpeg解决方案,让服务器逐个将jpeg分发到浏览器,然后使用canvas元素绘制这些jpeg? http://thejackalofjavascript.com/rpi-live-streaming/
这是一种非常常见的误解 . 没有实时HTML5视频支持(iOS和Mac Safari上的HLS除外) . 您可以使用webm容器“破解”它,但我不希望它得到普遍支持 . 您要查找的内容包含在Media Source Extensions中,您可以将片段一次一个地提供给浏览器 . 但你需要写一些客户端javascript .
尝试binaryjs . 就像socket.io一样,但它唯一能做的就是流式传输音频视频 . Binaryjs google它