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


212

我真的很想了解使用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视频标签。

我没有流传输回放的问题(使用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视频),因此任何指针将不胜感激。我已经花了数小时在此站点和网络上进行研究,但我还没有遇到能够在节点中进行实时流式传输的任何人,但我不能成为第一个,而且我认为这应该可以工作(以某种方式!)。


4
这是一个棘手的话题。首先是第一。你设置你Content-Type在你的头上?您是否在使用块编码?那就是我要开始的地方。另外,HTML5不一定提供流功能,您可以在此处阅读更多内容。您很可能需要实现一种使用自己的方式来缓冲和播放视频流的方法(请参见此处),以为可能无法很好地支持此方法。也谷歌到MediaSource API。
tsturzl 2014年

谢谢回复。是的,内容类型为“视频/ mp4”,此代码适用于流视频文件。不幸的是MediaSource仅是chrome,我必须支持其他浏览器。是否有关于HTML5视频客户端如何与HTTP流服务器交互的规范?我确定我想做的事,只是不确定具体如何做(使用node.js,但如果更容易,也可以使用C#或C ++)
deandob 2014年

2
问题不在您的后端。您正在流式传输视频就好了。问题出在您的前端/客户端中,您需要自己实现流式传输。HTML5根本不处理流。您最有可能需要浏览每个浏览器的选项。阅读视频标签和媒体API的w3标准将是一个不错的起点。
tsturzl 2014年

似乎应该可以使这项工作可行。我没有提供确切的答案,但是我怀疑这个问题与浏览器期望mp4容器标头/原子的其余部分在开始而不是在视频流中的下一帧有关。如果您发送MOOV原子以获得非常长的视频(以便播放器不断请求)以及其他预期的标头,然后开始从ffmpeg复制出来,则可能有效。您还必须在浏览器中使用js隐藏清理栏,以便它们无法向前扫描。
jwriteclub 2014年

我建议考虑WebRTC,它每天都在获得更好的跨浏览器支持。
Alex Cohn 2014年

Answers:


209

编辑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,您可以阅读他们希望找到的文件的开头。

现在让我们深入探讨。

平台:

  • 的iOS
  • 个人电脑
  • 苹果电脑
  • 安卓

编解码器:

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

浏览器中直播视频的常见交付方式:

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

浏览器中VOD的常见投放方式:

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

html5视频标签:

  • MP4
  • Webm
  • 奥格

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

苹果浏览器:

  • HLS(仅iOS和Mac)
  • h.264
  • MP4

火狐浏览器

  • DASH(通过MSE,但没有h.264)
  • h.264仅通过Flash!
  • VP9
  • MP4
  • 奥格
  • 韦伯

IE浏览器

  • DASH(仅通过MSE IE 11+)
  • h.264
  • MP4

  • DASH(通过MSE)
  • h.264
  • VP9
  • MP4
  • Webm
  • 奥格

MP4不能用于实时视频(注意:DASH是MP4的超集,因此不要对此感到困惑)。MP4分为两部分:moov和mdat。mdat包含原始音频视频数据。但是它没有被索引,因此没有moov,它是无用的。moov包含mdat中所有数据的索引。但是由于其格式,在知道每个帧的时间戳和大小之前,无法将其“展平”。可能有可能构建一个“使帧大小变小”的模型,但是在带宽方面却非常浪费。

因此,如果您想在任何地方交付,我们需要找到最小公分母。您将看到这里没有LCD而不使用Flash示例:

  • iOS仅支持h.264视频。而且仅支持HLS直播。
  • Firefox完全不支持h.264,除非您使用Flash
  • Flash无法在iOS中使用

最接近LCD的是使用HLS来吸引您的iOS用户,并为其他所有人闪烁。我个人最喜欢的是对HLS进行编码,然后使用Flash为其他所有人播放HLS。您可以通过JW播放器6在Flash中播放HLS(或像我一样将自己的HLS写入AS3中的FLV)

很快,最常见的方式将是在iOS / Mac上使用HLS,并在其他任何地方通过MSE通过DASH(这是Netflix即将采取的措施)。但是我们仍在等待所有人升级他们的浏览器。您可能还需要为Firefox使用单独的DASH / VP9(我知道open264;它很烂。它不能以主要或高姿态播放视频。因此,它目前无用)。


感谢szatmary提供详细的背景知识以及有关各种选项的赞成/反对意见。我选择此答案作为可接受的答案,因为概念的概述比我找到的回答原始问题的特定解决方案更为重要。祝您好运!
deandob 2014年

9
这不是这个问题的可行解决方案。下面有一个有效的解决方案。
jwriteclub 2014年

2
Firefox现在原生支持MSE和h.264。使用最新的FF浏览器访问www.youtube.com/html5进行确认。我使用FF 37进行了测试。Mac上的Safari 8+现在也支持MSE。
BigTundra

@BigTundra是的,自从优胜美地在Mac上发布以来,Safari就支持MSE。但不是iOS。不确定Windows。(Windows上的野生动物园是否仍然有用?)根据该链接,(我)Mac上的Firefox 37.0.2似乎根本不支持MSE。但确实支持H.264。Firefox在过去已添加和删除并重新添加了对H.264的支持。
szatmary

最新的浏览器支持MPEG-4 / H.264视频格式:caniuse.com/#feat=mpeg4
Maxence

75

谢谢大家,特别是szatmary,因为这是一个复杂的问题,涉及很多层,所有这些层都必须先工作,然后才能传输实时视频。为了澄清我的原始问题和HTML5视频使用与Flash的关系-我的用例非常喜欢HTML5,因为它是通用的,易于在客户端和将来实现。Flash是遥遥领先的第二好选择,因此我们坚持使用HTML5解决这个问题。

通过这个练习,我学到了很多东西,并且同意直播比VOD难得多(这对HTML5视频很有效)。但是在我的用例中,确实做到了这一点,令人满意的解决方案在解决了诸如MSE,Flash,Node中精心设计的缓冲方案等更复杂的选项之后,解决方案非常简单。问题是FFMPEG破坏了碎片化的MP4,我不得不调整FFMPEG参数,而我最初使用的HTTP上的标准节点流管道重定向就是所需要的。

在MP4中,有一个“片段化”选项,可将mp4分成更小的片段,这些片段具有自己的索引,并使mp4实时流式传输选项可行。但是不可能找回流(对于我的用例来说还可以),并且更高版本的FFMPEG支持分段。

注意时间安排可能是个问题,在我的解决方案中,由于重混合的原因,我有2到6秒的延迟(有效的FFMPEG必须接收实时流,将其重新混合,然后将其发送到节点以通过HTTP服务) 。无法对此做很多事情,但是在Chrome中,视频确实会尽一切可能赶上来,这使视频有点跳动,但比IE11(我的首选客户端)更新了。

除了在本文中解释代码的工作方式之外,还请查看带有注释的GIST(不包括客户端代码,它是带有节点http服务器地址的标准HTML5视频标记)。GIST在这里:https : //gist.github.com/deandob/9240090

我无法找到该用例的类似示例,因此我希望上面的解释和代码对其他人有所帮助,特别是因为我从该站点中学到了很多,并且仍然认为自己是初学者!

尽管这是我特定问题的答案,但我选择szatmary的答案作为公认的答案,因为它是最全面的答案。


33
抱歉,但是我是自己发现的,答案的记载清楚地表明了这一点。较早的答案都是有用的,值得赞赏,但贡献不大,我什至在GIST中提交了工作代码,其他人都没有。我对“声誉”不感兴趣,对了解是否可以改进我的方法和代码感兴趣。我选择的答案确实解决了我的问题,所以我对这里的问题感到困惑。我对SO相当陌生,因此很高兴被告知以其他方式进行交互,我发现此站点很有用,我的回答也应该对其他人有所帮助。
deandob 2014年

2
如果您提出问题,即使它确实解决了原始问题,似乎也不能选择您的答案作为接受的答案。尽管这似乎违反直觉,但是概念的文档比实际的修复更为重要,我可以接受,因为它可以帮助其他人学习。我取消选择了我的答案,并选择szatmary作为有关概念的最清楚的表述。
deandob 2014年

6
@deandob:我为您成功提供的此问题有效解决方案发布了赏金。公认的答案断言没有可行的解决方案,因此显然是不准确的。
jwriteclub 2014年

2
谢谢。似乎其他人认为我的原始答案是错误的,而当我是新手时,我只是假设事情就是这样。我不想引起任何大惊小怪,但我将与有关meta堆栈溢出的人员进行检查。顺便说一句-我的解决方案工作得很好,并且应该对其他人也可行,而且发布的解决方案也有所不同,可以减少初始滞后(首先在node.js中缓冲,然后在客户端寻求流的末端) 。
deandob 2014年

4
主持人向我澄清,我自己回答问题并选择答案作为答案的原始方法是正确的方法。有关更多信息(或者如果您想进一步辩论),请参见元网站上的主题。meta.stackexchange.com/questions/224068/...
deandob

14

看一下JSMPEG项目。在那里实现了一个好主意-使用JavaScript在浏览器中解码MPEG。例如,可以使用WebSockets或Flash将来自编码器(例如FFMPEG)的字节传输到浏览器。我认为,如果社区能够赶上潮流,它将是目前最好的HTML5实时视频流解决方案。


10
那是MPEG-1视频解码器。我不确定您了解MPEG-1有多古老;它比DVD更旧。它多于GIF文件前进。
卡米洛·马丁

13

我围绕百老汇h264编解码器(脚本)编写了HTML5视频播放器,该播放器可以在所有浏览器(台式机,iOS等)上实时(无延迟)播放h264视频。

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

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


1
github.com/Streamedian/html5_rtsp_player这些家伙做了类似的事情,在websocket上使用rtp h264
Victor.dMdB

12

将基于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

=>然后,可以将<video>标记与icecast-stream的URL(http://127.0.0.1:12000/cam.webm)一起使用,它将在支持webm的所有浏览器和设备中运行


3

看一下这个解决方案。据我所知,Flashphoner允许在纯HTML5页面中播放实时音频和视频流。

他们使用MPEG1G.711编解码器进行播放。黑客将解码后的视频渲染到HTML5 canvas元素,并通过HTML5音频上下文播放解码后的音频。



2

这是一个非常普遍的误解。没有实时HTML5视频支持(iOS和Mac Safari上的HLS除外)。您也许可以使用webm容器来“破解”它,但是我不希望它得到普遍支持。您要查找的内容包含在Media Source Extensions中,您可以在其中一次将片段提供给浏览器。但是您将需要编写一些客户端javascript。


有,solutions但没有support直播。这直接是指我上面看到的评论。在主要的浏览器上都支持webm,其中大多数是最新的稳定版本。
tsturzl 2014年

1
我真的希望不要将H.264转换为webm,这不是必须的。另外,由于我必须支持IE11和Safari,因此MediaSource扩展也无济于事。但是我想,如果我在服务器端模拟文件流(可以工作!),那么它应该可以工作,但是我必须在node.js上模拟文件缓冲区。
deandob

1
正如其他建议一样,我将寻求使用WebRTC的可能性,该WebRTC与VLC或Flash插件不同。我知道这项技术仍然很难实施。祝好运。

1
我通过更新到最新版本的FFMPEG来解决这个问题,因为使用分段模式时mp4似乎出现了损坏(MP4实时流式传输是必需的,因此客户端不必等待moov索引文件,实时运行时永远不会出现)流媒体)。现在,我的将FFMPEG流直接重定向到浏览器的nod​​e.js代码现在可以使用了。
deandob 2014年

1
是的,在IE11(我的首选浏览器)上可以正常工作。我在Chrome中反应迅速。
deandob 2014年

2

试试binaryjs。它就像socket.io一样,但它唯一能做的就是播放音频视频。Binaryjs谷歌它


1
Binary.JS与Socket.IO完全不同。而且,它并不特定于媒体流。
布拉德(Brad)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.