之前测试使用过 nginx 的 HLS 加密功能, 会使用到一个叫做 nginx-rtmp-module 的插件, 但此插件很久不更新了, 网上搜索到一个中国制造的叫做 SRS 的流媒体服务器, 比较活跃, 而且据说这个流媒体服务器的性能和功能都强大不少, 但遗憾的是没有 HLS 加密功能. 问原作者为什么没有添加这个功能没有回复, 所以还是自己动手, 丰衣足食吧, 花了几个晚上的时间自己参考 nginx-rtmp 实现了一下. 代码放到了 github 上.
功能介绍
几个新添加的参数配置项
- hls_keys on;
- hls_fragments_per_key 4;
- hls_key_file [app]/[stream]-[seq].key;
- hls_key_file_path ./objs/nginx/html;
- hls_key_url http://localhost:8080/live/h265.m3u8;
分别代表如下含义:
hls_keys: 是否开启 hls 加密, 默认关闭.
hls_fragments_per_key: 每个 key 可以加密多少个 ts 片段, 默认值是 10.
hls_key_file: key 文件相对路径的生成模板, 包括一个 [app] 文件夹以及名字[stream]-[seq], 后缀为. key, 默认值为[app]/[stream]-[seq].key.
hls_key_file_path: 可以为 key 文件的生成指定本地目录, 默认为 hls_path(存放 ts 的目录).
hls_key_url: 可以为 key 指定一个 HTTP url.
实现过程中的几个关键点
需要实现的功能点包括一下几个方面:
从配置文件读取配置项
这个仿照 srs 的实现添加, 比较简单.
key 和 iv 的自动生成和保存
在这里每隔 hls_fragments_per_key 个 ts 会自动的生成随机的 16bytes 的 key 和 iv.key 会保存在 hls_key_file_path 路径中, iv 会保存在 m3u8 文件中.
在代码实现中, key 和 iv 在内存中保存了三份. SrsHlsMuxer 中保存了一份, 用于提供每次 new SrsHlsSegment 时需要的 key 和 iv. 因为每次刷新 m3u8(refresh_m3u8)时, 都要从头重新生成一次 m3u8 文件, 所以需要为每个 SrsHlsSegment 对象备份一份 iv. 最后是传递给 writer 的 key 和 iv, 用于 AES128 加密.
AES128 加密
材料都准备好了, 最后的关键问题就是加密. 在这里使用了 Openssl 的加密库, SRS 的实现是每次写一个 packet(188bytes), 而 AES128 需要加密的 raw 数据是 16 的倍数, 因此需要在原有 SrsFileWriter 的实现上加一层缓冲. 缓冲到 16 的倍数后(也就是 188*4), 加密一次数据, 然后写到文件中. 具体实现是下面的样子:
- srs_error_t SrsEncFileWriter::write(void* buf, size_t count, ssize_t* pnwrite)
- {
- srs_assert(count == SRS_TS_PACKET_SIZE);
- srs_error_t err = srs_success;
- if(buflength != HLS_AES_ENCRYPT_BLOCK_LENGTH)
- {
- memcpy(tmpbuf+buflength,(char*)buf,SRS_TS_PACKET_SIZE);
- buflength += SRS_TS_PACKET_SIZE;
- }
- if(buflength == HLS_AES_ENCRYPT_BLOCK_LENGTH)
- {
- unsigned char encryptedbuf[HLS_AES_ENCRYPT_BLOCK_LENGTH];
- memset(encryptedbuf,0,HLS_AES_ENCRYPT_BLOCK_LENGTH);
- AES_cbc_encrypt((unsigned char *)tmpbuf, (unsigned char *)encryptedbuf, HLS_AES_ENCRYPT_BLOCK_LENGTH, &key, iv, AES_ENCRYPT);
- buflength = 0;
- memset(tmpbuf,0,HLS_AES_ENCRYPT_BLOCK_LENGTH);
- return SrsFileWriter::write(encryptedbuf,HLS_AES_ENCRYPT_BLOCK_LENGTH,pnwrite);
- }
- else
- {
- return err;
- }
- };
需要注意的是每次 close TS 文件的 FD 时需要判断缓冲中有没有数据, 如果有的话需要添加填充数据(添加到正好为 16 的倍数即可), 然后加密, 写文件, 关闭文件:
- int addBytes = 16 - buflength % 16;
- memset(tmpbuf + buflength, addBytes, addBytes);
- unsigned char encryptedbuf[buflength+addBytes];
测试
能够为 HLS TS 切片正常加密和播放. 没有做充分的测试, 对 SRS 了解的还不够深入. 如果大家需要这个功能的话, 可以尝试着使用一下, 遇到问题联系我.
如何使用
首先, 在配置文件中添加以下配置项.
- http_server {
- enabled on;
- listen 8080;
- dir ./objs/nginx/html;
- }
- vhost __defaultVhost__ {
- hls {
- enabled on;
- hls_fragment 10;
- hls_window 600000;
- hls_path ./objs/nginx/html;
- hls_m3u8_file [app]/[stream].m3u8;
- hls_ts_file [app]/[stream]-[seq].ts;
- hls_keys on;
- hls_fragments_per_key 4;
- hls_key_file [app]/[stream]-[seq].key;
- hls_key_file_path ./objs/nginx/html;
- hls_key_url http://localhost:8080/live;
- }
然后到 trunk 目录下启动:
./objs/srs -c conf/hls.conf
推送 rtmp 流的命令:
ffmpeg -re -i /Users/zexu/Movies/test.mp4 -c copy -f flv rtmp://localhost:1935/live/h265
最后在播放器中播放 URL:
http://localhost:8080/live/h265.m3u8
关于配置项要注意的地方
关于 hls_key_file_path 和 hls_key_url, 要么都不配置(注释掉即可), 这样的话 m3u8,ts 和 key 文件都在一个目录下面. 要么就都配置, 需要自己保证两个地址能够对上. 否则会出现 key 找不到而导致播放失败的问题.
来源: https://www.cnblogs.com/harlanc/p/8711449.html