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

222

我在尝试理解使用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)我使用Node.js的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 视频标签。

我对使用 fs.createReadStream 和 206 HTTP 部分内容将上述 FFMPEG 命令行录制的视频文件(而不是保存到 STDOUT)流式传输到 HTML5 客户端没有问题,所以我知道 FFMPEG 流是正确的,当连接到 HTTP 节点服务器时,我甚至可以在 VLC 中实时正确地看到视频流。

然而,尝试从 FFMPEG 通过 node HTTP 实时流式传输似乎要困难得多,因为客户端将显示一个帧然后停止。我怀疑问题在于我没有设置与 HTML5 视频客户端兼容的 HTTP 连接。我尝试了各种方法,例如使用 HTTP 206(部分内容)和 200 响应,将数据放入缓冲区然后进行流式传输,但都没有成功,因此我需要回归基本原理以确保我正在正确设置此项工作。

以下是我的理解,如果我错了,请纠正我:

1) FFMPEG 应该设置输出片段并使用空 moov(FFMPEG frag_keyframe 和 empty_moov mov 标志)。这意味着客户端不使用通常在文件末尾的 moov 原子,这在流式传输时不相关(没有文件结尾),但意味着不能进行寻址,这对我的用例来说没问题。

2)即使我使用 MP4 片段和空 MOOV,我仍然需要使用 HTTP 部分内容,因为 HTML5 播放器将等待整个流下载完成后再播放,但实时流永远不会结束,所以无法使用。

3)我不明白为什么将 STDOUT 流传输到 HTTP 响应时,在实时流式传输中不起作用,而如果保存到文件,则可以使用类似的代码轻松地将该文件流式传输到 HTML5 客户端。也许这是一个时间问题,因为需要一秒钟才能启动 FFMPEG spawn,连接到 IP 摄像头并向节点发送块,并且节点数据事件也是不规则的。但是字节流应该与保存到文件完全相同,并且 HTTP 应该能够处理延迟。

4) 当使用由FFMPEG从摄像机创建的MP4文件进行流式传输时,检查HTTP客户端的网络日志,我看到有3个客户端请求: 一个常规的视频GET请求,HTTP服务器返回约40Kb,然后是一个带有文件最后10K字节范围的部分内容请求,最后是位于中间且未加载的位。也许HTML5客户端在接收到第一个响应后正在请求文件的最后一部分来加载MP4 MOOV原子?如果是这种情况,它不适用于流媒体,因为没有MOOV文件和文件结尾。

5) 当尝试进行直播流式传输时,检查网络日志,发现初始请求被中止,只收到了大约200字节,然后重新请求再次中止,收到了200字节,并且第三个请求只有2K长。我不理解为什么HTML5客户端会中止请求,因为字节流与我成功从录制文件流式传输时使用的完全相同。它还似乎node没有将剩余的FFMPEG流发送给客户端,但我可以看到.on事件例程中的FFMPEG数据,因此它到达了FFMPEG节点HTTP服务器。

6) 虽然我认为将STDOUT流导向HTTP响应缓冲区应该有效,但我是否必须构建一个中间缓冲区并将其流式传输以使HTTP部分内容客户端请求像从文件中读取一样正常工作?我认为这是我的问题的主要原因,但在Node中如何最好地设置它,我不确定。而且我不知道如何处理对文件末尾数据的客户端请求,因为没有文件结尾。

7) 我是否在尝试处理206部分内容请求时犯了错,这应该使用正常的200 HTTP响应吗?VLC对HTTP 200响应正常工作,所以我怀疑HTML5视频客户端只能使用部分内容请求进行工作?

由于我仍在学习这方面的知识,因此很难理解这个问题的各个层面(FFMPEG、节点、流、HTTP、HTML5视频),因此非常感激任何指点。我已经在这个网站和网络上花了数小时进行研究,但我没有找到任何能够在节点中进行实时流传输的人,但我不能是第一个,我认为这应该能够工作(某种方式)!


4
这是一个棘手的主题。首要的是,你在头部设置了Content-Type吗?你正在使用分块编码吗?这是我开始的地方。此外,HTML5并不一定提供流式传输的功能,你可以在这里阅读更多内容。你很可能需要实现一种缓冲和使用自己的方法播放视频流的方式,参见这里,尽管这可能得到的支持不好。还可以搜索MediaSource API。 - tsturzl
谢谢回复。是的,content-type 是 'video/mp4',这段代码适用于流式传输视频文件。不幸的是,MediaSource 只适用于 Chrome 浏览器,我必须支持其他浏览器。HTML5 视频客户端与 HTTP 流媒体服务器交互的规范是什么?我相信我想要的可以实现,只是不确定具体如何实现(使用 node.js,但如果更容易的话也可以使用 C# 或 C++)。 - deandob
2
问题不在你的后端。你的视频流传输得很好。问题出在你的前端/客户端,你需要自己实现流媒体传输。HTML5 简单地不能处理流媒体。你需要探索每个浏览器的选项。阅读 w3 标准中关于视频标签和媒体 API 的内容是一个好的起点。 - tsturzl
似乎应该可以使其工作。我不提供明确的答案,但我怀疑这个问题与浏览器期望在视频流的下一帧之前在mp4容器头/原子中包含剩余部分有关。如果您为非常长的视频发送MOOV原子(以便播放器继续请求)以及其他预期的标头,然后从ffmpeg开始复制,这可能有效。您还必须使用浏览器中的js隐藏刮擦栏,以便他们无法向前扫描。 - jwriteclub
我建议考虑使用WebRTC,它正在逐渐获得更好的跨浏览器支持。 - Alex Cohn
显示剩余3条评论
8个回答

215
编辑3:从IOS 10开始,HLS将支持分段mp4文件。现在的答案是创建分段的mp4资产,并带有DASH和HLS清单。假装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,您可以读取文件的开头,然后跳转到想要的任何点。

现在让我们深入一点。

平台:

  • iOS
  • PC
  • Mac
  • Android

编解码器:

  • vp8/9
  • h.264
  • thora (vp3)

常见的浏览器中直播视频的传送方式:

  • DASH (HTTP)
  • HLS (HTTP)
  • flash (RTMP)
  • flash (HDS)

常见的浏览器中点播视频的传送方式:

  • DASH (HTTP流)
  • HLS (HTTP流)
  • flash (RTMP)
  • flash (HTTP流)
  • MP4 (HTTP伪流)
  • 我不会讨论MKV和OOG,因为我对它们不太了解。

html5 video标记:

  • MP4
  • webm
  • ogg

让我们看看哪些浏览器支持什么格式

Safari:

  • HLS(仅限iOS和mac)
  • h.264
  • MP4

Firefox

  • DASH(通过MSE但无h.264)
  • 仅通过Flash使用h.264!
  • VP9
  • MP4
  • OGG
  • Webm

IE

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

Chrome

  • Flash
  • DASH(通过MSE)
  • h.264
  • VP9
  • MP4
  • webm
  • ogg

MP4不能用于直播视频(注意:DASH是MP4的一个超集,不要混淆)。MP4被分为两个部分:moov和mdat。mdat包含原始音视频数据。但它没有索引,因此没有moov就无用。moov包含mdat中所有数据的索引。但由于其格式,除非知道每一帧的时间戳和大小,否则无法“展开”。可能可以构造一个“欺骗”帧大小的moov,但在带宽方面非常浪费。

因此,如果您想随处传递,我们需要找到最低公共分母。您将看到,在不使用Flash的情况下,这里没有LCD

  • iOS仅支持h.264视频,而且仅支持HLS用于直播。
  • Firefox根本不支持h.264,除非您使用flash
  • Flash在iOS中不起作用

最接近LCD的方法是使用HLS为iOS用户提供服务,然后让其他人使用flash。 我的个人首选是对HLS进行编码,然后使用flash播放HLS以满足其他所有用户的需求。您可以通过JW player 6在flash中播放HLS(或像我一样使用AS3将HLS转换为FLV)

很快,处理这个问题的最常见方法将是在iOS / Mac上使用HLS,在其他所有地方使用DASH通过MSE(这是Netflix即将要做的)。但是,我们仍在等待所有人升级其浏览器。您还可能需要为Firefox准备一个单独的DASH / VP9(我知道open264;它不行。它无法在主或高级配置文件中处理视频。因此,目前没有用处)。


10
这并不是这个问题的可行解决方案。下面有一个可行的解决方案来解决这个问题。 - jwriteclub
2
Firefox现在原生支持MSE和h.264。请使用最新版本的Firefox浏览器访问www.youtube.com/html5进行确认。我已经测试过FF 37。Mac上的Safari 8+也支持MSE。 - BigTundra
@BigTundra 是的,自从 Yosemite 在 Mac 上发布以来,Safari 就支持 MSE。但是在 iOS 上不支持。不确定 Windows 是否支持。(Safari 在 Windows 上还存在吗?)我的 Mac 上的 Firefox 37.0.2 看起来根据那个链接根本不支持 MSE。但是它支持 H.264。Firefox 过去曾添加、删除和重新添加过 H.264 支持。 - szatmary
最新版本的浏览器支持 MPEG-4/H.264 视频格式:http://caniuse.com/#feat=mpeg4 - Maxence

77

感谢所有人,特别要感谢 szatmary。这是一个复杂的问题,有很多层面需要考虑,必须确保所有层面都正常才能进行直播。为了澄清我的原始问题和 HTML5 视频与 Flash 的使用情况,我的应用场景更喜欢 HTML5,因为它是通用、易于在客户端实现,而且未来可期。Flash 是次优选项,所以我们在这个问题上坚持使用 HTML5。

通过这个练习,我学到了很多,并认为直播比 VOD 难多了(VOD 与 HTML5 视频配合效果很好)。但是对于我的应用场景,我还是满意地解决了这个问题,而且解决方案非常简单。在尝试了更复杂的选项(如 MSE、Flash、Node 中的精细缓冲方案)后,我发现问题其实出在 FFMPEG 上,它会破坏分段 MP4,因此我必须调整 FFMPEG 参数,并且最初使用的标准 Node 流管道重定向到 HTTP 上就足够了。

在 MP4 中,有一个“分段”选项,将 MP4 分成更小的片段,每个片段都有自己的索引,使 MP4 直播选项成为可能。但是不可能在流中回溯(对我的应用场景没问题),而且后期版本的 FFMPEG 支持分段。

注意时间可能会成为一个问题,使用我的解决方案,我有 2 到 6 秒的延迟,这是由于复用(实际上 FFMPEG 必须接收直播流,重新组合它,然后将其发送到 Node 以通过 HTTP 服务)。不能对此做太多改进,但在 Chrome 中,视频会尽可能地赶上进度,这使得视频有点跳跃,但比 IE11 更及时(我的首选客户端)。

与其在本帖子中解释代码是如何工作的,不如查看包含注释的 GIST(客户端代码未包括在内,它是一个标准的 HTML5 视频标签,带有 Node HTTP 服务器地址)。GIST 在这里:https://gist.github.com/deandob/9240090

我找不到类似的使用情况的例子,所以我希望上述的解释和代码能够帮助其他人,特别是因为我从这个网站学到了很多,但仍然认为自己是初学者!

虽然这是对我的具体问题的答案,但我选择了szatmary的答案作为最全面的答案。


15

看看JSMPEG项目。这里有一个很好的想法——使用JavaScript在浏览器中解码MPEG。可以使用WebSockets或Flash等方式将来自编码器(例如FFMPEG)的字节传输到浏览器。如果社区能跟上,我认为它将是目前最好的HTML5实时视频流方案。


11
这是一个MPEG-1视频解码器。我不确定你是否理解MPEG-1有多古老,它比DVD还要早。它比GIF文件略微先进一些。 - Camilo Martin

13

将基于RTSP协议的网络摄像头实时流传输到HTML5客户端的一种方法(需要重新编码,因此可能会有质量损失并需要一些CPU功率):

  • 设置一个Icecast服务器(可以在您的Web服务器所在的计算机上或接收来自摄像机的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
    

=> 您可以使用 <video> 标签与 icecast 流的 URL (http://127.0.0.1:12000/cam.webm),它在支持 webm 的所有浏览器和设备上都可以工作。


13
我写了一个基于Broadway H264编解码器(Emscripten)的HTML5视频播放器,可以在所有浏览器(桌面,iOS等)上实时播放(无延迟)H264视频。
视频流通过Websocket发送到客户端,逐帧解码并在画布中显示(使用WebGL进行加速)。
请在github上查看https://github.com/131/h264-live-player

1
这些家伙做了类似的事情,使用WebSocket上的RTP H264:https://github.com/Streamedian/html5_rtsp_player - Victor.dMdB

3
请看这个解决方案。 据我所知,Flashphoner可以在纯HTML5页面中播放实时音视频流。
他们使用MPEG1G.711编解码器进行播放。 技术上的突破是将解码后的视频渲染到HTML5画布元素中,并通过HTML5音频上下文播放解码后的音频。

3

这是一个非常普遍的误解。除了iOS和Mac Safari上的HLS,没有现成的HTML5视频支持。您可以尝试使用webm容器进行“黑客”操作,但不建议普遍采用。您需要的是媒体源扩展(Media Source Extensions),在其中,您可以一次向浏览器提供一个碎片。但是,您需要编写一些客户端JavaScript代码。


1
我真的不想从H.264转码到webm,也不应该需要这样做。另外,由于我必须支持IE11和Safari,MediaSource扩展程序无法帮助我。但是,我认为如果在服务器端模拟文件流(这是有效的!),那么它应该可以工作,但我将不得不在node.js上模拟文件缓冲区。 - deandob
感谢大家的反馈。我将尝试设置一个node.js流和缓冲区来模拟node文件流,这对于这个设置是有效的,并探索W3规范,以了解HTML5视频播放器如何期望HTTP流。我绝对喜欢HTML5视频客户端,如果node缓冲区不起作用,我将查看IE11和Chrome的mediasource,然后放弃,如果无法使其正常工作并转向flash... 我会发布进展情况。 - deandob
1
正如其他人建议的那样,我会寻求使用WebRTC的可能性,这是本地的,不像VLC或Flash插件。我知道这项技术仍然很难实现。祝你好运。 - user1028880
1
我通过更新到最新版本的FFMPEG使其正常工作,因为在使用分段模式(对于MP4实时流传输是必需的,这样客户端就不必等待永远不会出现的moov索引文件)时,mp4中存在损坏。而且我的node.js代码现在可以直接将FFMPEG流重定向到浏览器上了。 - deandob
1
是的,在IE11上运行良好(我的首选浏览器)。在Chrome中,我得到了跳跃的响应。 - deandob
显示剩余9条评论

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接