问题背景
动态视频流切换是指在视频播放过程中,将输入动态地从一个视频流切换到另一个视频流。具体场景包括:
- 切换播放画质
- 插播广告(有多个广告打包插播)
- 电视剧连续播放
- 多个点播节目拼接为虚拟直播频道
- 用户切换播放节目
动态切换播放视频流,需要做到无缓冲、不闪黑屏,声音连续。
方案设计
因为存在插播,直观上需要通过树形结构来描述视频流的关系,但是这样做的复杂度很高,需要寻找简单的模型。
所以我们将流切换任务用线性链表结构来表示,对于插播,需要有两个切换任务:进入插播和退出插播。
流切换任务包含“切换目标流的URL”和“切换类型”。切换类型有下列5种:
- 平滑切换(时间点接续,用户看到的内容连续)
- 开始插播(可以递归插播)
- 退出插播(退出一层插播,对应任务没有URL)
- 直接切换(前后流没有关联)
- 追加切换(在末尾接续播放)
使用线性链表结构是方案的关键,有了链表结构,我们可以很容易的实现任务的取消。要取消中间一部分任务,简单的列表删除就可以了。
流切换实现在视频解封装(demux)阶段,用一个MultipleDemuxer管理多个实际的demuxer。
增加一个切换任务,就创建一个新的具体的demuxer(退出插播除外),新的任务在切换前需要准备好(demuxer完成初始化,如mp4流已经获取到头部索引并完成解析),准备过程是异步的。提前准备可以消除切换引起的播放缓冲。
准备完成后,切换任务被顺序处理。根据不同的切换类型,切换处理是不一样的,下面具体分析。
1、平滑切换
平滑切换中,新老视频流需要满足时间戳一致,即同一个时间点对应在音视频内容是一样的(分辨率可以不同,广义分辨率包括视频宽高、颜色深度;音频采样率、声道数,采样深度);不需要编码同步帧位置一致。
根据当前播放流的播放位置,在新的流中寻找该位置的下一个同步帧(一般只需要处理视频,音频每一个帧都是同步帧),以同步帧的时间点(音视频取小者)为切换时间点,设置老流的截止位置。
当前流处理到截止位置时,执行切换,老的流被关闭丢弃。
2、开始插播
根据当前播放流的播放位置,计算下一个同步帧时间点,设置老流的截止位置。
当前流处理到截止位置时,执行切换,老的流不关闭,在插播栈中保存(push);退出插播时,从插播栈取出(pop)恢复处理。
3、退出插播
当前流处理结束后,从插播栈取出插播前的流,恢复处理。
4、直接切换
直接执行切换,老的流被关闭丢弃,新的流从头开始处理。
5、追加切换
当前流处理结束后,执行切换,老的流被丢弃,新的流从头开始处理。
-------------------------------------------------------------------------------------
流切换还需要decode阶段的配合处理,decode阶段需要根据demux阶段给出的切换提示,重新创建解码器。根据不同的切换类型,对老流的音视频数据也有不同的处理策略,比如decode前后有队列缓存,对于“直接切换”需要清空。