将实时 http 流式传输到 HTML5 视频客户端的最佳方法 [关闭]

Posted

技术标签:

【中文标题】将实时 http 流式传输到 HTML5 视频客户端的最佳方法 [关闭]【英文标题】:Best approach to real time http streaming to HTML5 video client [closed] 【发布时间】:2014-03-22 05:18:57 【问题描述】:

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

我对 HTML5 客户端的流式播放(使用 fs.createReadStream 和 206 HTTP 部分内容)没有问题,之前使用上述 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 atom?如果是这种情况,它将不适用于流式传输,因为没有 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 视频),因此任何指针都将不胜感激。我花了几个小时在这个网站和网络上进行研究,我还没有遇到任何能够在节点中进行实时流式传输的人,但我不能成为第一个,我认为这应该能够工作(不知何故!)。

【问题讨论】:

这是一个棘手的话题。第一件事是第一件事。您是否在脑海中设置了Content-Type?你在使用块编码吗?那就是我要开始的地方。此外,HTML5 不一定提供流式传输功能,您可以阅读更多关于 here 的信息。您很可能需要使用自己的方式(see here)实现一种缓冲和播放视频流的方法,认为这可能没有得到很好的支持。也谷歌进入 MediaSource API。 感谢您的回复。是的,内容类型是“视频/mp4”,此代码适用于流式视频文件。不幸的是 MediaSource 只是 chrome,我必须支持其他浏览器。是否有关于 HTML5 视频客户端如何与 HTTP 流服务器交互的规范?我确定我想做什么,只是不确定如何(使用 node.js,但如果更容易可以使用 C# 或 C++) 问题不在您的后端。您正在流式传输视频就好了。问题出在您的前端/客户端,您需要自己实现流式传输。 HTML5 根本不处理流。您最有可能需要探索每个浏览器的选项。阅读视频标签和媒体 API 的 w3 标准将是一个很好的起点。 似乎应该可以完成这项工作。我没有提供明确的答案,但我怀疑这个问题与浏览器期望 mp4 容器标头/原子的其余部分在开头而不是 而不是视频流。如果您为很长的视频(以便播放器不断请求)以及其他预期的标头发送 MOOV atom,然后开始从 ffmpeg 复制,这可能会起作用。您还必须在浏览器中使用 js 隐藏擦洗栏,以便它们无法向前扫描。 我建议考虑WebRTC,它每天都在获得更好的跨浏览器支持。 【参考方案1】:

编辑 3:从 ios 10 开始,HLS 将支持分段的 mp4 文件。答案 现在,是创建带有 DASH 和 HLS 清单的碎片化 mp4 资产。 > 假装flash,iOS9及以下和IE 10及以下不存在。

此行以下的所有内容都已过时。留在这里以供后代使用。


编辑 2:正如 cmets 中的人所指出的,事情发生了变化。 几乎所有浏览器都将支持 AVC/AAC 编解码器。 iOS 仍然需要 HLS。但是通过 hls.js 之类的适配器,你可以玩 MSE 中的 HLS。如果您需要 iOS,新的答案是 HLS+hls.js。要不就 如果您不这样做,则为碎片化 MP4(即 DASH)

视频,特别是直播视频非常困难的原因有很多。 (请注意,原始问题指定 HTML5 视频是必需的,但提问者说 Flash 在 cmets 中是可能的。所以立即,这个问题具有误导性)

首先我要重申:没有官方支持通过 HTML5 进行直播。有一些技巧,但你的里程可能会有所不同。

编辑:自从我写了这个答案以来,媒体源扩展已经成熟, 现在非常接近成为一个可行的选择。他们支持 在大多数主要浏览器上。 IOS 仍然是一个坚持。

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

现在让我们深入研究一下。

平台:

iOS 电脑 苹果机 安卓

编解码器:

vp8/9 h.264 thora (vp3)

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

DASH (HTTP) HLS (HTTP) 闪存 (RTMP) 闪存 (HDS)

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

DASH(HTTP 流) HLS(HTTP 流) 闪存 (RTMP) 闪存(HTTP 流) MP4(HTTP 伪流) 我不打算谈论 MKV 和 OOG,因为我不太了解它们。

html5视频标签:

MP4 网站 ogg

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

Safari:

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

火狐

DASH(通过 MSE,但没有 h.264) h.264 仅通过 Flash! VP9 MP4 OGG 网站

IE

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

闪光 DASH(通过 MSE) h.264 VP9 MP4 网站 ogg

MP4 不能用于实时视频(注意:DASH 是 MP4 的超集,所以不要混淆)。 MP4 分为两部分:moov 和 mdat。 mdat 包含原始音频视频数据。但是它没有被索引,所以没有moov,它是没用的。 moov 包含 mdat 中所有数据的索引。但是由于它的格式,在每个帧的时间戳和大小已知之前,它不能被“展平”。可以构建一个“fibs”帧大小的moov,但在带宽方面非常浪费。

因此,如果您想在任何地方交付,我们需要找到最小公分母。如果不使用闪光灯,您会看到这里没有 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 和其他任何地方通过 MSE 的 DASH(这是 Netflix 很快就会做的事情)。但我们仍在等待大家升级浏览器。您可能还需要一个单独的用于 Firefox 的 DASH/VP9(我知道 open264;它很烂。它不能在主要或高配置中播放视频。所以它目前没用)。

【讨论】:

这不是这个问题的有效解决方案。下面有一个解决这个问题的方法。 Firefox 现在原生支持 MSE 和 h.264。用最新的FF浏览器访问www.youtube.com/html5进行确认。我用 FF 37 测试过。Mac 上的 Safari 8+ 现在也支持 MSE。 @BigTundra 是的,自从 Yosemite 在 Mac 上推出以来,safari 就支持 MSE。但不是iOS。不确定Windows。 (Windows 上的 safari 仍然是一件事吗?)根据该链接,(我的)Mac 上的 Firefox 37.0.2 似乎根本不支持 MSE。但确实支持H.264。 Firefox 过去已添加、删除和重新添加 H.264 支持。 最新浏览器支持 MPEG-4/H.264 视频格式:caniuse.com/#feat=mpeg4【参考方案2】:

使用jpeg方案怎么样,让服务器将jpeg一张一张地分发给浏览器,然后用canvas元素来绘制这些jpeg? http://thejackalofjavascript.com/rpi-live-streaming/

【讨论】:

【参考方案3】:

我围绕百老汇 h264 编解码器 (emscripten) 编写了一个 HTML5 视频播放器,它可以在所有浏览器(桌面、iOS 等)上播放实时(无延迟)h264 视频。

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

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

【讨论】:

github.com/Streamedian/html5_rtsp_player 这些家伙做了类似的事情,在 websocket 上使用 rtp h264【参考方案4】:

看看this solution。 据我所知,Flashphoner 允许在纯 HTML5 页面中播放实时音频+视频流。

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

【讨论】:

【参考方案5】:

一种将基于 RTSP 的网络摄像头实时流式传输到 HTML5 客户端的方法(涉及重新编码,因此会出现质量损失并需要一些 CPU 能力):

设置一个 icecast 服务器(可以在您的网络服务器所在的同一台机器上,也可以在从 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

=> 然后您可以将

【讨论】:

【参考方案6】:

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

【讨论】:

solutions,但没有support 用于直播。这直接指的是我上面看到的评论。主流浏览器都支持 webm,主要是最新的稳定版本。 我真的不想从 H.264 转码到 webm,这应该没有必要。此外,由于我必须支持 IE11 和 Safari,MediaSource 扩展也无济于事。但我认为如果我在服务器端模拟一个文件流(这可行!)那么它应该可以工作,但我必须在 node.js 上模拟一个文件缓冲区。 正如其他人所建议的那样,我会寻求使用与 VLC 或 flash 插件不同的原生 WebRTC 的可能性。我知道这项技术仍然很难实施。祝你好运。 我通过更新到最新版本的 FFMPEG 来实现这一点,因为在使用分段模式时 mp4 似乎存在损坏(MP4 直播流所必需的,因此客户端不需要等待 moov实时流媒体时永远不会出现的索引文件)。我的 node.js 代码将 FFMPEG 流直接重定向到浏览器现在可以工作了。 是的,在 IE11(我的首选浏览器)上运行良好。我在 Chrome 中得到了紧张的反应。【参考方案7】:

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

【讨论】:

这是一个 MPEG-1 视频解码器。我不确定您是否了解 MPEG-1 的古老程度。它比 DVD 还早。它比 GIF 文件稍微高级。【参考方案8】:

感谢大家,尤其是 szatmary,因为这是一个复杂的问题,涉及到很多层面,所有这些都必须在您可以流式传输实时视频之前运行。为了澄清我最初的问题和 HTML5 视频使用 vs flash - 我的用例对 HTML5 有强烈的偏好,因为它是通用的,易于在客户端和未来实现。 Flash 是第二好的,所以让我们坚持使用 HTML5 来回答这个问题。

我通过这个练习学到了很多东西,并且同意直播比 VOD 更难(它适用于 HTML5 视频)。但在我的用例中,我确实让这个工作令人满意,而且解决方案非常简单,在追查了更复杂的选项(如 MSE、flash、Node.问题是 FFMPEG 破坏了碎片化的 MP4,我必须调整 FFMPEG 参数,而我最初使用的标准节点流管道重定向通过 http 就足够了。

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

注意时间可能是个问题,在我的解决方案中,我有 2 到 6 秒的延迟,这是由于重新混合(实际上 FFMPEG 必须接收实时流,重新混合然后将其发送到节点进行服务)通过 HTTP)。对此无能为力,但在 Chrome 中,视频确实会尽力赶上,这使得视频有点跳动,但比 IE11(我的首选客户端)更新。

本文不解释代码的工作原理,而是查看带有 cmets 的 GIST(不包括客户端代码,它是带有节点 http 服务器地址的标准 HTML5 视频标签)。 GIST 在这里:https://gist.github.com/deandob/9240090

我还没有找到这个用例的类似例子,所以我希望上面的解释和代码对其他人有帮助,特别是因为我从这个网站学到了很多东西并且仍然认为自己是初学者!

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

【讨论】:

以上是关于将实时 http 流式传输到 HTML5 视频客户端的最佳方法 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

仅使用视频标签实时流式传输到 HTML5(没有 webrtc)

将麦克风从客户端浏览器流式传输到远程服务器,并将音频实时传递到 ffmpeg 以与第二个视频源结合

在 Internet Explorer 9 中使用 HTML5 实时流式传输 h.264

将实时视频从我的 c# 应用程序流式传输到 ASP.NET 网页

如何将视频从 Play 2 框架流式传输到 HTML5 播放器

随着时间的推移,使用 java websockets 实时流式传输模拟视频变得无响应