草庐IT

html - 将 http 实时流式传输到 HTML5 视频客户端的最佳方法

coder 2023-04-23 原文

我真的很难理解使用 node.js 将 ffmpeg 的实时输出流式传输到 HTML5 客户端的最佳方式,因为有许多变量在起作用,而且我在这个领域没有很多经验,花了很多时间尝试不同的组合。

我的用例是:

1) IP 视频摄像机 RTSP H.264 流由 FFMPEG 拾取并使用 Node 中的以下 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 http 服务器来捕获 STDOUT 并根据客户端请求将其流式传输回客户端。当客户端第一次连接时,我会生成上面的 FFMPEG 命令行,然后将 STDOUT 流通过管道传输到 HTTP 响应。
liveFFMPEG.stdout.pipe(resp);

我还使用流事件将 FFMPEG 数据写入 HTTP 响应,但没有区别
xliveFFMPEG.stdout.on("data",function(data) {
        resp.write(data);
}

我使用以下 HTTP header (在流式传输预先录制的文件时也使用并工作)
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 Node 服务器时正确地看到 VLC 中的视频直播。

然而,尝试通过 Node HTTP 从 FFMPEG 实时流式传输似乎要困难得多,因为客户端将显示一帧然后停止。我怀疑问题在于我没有设置与 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 摄像机并将块发送到 Node ,并且 Node 数据事件也是不规则的。然而,字节流应该与保存到文件完全相同,并且 HTTP 应该能够满足延迟。

4) 从摄像头流式传输 FFMPEG 创建的 MP4 文件时,从 HTTP 客户端检查网络日志时,我看到有 3 个客户端请求:对视频的一般 GET 请求,HTTP 服务器返回大约 40Kb,然后是部分具有文件最后 10K 字节范围的内容请求,然后对中间位的最终请求未加载。也许 HTML5 客户端一旦收到第一个响应就会要求文件的最后一部分加载 MP4 MOOV 原子?如果是这种情况,它将不适用于流式传输,因为没有 MOOV 文件并且没有文件结尾。

5) 在尝试直播时检查网络日志时,我收到一个中止的初始请求,仅收到了大约 200 个字节,然后再次中止了 200 个字节的重新请求和只有 2K 长的第三个请求。我不明白为什么 HTML5 客户端会中止请求,因为字节流与从录制文件流式传输时可以成功使用的字节流完全相同。 Node 似乎也没有将 FFMPEG 流的其余部分发送到客户端,但我可以在 .on 事件例程中看到 FFMPEG 数据,因此它正在到达 FFMPEG Node HTTP 服务器。

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

7) 我是否在尝试处理 206 个部分内容请求时走错了路,这是否应该适用于正常的 200 个 HTTP 响应? HTTP 200 响应适用于 VLC,所以我怀疑 HTML5 视频客户端只能处理部分内容请求?

由于我仍在学习这些东西,因此很难解决这个问题的各个层面(FFMPEG、 Node 、流媒体、HTTP、HTML5 视频),因此将不胜感激。我花了几个小时在这个网站和网络上进行研究,我还没有遇到任何能够在 node 中进行实时流式传输的人,但我不能成为第一个,我认为这应该能够工作(不知何故!)。

最佳答案

EDIT 3: As of IOS 10, HLS will support fragmented mp4 files. The answer now, is to create fragmented mp4 assets, with a DASH and HLS manifest. > Pretend flash, iOS9 and below and IE 10 and below don't exist.



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

EDIT 2: As people in the comments are pointing out, things change. Almost all browsers will support AVC/AAC codecs. iOS still requires HLS. But via adaptors like hls.js you can play HLS in MSE. The new answer is HLS+hls.js if you need iOS. or just Fragmented MP4 (i.e. DASH) if you don't



视频,特别是实时视频非常困难的原因有很多。 (请注意,原始问题指定 HTML5 视频是一项要求,但提问者在评论中表示 Flash 是可能的。因此,这个问题立即具有误导性)

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

EDIT: since I wrote this answer Media Source Extensions have matured, and are now very close to becoming a viable option. They are supported on most major browsers. IOS continues to be a hold out.



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

现在让我们深入了解一下。

平台:
  • iOS
  • 电脑
  • 苹果机
  • 安卓

  • 编解码器:
  • vp8/9
  • h.264
  • 索拉 (vp3)

  • 浏览器中直播视频的常见交付方式:
  • DASH (HTTP)
  • HLS (HTTP)
  • 闪存 (RTMP)
  • 闪存 (HDS)

  • 浏览器中VOD的常见交付方式:
  • DASH(HTTP 流)
  • HLS(HTTP 流)
  • 闪存 (RTMP)
  • 闪存(HTTP 流)
  • MP4(HTTP 伪流)
  • 我不打算谈论 MKV 和 OOG,因为我不太了解它们。

  • html5 视频标签:
  • MP4
  • webm
  • 奥格


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

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

  • 火狐
  • DASH(通过 MSE 但没有 h.264)
  • h.264 仅通过 Flash!
  • VP9
  • MP4
  • OGG
  • 微信

  • 浏览器
  • 快闪
  • DASH(仅通过 MSE IE 11+)
  • h.264
  • MP4

  • Chrome
  • 快闪
  • DASH(通过 MSE)
  • h.264
  • VP9
  • MP4
  • webm
  • 奥格

  • 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 用户,并为其他所有人闪现。
    我个人最喜欢的是编码 HLS,然后使用 flash 为其他人播放 HLS。您可以通过 JW player 6 在 flash 中播放 HLS,(或者像我一样在 AS3 中将自己的 HLS 写入 FLV)

    很快,最常见的方法将是 iOS/Mac 上的 HLS 和其他地方通过 MSE 的 DASH(这是 Netflix 即将要做的)。但我们仍在等待每个人升级他们的浏览器。您可能还需要一个单独的 DASH/VP9 用于 Firefox(我知道 open264;它很糟糕。它不能在主要或高调中播放视频。所以它目前没用)。

    关于html - 将 http 实时流式传输到 HTML5 视频客户端的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21921790/

    有关html - 将 http 实时流式传输到 HTML5 视频客户端的最佳方法的更多相关文章

    1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    2. ruby - 如何模拟 Net::HTTP::Post? - 2

      是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

    3. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

      在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

    4. ruby-on-rails - 使用 Sublime Text 3 突出显示 HTML 背景语法中的 ERB? - 2

      所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择

    5. ruby-on-rails - Ruby url 到 html 链接转换 - 2

      我正在使用Rails构建一个简单的聊天应用程序。当用户输入url时,我希望将其输出为html链接(即“url”)。我想知道在Ruby中是否有任何库或众所周知的方法可以做到这一点。如果没有,我有一些不错的正则表达式示例代码可以使用... 最佳答案 查看auto_linkRails提供的辅助方法。这会将所有URL和电子邮件地址变成可点击的链接(htmlanchor标记)。这是文档中的代码示例。auto_link("Gotohttp://www.rubyonrails.organdsayhellotodavid@loudthinking.

    6. ruby - Net::HTTP 获取源代码和状态 - 2

      我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

    7. ruby-on-rails - capybara ::ElementNotFound:无法找到 xpath "/html" - 2

      我正在学习http://ruby.railstutorial.org/chapters/static-pages上的RubyonRails教程并遇到以下错误StaticPagesHomepageshouldhavethecontent'SampleApp'Failure/Error:page.shouldhave_content('SampleApp')Capybara::ElementNotFound:Unabletofindxpath"/html"#(eval):2:in`text'#./spec/requests/static_pages_spec.rb:7:in`(root)'

    8. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

      1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

    9. ruby - 如何使用 Ruby 将 CSV 文件读入 HTML 表格? - 2

      我正在尝试将一个简单的CSV文件读入HTML表格以在浏览器中显示,但我遇到了麻烦。这就是我正在尝试的:Controller:defshow@csv=CSV.open("file.csv",:headers=>true)end查看:输出:NameStartDateEndDateQuantityPostalCode基本上我只获取标题,而不会读取和呈现CSV正文。 最佳答案 这最终成为最终解决方案:Controller:defshow#OpenaCSVfile,andthenreaditintoaCSV::Tableobjectforda

    10. ruby-on-rails - Rails - 从命名路由中提取 HTTP 动词 - 2

      Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba

    随机推荐