实时HTTP流式传输到HTML5视频客户端的最佳方法

dea*_*dob 204 streaming html5 ffmpeg node.js

我真的很难理解使用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});
Run Code Online (Sandbox Code Playgroud)

2)我使用节点http服务器捕获STDOUT并在客户端请求时将其流回客户端.当客户端第一次连接时,我生成上面的FFMPEG命令行,然后将STDOUT流传递给HTTP响应.

liveFFMPEG.stdout.pipe(resp);
Run Code Online (Sandbox Code Playgroud)

我还使用了流事件将FFMPEG数据写入HTTP响应,但没有任何区别

xliveFFMPEG.stdout.on("data",function(data) {
        resp.write(data);
}
Run Code Online (Sandbox Code Playgroud)

我使用以下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
});
Run Code Online (Sandbox Code Playgroud)

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视频),所以任何指针都将非常感激.我花了几个小时研究这个网站和网络,我没有遇到任何能够在节点中进行实时流媒体的人,但我不能成为第一个,我认为这应该可以工作(不知何故!).

sza*_*ary 208

编辑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上没有正式的支持.有黑客,但你的里程可能会有所不同.

编辑:自从我写了这个答案后,Media Source Extensions已经成熟,现在已经非常接近成为一个可行的选择.大多数主流浏览器都支持它们.内部监督办公室仍然是一个坚持.

接下来,您需要了解视频点播(VOD)和实时视频是非常不同的.是的,它们都是视频,但问题不同,因此格式不同.例如,如果计算机中的时钟运行速度比应有的速度快1%,则您不会注意到VOD.使用实时视频,您将尝试播放视频.如果要加入正在进行的实时视频流,则需要初始化解码器所需的数据,因此必须在流中重复,或者在带外发送.使用VOD,您可以阅读文件的开头,搜索到您想要的任何点.

现在让我们深入挖掘一下.

平台:

  • iOS版
  • 个人计算机
  • 苹果电脑
  • Android的

编解码器:

  • VP8/9
  • H.264
  • thora(vp3)

浏览器中实时视频的常用交付方法:

  • DASH(HTTP)
  • HLS(HTTP)
  • 闪存(RTMP)
  • 闪光灯(HDS)

浏览器中VOD的常用交付方式:

  • DASH(HTTP流)
  • HLS(HTTP流)
  • 闪存(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

  • DASH(仅限MSE IE 11+)
  • H.264
  • MP4

  • 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.
  • 除非你使用flash,否则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;它很糟糕.它无法在主要或高调中播放视频.因此它目前无用).

  • 这不是这个问题的有效解决方案.下面有一个解决这个问题的工作方案. (9认同)
  • 当它不是答案时,为什么这个标记为答案? (9认同)
  • Firefox现在支持MSE和h.264.使用最新的FF浏览器访问www.youtube.com/html5进行确认.我使用FF测试37. Mac上的Safari 8+现在也支持MSE. (2认同)

dea*_*dob 74

感谢大家特别是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的答案作为被接受的答案,因为它是最全面的.

  • 对不起,但我自己发现了这个问题,写下我的回答非常清楚.之前的答案都很有帮助和赞赏,但没有显着的贡献,我甚至在GIST中提交了工作代码而没有其他人.我对'声誉'不感兴趣,我有兴趣了解我的方法和代码是否可以改进.我已经勾选的答案确实解决了我的问题,所以我很困惑这里的问题.我对SO很新,所以我很高兴被告知以不同的方式进行互动,我发现这个网站很有用,我的回答应该有所帮助. (32认同)
  • @deandob:我为你成功提供的这个问题*发布了一个*工作解决方案的赏金.接受的答案断言没有可行的解决方案,因此显然不准确. (6认同)
  • 我从主持人那里得到澄清,我自己回答问题的原始方法,并选择答案是正确的方法.有关更多信息(或者如果您想进一步讨论这个问题),请参阅元网站上的主题.http://meta.stackexchange.com/questions/224068/debate-about-the-correct-answer-large-bounty-involved/224152?noredirect=1#224152 (4认同)
  • 似乎在这个社区中选择你的答案作为接受的答案是不正确的,如果你问这个问题,即使它确实解决了原来的问题.虽然这看起来有点反直觉,但概念的文档比实际修复更重要,我可以帮助其他人学习.我已经取消了我的答案,并选择了szatmary作为概念中最清晰的. (2认同)
  • 谢谢.似乎其他人已经将我原来的答案视为错误而且我是新的我只是假设这是事情在这里工作的方式.我不想引起任何大惊小怪,但我会检查有关元堆栈溢出的人.顺便说一句 - 我的解决方案工作得很好,它应该适用于其他人,并且发布的解决方案有一个变化,可以减少初始延迟(node.js中的缓冲区最初然后寻求在客户端结束流) . (2认同)
  • @jwriteclub然后你应该将赏金给予deandob的答案. (2认同)
  • 谢谢Vishwas - 当我在元堆栈溢出时发布了这个评论时得到了类似的评论.下次的经验教训. (2认同)
  • 请注意,HTML5视频客户端将始终缓冲传入的视频,因此无论您选择何种解决方案,使用HTML5视频客户端都会有延迟.如果您需要实时,则需要另一种解决方案. (2认同)

Mic*_*nko 13

看看JSMPEG项目.在那里实现了一个很棒的想法 - 使用JavaScript在浏览器中解码MPEG.例如,编码器(例如,FFMPEG)的字节可以使用WebSockets或Flash传输到浏览器.如果社区能赶超,我想,它将是目前最好的HTML5直播视频流解决方案.

  • 这是一个MPEG-1视频解码器.我不确定你是否了解MPEG-1的古老程度; 它比DVD更旧.它比GIF文件略高**. (10认同)

131*_*131 12

我在broadway h264编解码器(emscripten)上编写了一个HTML5视频播放器,可以在所有浏览器(桌面,iOS,...)上播放实时(无延迟)h264视频.

视频流通过websocket发送到客户端,每帧解码帧并显示在canva中(使用webgl进行加速)

在github上查看https://github.com/131/h264-live-player.


Jan*_*nis 11

将基于RTSP的网络摄像头直播到HTML5客户端的一种方法(涉及重新编码,因此期望质量损失并需要一些CPU功率):

  • 设置一个icecast服务器(可以在你的Web服务器所在的同一台机器上,或者在从cam接收RTSP流的机器上)
  • 在从相机接收流的机器上,不要使用FFMPEG而是使用gstreamer.它能够接收和解码RTSP流,对其进行重新编码并将其流式传输到icecast服务器.示例管道(仅视频,无音频):

    gst-launch-1.0 rtspsrc location=rtsp://192.168.1.234:554 user-id=admin user-pw=123456 ! rtph264depay ! avdec_h264 ! vp8enc threads=2 deadline=10000 ! webmmux streamable=true ! shout2send password=pass ip=<IP_OF_ICECAST_SERVER> port=12000 mount=cam.webm
    
    Run Code Online (Sandbox Code Playgroud)

=>然后,您可以将<video>标记与icecast-stream的URL(http://127.0.0.1:12000/cam.webm)一起使用,它将适用于支持webm的每个浏览器和设备