当前位置:首页 » 《随便一记》 » 正文

RTSP前端实时流

23 人参与  2024年11月27日 12:01  分类 : 《随便一记》  评论

点击全文阅读


因项目需求探索前端实时流,本文介绍了 RTSP 前端不能直接播放,需中间层转换协议,如 RTSP 转 RTMP、HLS、HTTP-FLV,分别阐述其特点、配置和播放方式,还提及关键帧、延迟与卡顿的关系,以及直播平台使用云服务商 CDN 的流程。

概览

视频有一个流的概念,所以称流媒体。实时视频的流很好理解,因为视频是实时的,需要有一个地方不停地输出视频出来,所以整个视频可以用流来称呼。那么视频可否直接输出到前端页面上呢?可惜,如果可以的话,就没有我这篇文章了。现在摄像头的实时视频流普遍采用的是 RTSP 协议,而前端并不能直接播放 RTSP 的视频流。

RTSP(Real-Time Stream Protocol),是 TCP/UDP 协议体系中的一个应用层协议,跟 HTTP 处在同一层。RTSP 在体系结构上位于 RTP 和RTCP 之上,它使用 TCP 或者 RTP 完成数据传输。RTSP 实时效果非常好,适合视频聊天、视频监控等方向。


那么我们就需要一层中间层,来将 RTSP 流转成前端可以支持的协议,这也引申出了目前实时流技术的几种方向:

RTSP -> RTMPRTSP -> HLSRTSP -> RTMP -> HTTP-FLV

 

RTMP

RTMP(Real Time Messaging Protocol)是属于 Adobe 的一套视频协议,这套方案需要专门的 RTMP 流媒体,并且如果想要在浏览器上播放,无法使用 HTML5 的 video 标签,只能使用 Flash 播放器。(通过使用 video.js@5.x 以下的版本可以做到用 video 标签进行播放,但仍然需要加载 Flash)。它的实时性在几种方案中是最好的,但是由于只能使用 Flash 的方案,所以在移动端就直接 GG 了,在 PC 端也是明日黄花。
由于下面的两种方法也需要用到 RTMP,所以这里就展示一下 RTSP 流如何转换成 RTMP ,我们使用 ffmpeg+Nginx+nginx-rtmp-module 来做这件事:

 

# 在 http 同一层配置 rtmp 协议的相关字段rtmp {    server {      # 端口        listen 1935;    # 路径        application test {    # 开启实时流模式            live on;            record off;        }    }}
# bash 上执行 ffmpeg 把 rtsp 转成 rtmp,并推到 1935 这个端口上ffmpeg -i "rtsp://xxx.xxx.xxx:xxx/1" -vcodec copy -acodec copy -f flv "rtmp://127.0.0.1:1935/live/"

这样我们就得到了一个 RTMP 的流,我们可以直接用 VLC 或者 IINA 来播放这个流。

HLS 

HLS(HTTP Live Streaming)是苹果公司提出的基于 HTTP 协议的的流媒体网络传输协议,它的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。HLS 具有跨平台性,支持 iOS/Android/浏览器,通用性强。但是它的实时性差:苹果官方建议是请求到3个片之后才开始播放。所以一般很少用 HLS 做为互联网直播的传输协议。假设列表里面的包含5个 ts 文件,每个 TS 文件包含5秒的视频内容,那么整体的延迟就是25秒。苹果官方推荐的小文件时长是 10s,所以这样就会有30s(n x 10)的延迟。
下面是 HLS 实时流的整个链路:

从图中可以看出来我们需要一个服务端作为编码器和流分割器,接受流并不断输出成流片段(stream),然后前端再通过一个索引文件,去访问这些流片段。那么我们同样可以使用 nginx+ffmpeg 来做这件事情。

# 在 rtmp 的 server 下开启 hls# 作为上图中的 Server,负责流的处理application hls{live on;hls on; hls_path xxx/; #保存 hls 文件的文件夹hls_fragment 10s;}
# 在 http 的 server 中添加 HLS 的配置:# 作为上图中的 Distribution,负责分片文件和索引文件的输出location /hls {  # 提供 HLS 片段,声明类型  types {    application/vnd.apple.mpegurl m3u8;    video/mp2t ts;  }  root /Users/mark/Desktop/hls; #访问切片文件保存的文件夹  # Cache-Controll no-cache;  expires -1;}

然后同样使用 ffmpeg 推流到 hls 路径上:

ffmpeg -i "rtsp://xxx.xxx.xxx:xxx/1" -vcodec copy -acodec copy -f flv rtmp://127.0.0.1:1935/hls

这个时候可以看到文件夹里已经有许多流文件存在,且不停地更新:

然后我们可以使用 video.js+video.js-contrib-hls 来播放这个视频:

<html><head><title>video</title><!-- 引入css --><link href="https://unpkg.com/video.js/dist/video-js.min.css" rel="stylesheet"></head><body><div class="videoBox">    <video id="video" class="video-js vjs-default-skin" controls>        <source src="http://localhost:8080/hls/test.m3u8" type="application/x-mpegURL">     </video></div></body></html><script src="https://unpkg.com/video.js/dist/video.min.js"></script><script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"></script><script>videojs.options.flash.swf = "./videojs/video-js.swf"videojs('video', {"autoplay":true}).play();</script>

在我的测试下,HLS 的延迟在10-20秒左右,我们可以通过调整切片的大小来减少延迟,但是由于架构的限制,延迟是一个不可忽视的问题。

HTTP-FLV

接下来就是重头戏 HTTP-FLV 了,它集合了 HLS 的通用性和 RTMP 的实时性,可以做到在浏览器上用 HTML5 的 video 标签,以较低的延时播放实时流。HTTP-FLV 依靠 MIME 的特性,根据协议中的 Content-Type 来选择相应的程序去处理相应的内容,使得流媒体可以通过 HTTP 传输。除此之外,它可以通过 HTTP 302 跳转灵活调度/负载均衡,支持使用 HTTPS 加密传输,也能够兼容支持 Android,iOS 等移动端。HTTP-FLV 本质上是将流转成 HTTP 协议下的 flv 文件,在 Nginx 上我们可以使用 nginx-http-flv-module 来将 RTMP 流转成 HTTP 流。

其实 flv 格式依然是 Adobe 家的格式,原生 Video 标签无法直接播放,但是好在我们有 bilibili 家的 flv.js,它可以将 FLV 文件流转码复用成 ISO BMFF(MP4 碎片)片段,然后通过 Media Source Extensions 将 MP4 片段喂进浏览器。

在支持浏览器的协议里,延迟排序是这样的:RTMP = HTTP-FLV = WebSocket-FLV < HLS
而性能排序是这样的:RTMP > HTTP-FLV = WebSocket-FLV > HLS

说了这么多,不如直接上手看看吧:

1.首先我们需要一个新的 nginx 插件:nginx-http-flv-module

2.在 nginx.conf 中进行一些新的配置:

# rtmp serverapplication myvideo {  live on;  gop_cache: on; #减少首屏等待时间}# http serverlocation /live {  flv_live on;}

3.依然用 ffmpeg 来推流,使用上面 RTMP 的命令 

4.前端 import flv.js,然后使用它来播放

// 前端使用 flv.js,开启实时模式,然后访问这个 nginx 地址下的路径即可import flvJs from 'flv.js';export function playVideo(elementId, src) {  const videoElement = document.getElementById(elementId);  const flvPlayer = flvJs.createPlayer({    isLive: true,    type: 'flv',    url: src,  });  flvPlayer.attachMediaElement(videoElement);  flvPlayer.load();}playVideo('#video', 'http://localhost:8080/live?port=1985&app=myvideo&stream=streamname')

可以看到 flv.js 使用了 video/x-flv 这个 MIME 返回数据。

 

 

如果对延迟有更高的要求,可以尝试下面的操作:

可以配置 flv.js 的 enableStashBuffer 字段,它是 flv.js 用于控制缓存 buffer 的开关,关闭了之后可以做到最小延迟,但由于没有缓存,可能会看到网络抖动带来的视频卡顿。可以尝试关闭 nginx 的 http 配置里的 gop_cache 。gop_cache 又称关键帧缓存,其意义是控制视频的关键帧之间的缓存是否开启。

这里引入了一个关键帧的概念:我们使用最广泛的 H.264 视频压缩格式,它采用了诸如帧内预测压缩/帧间预测压缩等压缩方案,最后得到了 BPI 三种帧:

I 帧:关键帧,采用帧内压缩技术。P 帧:向前参考帧,在压缩时,只参考前面已经处理的帧,表示的是当前帧画面与前一帧(前一帧可能是 I 帧也可能是 P 帧)的差别。采用帧间压缩技术。B 帧:双向参考帧,在压缩时,它即参考前面的帧,又参考它后面的帧。B 帧记录的是本帧与前后帧的差别。采用帧间压缩技术。

带有 I 帧、B 帧和 P 帧的典型视频序列。P 帧只需要参考前面的 I 帧或 P 帧,而 B 帧则需要同时参考前面和后面的 I 帧或 P 帧。由于 P/B 帧对于 I 帧都有直接或者间接的依赖关系,所以播放器要解码一个视频帧序列,并进行播放,必须首先解码出 I 帧。假设 GOP(就是视频流中两个I帧的时间距离) 是 10 秒,也就是每隔 10 秒才有关键帧,如果用户在第 5 秒时开始播放,就无法拿到当前的关键帧了。这个时候 gop_cache 就起作用了:**gop_cache 可以控制是否缓存最近的一个关键帧。**开启 gop_cache 可以让客户端开始播放时,立即收到一个关键帧,显示出画面,当然,由于增加了对上一个帧的缓存,所以延时自然就变大了。如果对延时有更高的要求,而对于首屏时间/播放流畅度的要求没那么高的话,那么可以尝试关闭 gop_cache,来达到低延时的效果。

思考

 

延迟与卡顿

实时视频的延时与卡顿是视频质量中最重要的两项指标。 然而,这两项指标从理论上来说,是一对矛盾的关系——需要更低的延时,则表明服务器端和播放端的缓冲区都必须更短,来自网络的异常抖动容易引起卡顿;业务可以接受较高的延时时,服务端和播放端都可以有较长的缓冲区,以应对来自网络的抖动,提供更流畅的体验。

直播厂商是怎么做的?

现在各个直播平台基本上都放弃了以上这些比较传统的方式,使用了云服务商提供的 CDN,但还是离不开前文所说的几种协议与方式。如下图是阿里云的直播服务图。可以看到其流程大概分为这几步:

视频流推送(主播端使用 RTMP 进行推流)SDK 推流到 CDN 节点(上传流)CDN 节点转到直播中心,直播中心具有强大的计算能力,可以提供额外服务诸如落存(录制/录制到云存储/点播),转码,审核,多种协议的输出等。直播中间分发到 CDN 节点用户播放(阿里云支持 RTMP、FLV 及 HLS 三种播流协议)

​​​​​​​

 


点击全文阅读


本文链接:http://zhangshiyu.com/post/192759.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1