FFmpeg推流

0 背景

在编译FreeSwitch的时候,我们顺手编译了一下作为依赖的FFmpeg,除了提供libavformat和libswscale这两个库之外,FFmpeg还有其他很多有趣的功能,这次我们来玩一下FFmpeg的推流功能,主要是RTMP和RTP这两种协议。

1 编译Nginx

在常规情况下需要一个web服务器作为RTMP服务器,设备A将数据流推送到RTMP服务器上,设备B再从RTMP服务器上拉取数据流播放。

一般来说可以使用Nginx作为RTMP服务器,由于需要额外的RTMP模块,因此需要我们重新编译一份Nginx。

我现在有一台虚拟机,一台宿主机,计划在虚拟机上搭Nginx服务以及作为设备A进行推流,宿主机内使用VLC和Wireshark进行抓包。

1
2
3
4
5
6
7
8
9
# 下载源码
git clone https://github.com/nginx/nginx.git
git clone https://github.com/arut/nginx-rtmp-module.git

# 编译并安装
cd nginx
./auto/configure --with-http_ssl_module --add-module=../nginx-rtmp-module
make
make install 2>&1 | tee make.log

2 配置Nginx的RTMP服务器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
vim /usr/local/nginx/conf/nginx.conf
# 在尾部添加:
rtmp {
    server {
        listen 1935; # 默认的RTMP端口
        chunk_size 4096;

        application live {
            live on;
            record off; # 不保存录制
        }
    }
}

在添加了这么个RTMP服务配置之后就可以启动nginx了:

1
/usr/local/nginx/sbin/nginx

3 推流及测试

3.1 RTMP测试

使用这个命令来推送RTMP数据流:

1
2
#  服务器地址还是填虚拟机地址
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://your_server_ip/live/stream

如果直接执行上面这条指令的话会出现如下报错:

1
error while loading shared libraries: libavdevice.so.57: cannot open shared object file: No such file or directory

我们之前编译ffmpeg中的lib并没有被系统识别到,因此导致了问题。

之前编译freeswitch的时候是通过导入环境变量这种一次性手段解决的,为了一劳永逸解决这个问题,我们选择下面的方法:

这个方法只是在CentOS 7.9中实验过,别的系统自行测试

ffmpeg编译出来的so文件默认位于/usr/local/lib中,系统并不读取这个路径,所以我们需要在配置文件中手动添加一个:

1
2
3
4
5
6
7
vim /etc/ld.so.conf.d/usr.conf

# 在其中添加刚刚的路径:
/usr/local/lib

# 编辑完成后保存退出并更新动态库缓存
ldconfig

下来可以测试一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[root@localhost ~]# ldconfig -p| grep av
	libxcb-screensaver.so.0 (libc6,x86-64) => /lib64/libxcb-screensaver.so.0
	libavutil.so.55 (libc6,x86-64) => /usr/local/lib/libavutil.so.55
	libavutil.so (libc6,x86-64) => /usr/local/lib/libavutil.so
	libavresample.so.3 (libc6,x86-64) => /usr/local/lib/libavresample.so.3
	libavresample.so (libc6,x86-64) => /usr/local/lib/libavresample.so
	libavformat.so.57 (libc6,x86-64) => /usr/local/lib/libavformat.so.57
	libavformat.so (libc6,x86-64) => /usr/local/lib/libavformat.so
	libavfilter.so.6 (libc6,x86-64) => /usr/local/lib/libavfilter.so.6
	libavfilter.so (libc6,x86-64) => /usr/local/lib/libavfilter.so
	libavdevice.so.57 (libc6,x86-64) => /usr/local/lib/libavdevice.so.57
	libavdevice.so (libc6,x86-64) => /usr/local/lib/libavdevice.so
	libavcodec.so.57 (libc6,x86-64) => /usr/local/lib/libavcodec.so.57
	libavcodec.so (libc6,x86-64) => /usr/local/lib/libavcodec.so
	libavahi-common.so.3 (libc6,x86-64) => /lib64/libavahi-common.so.3
	libavahi-client.so.3 (libc6,x86-64) => /lib64/libavahi-client.so.3

可以看到,ffmpeg编译出来的.so文件都已经被加载,此时就可以重新测试推流了。

可以使用vlc拉流查看结果。

3.2 RTP测试

在设计的时候。rtp协议规定每个rtp数据流只能推送音频或视频数据,所以并不能像rtmp一样一个地址包圆。

同时rtp规定偶数端口用于rtp数据流的传输,奇数端口用来传输对应的rtcp数据,在初期测试的时候我没有搞明白这个原因然后被vlc疯狂gank不明所以。

所以我们可使用这个命令分别推送视频流和音频流:

1
2
3
4
5
6
7
8
# 这里的地址填的是目标设备地址,也就是播放这个视频的设备地址
# 感觉很多教程并没有说明白这一点,然后就被坑了
# 第二行表示推送视频流,禁用音频流
# 第三行表示推送音频流,禁用视频流
# 二三两行分别将两条数据流推送到同一主机的两个端口上,
ffmpeg -re -i x.mp4 \
-vcodec copy -an -f rtp rtp://192.168.107.242:1234 \
-acodec copy -vn -f rtp rtp://192.168.107.242:1236

这时终端会同时产生一个sdp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
t=0 0
a=tool:libavformat 57.83.100
m=video 1234 RTP/AVP 96
c=IN IP4 192.168.107.242
b=AS:567
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAMqzKcB4AiflhAAADA+kAAOpgDxgwp4A=,aOvvLA==; profile-level-id=640032
m=audio 1236 RTP/AVP 97
c=IN IP4 192.168.107.242
b=AS:128
a=rtpmap:97 MPEG4-GENERIC/44100/2
a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1210

保存后用vlc打开就能拉取rtp数据流进行播放了。

image-20230824144233195

3B1B万岁!