作者: 阿虎
获51CTO原创精华贴:https://ost.51cto.com/posts/13826
1. 什么是视频点播系统
视频点播是二十世纪90年代发展起来的,英文称为“Video on Demand”,简称“VOD”,它泛指一类能在用户需要时随时提供交互式视频服务的业务,即“想看什么就看什么,想什么时候看就什么时候看”。
为了实现这目的,视频点播放系统为用户提供包括音视频采集上传、音视频存储、自动化转码处理、加速播放、媒体资源管理等能力,如下图:
采集端
通过各种音视频智能设备,自动或手动获取业务场景中的视频数据,将视频流通过相关SDK或API推送给点播服务;
服务端
接收到视频流后,对其进行存储、转码切片、打水印、内容分发等加工处理,以提供快速、稳定、流畅、全新的视频体验
播放端
处理过的视频,经过内容分发网络(CDN)进行分发,解码后最终在用户的终端设备上播放。
2. 为什么要自建点播能力
-
自研视频处理能力,是一个强有力的竞争优势
近几年随着移动互联及5G网络普及,音视频技术在国内发展非常之快,各领域都有着广泛的运用场景,拥有自己的视频处理能力,是一个强有力的竞争优势。 -
视频处理技术越来越成熟和大众化
随着类似于ffmpeg等框架视频处理技术越来越成熟和大众化,低成本快速打造自己的私有化点播能力而不再依第三方云厂商成为可能。 -
为项目节省掉第三方云点播费用
依赖第三方厂商云点播服务和长期的按需付费是项目的一个痛点
随着业务项目越来越多,第三方点播费用成本越发明显,团队决定自研点播技术,在笔者主导下进行通用的点播能力封装打造,最终在众多项目得到落地复用,达到了不错的效果,相关功能如下:
3. HLS协议概述
在进行点播方案搭建前,要了解流行的HLS协议,常用的流媒体协议主要有 HTTP 渐进下载和基于 RTSP/RTP 的实时流媒体协议,这二种基本是完全不同的东西,目前比较方便又好用的是用 HTTP 渐进下载方法。在这个中 apple 公司的HLS(HTTP Live Streaming)是这个方面的代表,HTTP Live Streaming的工作原理是将整个视频切割成一个个小的可以通过 HTTP 下载的媒体文件(TS),然后提供一个配套的媒体列表文件(M3U8)给客户端,让客户端顺序地拉取这些媒体文件播放, 来实现看上去是在播放一条流的效果。HLS 目前广泛地应用于点播和直播领域
什么是HLS https://ottverse.com/hls-http-live-streaming-how-does-it-work/
HLS优势
-
客户端支持简单,只需要支持 HTTP 请求即可,HTTP 协议无状态,只需要按顺序下载媒体片段即可。
-
使用 HTTP 协议网络兼容性好, HTTP 数据包也可以方便地通过防火墙或者代理服务器,CDN 支持良好。
-
终端支持性好,Apple 的全系列产品支持,现在Android 也加入了对 HLS 的支持。
-
自带多码率自适应,Apple 在提出 HLS 时,就已经考虑了码流自适应的问题。
-
由于是切割成一个个小片段,播放起来更加流畅,并且较好的支持视频快进和回退
4. 音视频开发利器ffmpeg
HLS协议做视频点播有那么多优势,但如何来实现?FFmpeg 是一个强大的开源音视频处理工具,它为开发者提供了丰富的音视频处理的调用接口,我们可以使用 FFmpeg 来进行多种格式音频和视频的录制、转换、视频流内容处理功能,笔者也正是得益于ffmpeg的音视频处理能力才能为项目打造自己的点播能力。
我在进行自研点播能力设计前,对传说已久的ffmpeg工具以下几方面进了详细预研和打通
4.1 FFmpeg的安装
FFmpeg是跨平台工具,linux下可以通过如下命令进行安装:
sudo apt-get install ffmpeg
安装后,FFmpeg提供了三个主要的命令行应用程序,在 bin 目录中:
├── bin
│ ├── ffmpeg # 用于音视频编解码
│ ├── ffplay # 用于播放
│ ├── ffprobe # 用于分析视频文件
4.2 FFmpeg转码切片
HLS 是当下直播回放和部分实时直播场景最常使用的协议,它对应的媒体格式是 M3U8 + TS,将源视频转M3U8和TS文件的这个过程其实就是实现点播的转码和切片,如下调用命令:
ffmpeg -y -i field.mp4 -force_key_frames "expr:gte(t,n_forced*5)" -hls_time 5 -vf format=pix_fmts=yuv420p,scale=1280:720 -r 25 -vcodec h264 -acodec aac ty.m3u8
参数说明:
参数 | 说明 |
---|---|
-y | 输出覆盖目标文件 |
-i field.mp4 | 输入视频 |
-force_key_frames "expr:gte(t,n_forced*5)" | 每5秒设置一次关键帧 |
-hls_time 5 | 切片时间间隔 |
-vf format=pix_fmts=yuv420p,scale=1280:720 | 设定图像过滤链像素格式及大小 |
-r 25 | 设置帧率25 |
-vcodec h264 | 设置视频编码h264 |
-acodec aac | 设置音频编码acc |
执行效果:
4.3 获取转码后的视频时长及大小
在具体业务场景下,经常需要显示视频的时间或大小,这个时候就需要点播服务在转码切片过程中,同时也需要拿到视频时及大小等视频属性,通过如下命令获取:
ffprobe -loglevel warning -show_format -of json ty.m3u8
4.4 为视频自动截预览图
在点播系统中,对所有播放的视频资源至少都需要一张预览图的,所以在转码完毕后,直接对m3u8文件进行抽帧截图,命令如下:
ffmpeg -i ty.m3u8 -y -f image2 -ss 1 -vframes 1 ty.png
参数说明:
参数 | 说明 |
---|---|
-i ty.m3u8 | 输入视频 |
-y | 输出覆盖目标文件 |
-f | 输出格式为图片 |
-ss 1 | 从视频第1秒处开始截图 |
-vframes 1 | 只截取一帧 |
ty.png | 输出的文件名 |
5. 点播服务整体架构设计
对自研点播服务中需要的音视频流处理技术打通后,就可以开始整个点播服务的设计了,笔者主要从以下几方面进行思考和设计:
- 业务接入点播简便快捷
- 转码切片任务全生命周期管理
- 轻量级,支持快速部署,普通服务器即可稳定运行
- 支持集群部署,节点扩容,在高并发下支持转码切片节点无缝扩容
- 异常中断恢复,当任务节点异常中断后,可以自行恢复及重试
6. 点播任务状态图设计
- CREATE: 任务刚刚创建,DB记录数据,什么还没开始做
- WAITING: 任务被推送至消息队列,等候处理
- DOING:任务消息被具体转码节点消费,已开启转码线程
- SUCCESS:转码成功
- FAILED:转码失败(达到多次重试限制或超时)
- RETRY:单次处理失败,还未达到失败重试上限和超时,可以再次进入WAITING状态
7. 视频转码java-sdk
为了简化使用,JAVA-SDK内置了SpringBoot自动装配机制,开关打开就支自动装箱IOC容器
当然为了非SpringBoot项目同时也是支持手工实例化方式。
-
添加依赖
<dependency> <groupId>com.talkweb.ssop</groupId> <artifactId>ssop-sdk-vod</artifactId> <version>1.0.1-SNAPSHOT</version> </dependency>
-
参数配置
ssop: vod: # 开关 enable: true # 应用id,业务自己和SSO集成的时候对应的clientId,需要先提供给转码相关人员进行应用转码接入配置 appClientId: 9cd96cc9-231a-4362-b654-0283e02dadba
-
发起转码切片任务和查询转码结果
发起转码切片任务前,需要通过存储java-sdk或js-sdk先上传视频文件
@Api(value = "视频转码demo", tags = "视频转码demo")
@RestController
@RequestMapping("/{version}/demo")
public class VideoDemoController {
@Resource
VodClient vodClient;
/**
* 视频转码
* @param fileId
* @return
*/
@ApiVersion(1)
@ApiOperation(value = "发起视频转码", notes = "发起视频转码")
@RequestMapping("/encode")
public Result<ConvertTaskDO<VodConvertResultDO>> encode(@RequestParam("fileId") String fileId) {
//发起视频转码,可以使用fileId和taskId查询转码状态
ConvertTaskDO<VodConvertResultDO> convertTaskDO = vodClient.encode(fileId);
return Result.success(convertTaskDO);
}
/**
* 查询视频转码结果
* @param fileId
* @return
*/
@ApiVersion(1)
@ApiOperation(value = "查询最新的视频转码任务信息", notes = "查询最新的视频转码任务信息")
@RequestMapping("/getVideoByFileId")
public Result<ConvertTaskDO<VodConvertResultDO>> getVideoByFileId(@RequestParam("fileId") String fileId) {
//同一个fileId可以转码多次,所以可能会有多个转码结果
ConvertTaskDO<VodConvertResultDO> convertTaskDO = vodClient.getVideoByFileId(fileId);
return Result.success(convertTaskDO);
}
}
8. 在线播放示例
将查询到的转码切片结果m3u8地址,放到前端的播放器中效果如下: