视频点播(VOD)是根据用户的要求进行内容播放,也就是我们平时使用的“优爱腾”等视频网站。

之前研究直播的时候研究过好多种协议,但是在点播中,常用的网络传输协议一般是HLS和DASH(B站的方案),下面我们将基于HLS协议来探究下视频点播的奥秘。(纯小白看起来可能比较吃力)。

HLS

HLS(Http Live Streaming)是苹果公司推出的用于实时流传输的协议,HLS基于HTTP协议实现,传输内容包括两部分,一是M3U8描述文件,二是TS媒体文件。苹果自家的浏览器都先天支持HLS,其他平台的可以通过hls.js来引入HLS解码的能力。

HLS的优势就是:可以自适应调整播放码流,即网络畅通时选择高码流,网络繁忙时选择低码流,二者可以随意自行切换,以保证视频流的流畅度。

img

通过上面的图片可以很容易的看出HLS的结构,从左到右依次是一级索引、二级索引、切片文件。真正起作用的其实是中间的二级索引,一级索引用于收录各种码率的二级索引地址,根据网络环境自动选择合适的码率(如下图),二级索引里面是存放了视频切片的地址。

(图片来源:https://www.wowza.com/blog/hls-streaming-protocol)

一级索引长下面这样

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2149280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=1280x720,NAME="720"
720p.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x480,NAME="480"
480p.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6221600,CODECS="mp4a.40.2,avc1.640028",RESOLUTION=1920x1080,NAME="1080"
1080p.m3u8

二级索引长下面这样

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:11.200000,
360p0.ts
#EXTINF:9.600000,
360p1.ts
#EXTINF:9.600000,
360p2.ts
#EXTINF:9.600000,
360p3.ts
#EXTINF:11.200000,

.....

360p24.ts
#EXT-X-ENDLIST

以mp4为例,将视频转码为HLS(m3u8),可以使用ffmpeg进行转码

$ ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict experimental -hls_time 10 -hls_list_size 0 -f hls output.m3u8

命令执行完成之后会生成索引文件及切片文件

image-20231015162016227

如果想要批量导出不同分辨率的视频可以使用

ffmpeg -i input.mp4\
  -vf scale=w=640:h=360:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 10 -hls_playlist_type vod  -b:v 800k -maxrate 856k -bufsize 1200k -b:a 96k beach/360p.m3u8 \
  -vf scale=w=842:h=480:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 10 -hls_playlist_type vod -b:v 1400k -maxrate 1498k -bufsize 2100k -b:a 128k beach/480p.m3u8 \
  -vf scale=w=1280:h=720:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 10 -hls_playlist_type vod -b:v 2800k -maxrate 2996k -bufsize 4200k -b:a 128k beach/720p.m3u8 \
  -vf scale=w=1920:h=1080:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 10 -hls_playlist_type vod -b:v 5000k -maxrate 5350k -bufsize 7500k -b:a 192k beach/1080p.m3u8

但是批量导出之后并不会自动生成一级索引,需要我们使用脚本的方式自己生成(或者是我没有找到更优雅的方式)。

然后可以在hls.js官网提供的demo进行测试播放,可以看到网络面板中切换网络时(从慢速3G切到了普通模式)自动加载了适合当前网速的视频切片。

image-20231014164922393

补充

MSE(Media Source Extensions)提供了实现无插件且基于 Web 的流媒体的功能。使用 MSE,媒体串流能够通过 JavaScript 创建,并且能通过使用 <audio><video> 元素进行播放。hls.js 就是基于MSE实现的。

image-20231015121131380

MSE的流程如上图所示,先通过请求获取音视频的二进制文件,经过MediaSource处理之后交给多媒体元素播放,也就是我们在网络面板看到的ts切片是通过XHR获取的而不是直接请求的资源。

点播架构

image-20231016194837403

如上图所示,我简单的画了一下视频点播最基础的业务流程。

  1. 视频发布者将视频源文件上传到服务器;
  2. 服务器接收到视频开始转码,因为是点播业务,不同于直播的实时性,我们有足够的时间来进行转码、加水印等视频处理操作;
  3. 通过ffmpeg对视频进行转码,将转码后的切片及索引存放到服务器或者CDN;
  4. 用户通过app或者浏览器进行拉流播放。

拓展延伸

如果点播网站有会员机制,那么就需要进行加密,否则会被白嫖,造成损失;另外如果是弹幕网站,那么多半是会有防挡弹幕的机制,这些防挡弹幕的原理就是使用mask遮罩,遮罩的来源就是服务端通过AI将视频中的人像抠出来(有部分网站是在前端实现的);……

image-20231016195531363

此时我们的架构可以如此设计,接下来我们来看下刚才提到的两个功能如何实现。

加密

加密的方式有两种:

  1. 防盗链:为了保护自己的内容资源不被非授权的网站进一步使用、传播,而采取的一种技术手段,可以防止用户非法下载视频文件进行传播;
  2. DRM:(Digital Media Right)是数字版权管理的英文缩写,是一种数字内容的防盗技术,对视频文件进行加密,用户若是没有key进行解密则无法播放。

稳健的加密系统一般是两种结合使用,用来防止用户下载文件进行复原,但是也是防君子不防小人,通过抓包工具可以保存所有的请求数据。

防盗链一般是CDN提供的能力,通过Referer、Cookie、Token等手段来防止盗取文件,实现相对简单这里就不再赘述。

这里详细说一下内容加密,HLS天生支持ASE-128加密,加密后的m3u8文件格式如下

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:17
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="http://127.0.0.1:8080/file.key",IV=0xba36e87fa9c30b90d338fb9f72e3c366
#EXTINF:15.966667,
beyond0.ts
#EXTINF:8.333333,
beyond1.ts
......

比起原来的索引文件,多了#EXT-X-KEY字段,这个字段有三个内容:加密算法、密钥地址、IV(起始偏移量)

要加密视频文件,我们只需要在转码的时候提供加密描述文件即可,准备一个描述文件,格式如下

key URI
key file path
IV (optional)

第一行是要写入m3u8文件的密钥地址,第二行是进行加密的密钥路径(播放器使用第一行获取密钥,ffmpeg使用第二行获取密钥),第三行是可选的起始偏移量。

我们将生成info文件的操作总结为一个shell脚本:

#!/bin/sh
BASE_URL=${1:-'.'}
openssl rand 16 > file.key
echo $BASE_URL/file.key > file.keyinfo
echo file.key >> file.keyinfo
echo $(openssl rand -hex 16) >> file.keyinfo

每次转码前执行以下这个脚本即可

$ ./genEncry.sh https://example.com

如果没有权限,执行下chmod +x ./genEncry.sh赋一下权限。

然后通过ffmpeg转码的时候声明一下加密描述文件即可

$ ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict experimental -hls_key_info_file file.keyinfo -hls_time 10 -hls_list_size 0 -f hls ./hls/output.m3u8

防挡

近年来有些网站会将弹幕防挡的计算放在浏览器,这样可以节省服务器资源,但有些性能差的机器会吃不消,一般来说点播的实时性不强,弹幕可以在服务端生成(根据策略,多少播放量以上给生成抠图)。

通过MediaPipe的image_segmentation模型对视频帧进行抠图并保存即可,播放器通过拉取蒙版文件进行设置即可实现防挡弹幕,python示例

具体实现这里不过多赘述(没研究明白)。


前端小白