RTMP、HLS与HTTP-FLV

最近,有个项目需要使用OpenCV输出的图片推流到网页。这个东西相当的麻烦,主要是我以前从来没做过相关的东西。

这种需求相关的协议有三种:

协议 优点 缺点
RTSP 超低时延,可以达到毫秒级。 技术实现复杂。
RTMP 低时延,秒级;适应性好。 需要Flash支持,目前不流行。
HLS 动态切换码流;苹果安卓都可以用。 高时延,不适合做视频直播,一般用作点播或者音频广播。

首先排除RTSP,这东西几乎没啥可用文档。

  • RTMP


RTMP应用层协议,全称Real Time Message ProtocolAdobe开发的协议,部署起来很简单,但是已经停止开发了。

协议细节请点击这里查看。

不过除非你要基于TCP/IP写一个RMTP应用,一般是不用太深入了解,只需要知道它能分组传输消息就行。

RTMP是基于FLV文件格式的协议,这种文件格式专门为流媒体设计,可以用一种持续不断“流”的方式进行传输。播放器拿到这种格式的分包后,会根据其中信息进行重排序,进而得到一个完整的媒体文件。

  • HLS


不同于RTMPHLS同是应用层协议,但是其是基于HTTP协议传输的。

这个协议最大的特点就是把一个完整的媒体文件切分成许多短媒体,然后在一个m3u8文件里控制这些短媒体文件的读取。

本质上是在传输这些切分好的文件。

下面是CCTV1频道的语音主m3u8文件,它指出了各种带宽应使用的源。

依据奈氏准则,理想低通信道最高码元传输速率 = 2W Baud

1
2
3
4
5
6
7
8
9
10
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1800000,RESOLUTION=1280x720
/cctvwbcd/cdrmcctv1_1/index.m3u8?BR=td
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1350000,RESOLUTION=1024x576
/cctvwbcd/cdrmcctv1_1/index.m3u8?BR=ud
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=900000,RESOLUTION=854x480
/cctvwbcd/cdrmcctv1_1/index.m3u8?BR=hd
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=600000,RESOLUTION=640x360
/cctvwbcd/cdrmcctv1_1/index.m3u8?BR=md

下面则是从主m3u8找到的对应的子m3u8,它控制着每个短流媒体的读取。

在直播场景下,子m3u8不能有#EXT-X-ENDLIST标志,否则浏览器会停止请求子m3u8,进而导致播放列表无法更新,直播断流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:1684822835
#EXTINF:12.000000,
#EXT-X-PROGRAM-DATE-TIME:2023-05-29T16:33:34+0800
cdrmcctv1_1_1800-1684822835.ts
#EXTINF:12.000000,
#EXT-X-PROGRAM-DATE-TIME:2023-05-29T16:33:46+0800
cdrmcctv1_1_1800-1684822836.ts
#EXTINF:12.000000,
#EXT-X-PROGRAM-DATE-TIME:2023-05-29T16:33:58+0800
cdrmcctv1_1_1800-1684822837.ts
#EXTINF:12.000000,
#EXT-X-PROGRAM-DATE-TIME:2023-05-29T16:34:10+0800
cdrmcctv1_1_1800-1684822838.ts

实践证明,HLS大多数时间都是用作点播,直播支持较差。


上面两个协议使用起来体验都不太好。

前者要求浏览器拥有Flash支持,几乎只能使用本地的VLC媒体播放器;后者则是要求对媒体进行切分,时间粒度较大,延时较高,而且直播支持不太完善。

所以,对于当下的直播,大多数使用的是HTTP-FLV

  • HTTP-FLV


这种方式结合了RTMPHLS的优点,使用HTTP来分发FLV流。

在浏览器的网络监视器中,可以看到一个持续不断的文件请求,类型是x-flv

实际上,我们也可以用HTTPS来传输。

  • 服务器配置


Nginx有专门的包对RTMPHTTP-FLV做了支持(HLS一般访问文件即可),所以使用起来很简单。

首先需要从GitHub上拉取nginx-http-flv-module。这个模块已经支持了RTMP,所以不用另外加上别的模块。

解压后执行下面的命令进行编译安装:

1
2
3
./configure --add-module=/tmp/nginx-http-flv-module --with-http_ssl_module
make
make install

安完后默认在/usr/local/nginx下,可执行文件在上述路径的sbin下。

我的网站有SSL证书,所以配置了HTTPS。参考配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
worker_processes  1;

events {
worker_connections 1024;
}

# 添加RTMP服务
rtmp {
server {
listen 1935;
application live {
live on;
}
}
}

# 添加http-flv服务
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;

server {
listen 443;
server_name blueberrycat.site;

ssl on;
#ssl证书的pem文件路径
ssl_certificate /root/blueberrycat.site_nginx/blueberrycat.site_bundle.pem;
#ssl证书的key文件路径
ssl_certificate_key /root/blueberrycat.site_nginx/blueberrycat.site.key;

location / {
root html;
index index.html index.htm;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

location /live {
flv_live on;
chunked_transfer_encoding on;
# 添加一些控制访问的头
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'X-Requested-With';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
add_header 'Cache-Control' 'no-cache';
}
}

server {
listen 80;
server_name blueberrycat.site;
# 将请求转成https
rewrite ^(.*)$ https://$host$1 permanent;
}
}
  • 推流


推流地址可以是

1
https://blueberrycat.site/live?port=1935&app=live&stream={ 自定义的推流名 }

也可以是

1
rtmp://blueberrycat.site/live/{ 自定义的推流名 }

取决于你想用哪种协议。

回到项目,PythonOpenCV推流代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import cv2
import subprocess as sp

cap = cv2.VideoCapture(0)

fps = cap.get(cv2.CAP_PROP_FPS)

size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
sizeStr = str(size[0]) + 'x' + str(size[1])

command = ['ffmpeg',
'-y', # 覆盖确认
'-f', 'rawvideo', # 指定输入格式
'-pix_fmt', 'bgr24', # 格式
'-s', sizeStr, # 大小
'-r', str(fps), # FPS
'-i', '-', # 输入-管道
'-c:v', 'libx264', # 264编码
'-pix_fmt', 'yuv420p', # 420p
'-preset', 'ultrafast', # 高速处理
'-f', 'flv', # flv输出
'-b', '1000000', # 码率
'rtmp://blueberrycat.site/live/123'] # 推流地址

pipe = sp.Popen(command, stdin=sp.PIPE, shell=False, bufsize=10**8) # 建立管道,这步很重要

while cap.isOpened():
ret, frame = cap.read()
if ret == True:
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break

pipe.stdin.write(frame.tobytes()) # 推流

为了测试这些协议,我在旧网站上做了大量的实验,结果直接把主机弄炸了,旧网站直接下线。

默哀。

别在 Ubuntu 上用 yumg++

  • 网页端


建议使用hlv.js,Bilibili开发(难以置信)。

官方说明

An HTML5 Flash Video (FLV) Player written in pure JavaScript without Flash. LONG LIVE FLV!

😂

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script src="flv.min.js"></script>
<video id="videoElement"></video>
<script>
if (flvjs.isSupported()) {
var videoElement = document.getElementById('videoElement');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: '{ 这里换成自己的推流 }'
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
</script>

最终效果是这样的

直播效果图