在前面一篇文章中《流媒体协议之HLS》介绍了什么是流媒体,什么是HLS以及分析了m3u8文件格式内容的含义。在本篇文章中更多的是实际操作,搭建一个流媒体服务,使用ffmpeg切割大的视频文件,使用ffmpeg切割大的视频文件并加密,使用ffmpeg整合视频片断,以及在代码中如何实现根据m3u8下载并解码对应的媒体文件。演示的环境是Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-62-generic x86_64)、nginx-1.19.6、gcc version 9.3.0、GNU Make 4.2.1O、OpenSSL 1.1.1f
编译安装Nginx
这个时候选择编译安装的方式:
nginx的rtmp模块,下载地址是https://github.com/arut/nginx-rtmp-module
nginx_mod_h264_streaming的下载地址是:http://h264.code-shop.com/download/nginx_mod_h264_streaming-2.2.7.tar.gz
nginx源码包,下载地址是:http://nginx.org/download/nginx-1.19.6.tar.gz
先准备好Nginx源码包、rtmp模块和nginx_mod_h264_streaming模块,并解压:
1 | # 在root目录下准备nginx-1.19.6.tar.gz、nginx-rtmp-module.zip、nginx_mod_h264_streaming-2.2.7.tar.gz并解压 |
编译过程可能会遇到的问题:
问题1:ngx_http_streaming_module.c:158:8: error: ngx_http_request_t has no member named zero_in_uri
解决方案:注释掉nginx_mod_h264_streaming-2.2.7/src/ngx_http_streaming_module.c的158到161行即可
问题2:error: variable ‘stream_priority’ set but not used [-Werror=unused-but-set-variable]
解决方案:修改nginx-1.10.2/objs/Makefile文件第2行CFLAGS变量去掉“-Werror”字段
现在找一个mp4与flv文件分别放在/root/videos/mp4/
和/root/videos/flv/
下,则配置文件修改如下:
1 | ... |
然后输入http://xx.xx.xx.xx/test.mp4即可开始播放,就说明已经配置好了,现在你已经拥有了一个基本的视频点播站点,就像下面这样:
对于flv的视频,可以使用VLC来播放,这是下载地址https://get.videolan.org/vlc/3.0.11.1/macosx/vlc-3.0.11.1.dmg
apt安装ffmpeg
1 | apt install ffmpeg |
如果没有更换源的话会很慢,下面可以更新一下镜像源,再执行apt install ffmpeg
1、首先备份原来的源:
1 | cp /etc/apt/sources.list /etc/apt/sources.list.bak |
2、查看本Ubuntu的代号
1 | lsb_release -a |
对于我的Ubuntu20.04来说,代号就是focal
3、确认查看阿里云是否存在该源
http://mirrors.aliyun.com/ubuntu/dists/看来是存在focal的。
4、将下面的XXX全部替换为系统的代号,比如我的系统代号是focal
1 | deb http://mirrors.aliyun.com/ubuntu/ XXX main restricted universe multiverse |
那么替换完成后就是
1 | deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse |
5、更新缓存
1 | apt update |
ffmpeg安装成功后即可查看ffmpeg的版本:
1 | ffmpeg |
使用ffmpeg切割媒体文件
进入到test.mp4存在的目录,执行如下命令完成对test.mp4的切割:
1 | cd ~/videos/mp4 |
hls_time: 设置每片的长度,单位是秒,默认值为2秒。
hls_list_size: 设置播放列表保存的最多条目,设置为0会保存有所片信息,默认值为5。
hls_wrap: 设置多少片之后开始覆盖,如果设置为0则不会覆盖,默认值为0。这个选项能够避免在磁盘上存储过多的片,而且能够限制写入磁盘的最多的片的数量。
start_number: 设置播放列表中sequence number的值为number,默认值为0
hls_base_url: 参数用于为M3U8列表的文件路径设置前置基本路径参数,因为在FFmpeg中生成M3U8时写入的TS切片路径默认为M3U8生成的路径相同,但是实际上TS所存储的路径既可以为本地绝对路径,也可以为相对路径,还可以为网络路径,因此使用hls_base_url参数可以达到该效果
切割完成后,可以看到文件夹下的ts片断和对应的m3u8文件:
这些ts片断都是可以直接播放的,而且可以看到对应的m3u8文件如下,关于m3u8文件属性的内容在《流媒体协议之HLS》中已经介绍过了。
1 | #EXTM3U |
修改一下Nginx的配置文件
1 | server { |
这样通过VLC打开网络串流,输入m3u8的地址:http://172.16.26.2/media/test.m3u8即可播放对应的媒体资源:
如果加上hls_base_url参数生成的m3u8文件如下:
1 | ffmpeg -i test.mp4 -c:v libx264 -c:a copy -f hls -threads 8 -hls_time 30 -hls_list_size 0 -hls_base_url http://172.16.26.2/media/ test.m3u8 |
使用ffmpeg合并ts文件
使用ffmpeg也可以通过m3u8索引文件把所有的ts片段文件合并:
1 | ffmpeg -i ./test.m3u8 -acodec copy -vcodec copy output.mp4 |
如果是网络上的m3u8点播列表,也可以下载并合并到mp4中:
1 | ffmpeg -i "http://xxx.com/media/test.m3u8" "save_video.mp4" |
ffmpeg切割并加密媒体文件
将一个mp4视频文件切割为多个ts片段,并在切割过程中对每一个片段使用AES-128加密,最后生成一个m3u8的视频索引文件:
加密用的key,通过OpenSSL生成一个enc.key文件
1 | openssl rand 16 > enc.key |
另一个是 iv
1 | openssl rand -hex 16 |
这里生成的IV是ef157287b9fc922ed1cc101a09e742b3
新建一个文件 enc.keyinfo 内容格式如下:
1 | http://172.16.26.2/media/enc.key |
因为enc.key直接放在了/root/vides/mp4/
目录下,所以通过http://172.16.26.2/media/enc.key
这个地址完全可以访问到这个enc.key文件。
1 | ffmpeg -y -i test.mp4 -hls_time 30 -hls_key_info_file enc.keyinfo -hls_playlist_type vod -hls_segment_filename "file%d.ts" -hls_base_url http://172.16.26.2/media/ test.m3u8 |
上述命令中-hls_time 30
即每个片段30s,-hls_playlist_type vod
表示这是一个点播播放列表,hls_segment_filename "file%d.ts"
规定了片断的文件名。生成的m3u8文件如下:
1 | #EXTM3U |
这样通过加密生成的每个ts片断都需要解密才能播放。HTTP Live Streaming中内容加密有两种,一种是对TS切片文件直接加密;另一种是对H.264编码文件中类型为1和5的NAL单元进行加密,其它类型的NAL单元不加密。HLS中媒体分块如果是加密的,其加密密钥通过M3U8文件中的#EXT-X-KEY
来指定,密钥文件由客户端从服务器请求认证获得。一个播放列表可以有一个以上的#EXT-X-KEY
,同一个媒体段也可以有多个不同KEYFORMAT属性值的#EXT-X-KEY
,在本例中使用的是对每个TS片断进行加密。
在上面的示例m3u8文件中,#EXT-X-KEY
有一个属性URI,其实这个URI就是秘钥的地址,在实际音视频版权保护的案例中,TS切片文件的加解密是非常重要的一环,因为客户端只有在拿到了key文件之后才能对TS切片文件进行解密,所以在URI上面做文章就很关键,这里只是用了一个简单的HTTP URL表示了key文件的地址,实际场景中需要配合用户Token等一系列校验过程才能使客户端拿到真正的key,另外如果key文件本身也是加密的话还需要对Key文件本身进行解密,如果把解密的代码放到SO库里(也就是C/C++编写的库),那么要破译Key就更难了。所以为了防盗链还是有很多的方法流程的。
代码中解密TS文件
在很多播放器内就内置了解密m3u8文件的功能,但是必须是在本例中这样直接给出key的URL才可以。对于这样的直接给出Key的地址的情况,只需要根据对应的Key做解密操作就行了。上面的每一个TS文件未解密都不能播放,因此每个TS文件都需要进行解密。下面是我写的关于TS文件AES128加解密的代码:
先引入Java实现AES加密模块的依赖
1 | implementation group: 'org.bouncycastle', name: 'bcprov-jdk16', version: '1.46' |
AES128Utils.java
1 | public class AES128Utils { |
IOUtils.java
1 | public class IOUtils { |
M3u8Parser.java
1 | public class M3u8Parser { |
参考资料
1、Example Playlists for HTTP Live Streaming
- 本文作者: Tim
- 本文链接: https://zouchanglin.cn/901333507.html
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!