本章节主要以本地音视频播放为例,简要描述讲解一个基本的播放器中,音视频播放如何实现同步的。
通用媒体播放器框架
其中各个组件模块:
1、Media Demux: 进行媒体文件的解析,分别解析出音频流数据包和视频流数据包。主要使用?libavformat库中的函数。
2、Video Decoder:视频解码器,解码后的视频帧放入到帧队列,主要使用libavcodec库中的函数。
3、Audio Decoder:音频解码器,解码后的PCM数据放入缓存队列,主要使用libavcodec库中的函数。
4、Video Post-process:视频帧后处理,通常是进行色彩格式转换、大小缩放、特效等处理,主要使用libswscale库中的函数。
5、Audio Post-process:音频帧后处理,通常是重采样、均衡器、变声等处理,主要使用libswresample库中的函数。
6、Video Display:视频帧显示,正常播放时直接显示到界面控件。这个是跟平台相关的处理,通常使用 OpenGL进行视频帧渲染,包括一些特效处理。
7、Audio Render:音频设备输出,通常的音频设置输出每播放完一段会有相应的回调通知。这个是跟平台相关的处理,需要调用系统平台支持的一些API进行音频输出,如果要跨平台处理,可以考虑使用 OpenSL接口(前提是平台支持)
另外:
(1)音视频同步控制(AV Sync Control)通常都是在音视频数据解码后进行同步控制。
(2)很多时候,视频后处理(Video Post Process) 和 视频显示(Video Display) 模块可以集中在一个模块中,直接在GPU中进行处理。
音视频流播放时序
1、视频播放时,以30FPS帧率为例,一帧视频帧平均显示33ms,这个时间包括从码流解析、解码、后处理 到显示整个过程处理时间要 < 33ms。
2、音频播放时,通常音频设备每次送入一段PCM数据进行播放,播放完成后设备回调通知送入下一段PCM数据,从而形成连续播放效果。 PCM数据字节数与播放时长线性相关。
3、音频的播放是线性顺序的,整体播放过程是流畅的;而 视频帧的播放受限于解析、解码、后处理等过程时快时慢,为了配合音频,要进行控制处理。
【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~
I 帧(Intra coded frames): 关键帧,使用帧内压缩,不使用运动补偿,不依赖其它帧,可以独立解码。
P 帧(Predicted frames): 采用帧间编码方式,即同时利用了空间和时间上的相关性。P 帧图像只采用前向时间预测,可以提高压缩效率和图像质量。P 帧图像中可以包含帧内编码的部分,即 P 帧中的每一个宏块可以是前向预测,也可以是帧内编码。P帧解码依赖于前面的I帧或者P帧。
B 帧(Bi-directional predicted frames): 采用帧间编码方式,且采用双向时间预测,可以大大提高压缩倍数。也就是其在时间相关性上,还依赖后面的视频帧,也正是由于 B 帧图像采用了后面的帧作为参考,因此造成视频帧的传输顺序和显示顺序是不同的。B帧解码既要依赖于前面的P帧或I帧,还依赖于其之后的P帧。
GOP: 指两个关键帧(I帧)之间的距离。
Reference?:指两个P帧之间的距离。
在码率不变的前提下,GOP值越大,P、B帧的数量会越多,平均每个I、P、B帧所占用的字节数就越多,也就更容易获取较好的图像质量;Reference越大,B帧的数量越多,同理也更容易获得较好的图像质量。 需要说明的是,通过提高GOP值来提高图像质量是有限度的,在遇到场景切换的情况时,H.264编码器会自动强制插入一个I帧,此时实际的GOP值被缩短了。另一方面,在一个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量比较差时,会影响到一个GOP中后续P、B帧的图像质量,直到下一个GOP开始才有可能得以恢复,所以GOP值也不宜设置过大。
编码器在对视频帧编码处理时,每一帧视频帧都附带 DTS 和 PTS 两个时间戳信息,以及一个Duration显示时长信息。
DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
Duration: 当前视频帧显示时长。以30FPS帧率为例,显示时长为33ms
当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是一致的。但如果有 B 帧时,就回到了我们前面说的问题:解码顺序和播放顺序不一致了,即视频输出是非线性的。
I frame 的解码不依赖于任何的其它的帧.
而 P frame的解码则依赖于其前面的I frame或者P frame.
B frame的解码则依赖于其前的最近的一个I frame或者P frame 及其后的最近的一个P frame
每一帧音频帧也有PTS和DTS时间戳。对于音频数据,当其通道数、采样位数、采样率固定后,其对应的播放时长和字节数是线性对应的,1秒的音频时长总计需要PCM字节数:
采样率 * 通道数 * (采样位数/8)
以双声道、16位数据、44.1KHz采样率的音频为例, 1秒音频时长需要字节数:
44100 * 2 *(16/2)=172.27KB
根据这个公式,可以根据PCM的字节数来计算整个播放时长。
音频输出设备在播放时,通常是一段一段的送入PCM数据,当一段PCM数据播放完成后,音频设备总是通过回调通知送入下一段PCM数据,在回调时通常会附带播放时间戳信息,可以根据这个信息更新音频播放时长。
(1)以视频帧播放速度为基准,将音频播放同步到视频播放时间上
(2)以音频的播放速度为基准,将视频播放同步到音频上
(3)使用一个统一的全局时钟为基准,视频和音频的播放速度都以该时钟为标准
考虑到人对声音的敏感度要强于视频,频繁调节音频会带来较差的观感体验,且音频的播放时钟为线性增长,所以通常不会采用第一种策略,而是采用第二或第三种策略,或者将两者策略混合进行。
(1)使用一个全局的系统时钟,自动增加时钟。
(2)音频数据通常每段播放200ms左右的PCM数据,播放完成后设备回调通知播放下一段数据,同时将播放时间戳信息回调上来,根据已经播放的时间戳来累加统计音频播放的精准时间。同时将音频时钟 更新到全局系统时钟上。
解码后的视频帧的显示会根据当前视频帧PTS和基准时钟来控制显示:
(1)当前视频帧的PTS >= 基准时钟: (视频帧解码处理很快,这种情况极少)
直接延长当前视频帧的显示时间即可。
(2)当前视频帧的PTS < 基准时钟 < 下一GOP开始时间点:
当前视频帧解码后直接丢弃,开始解码后续帧,一直解码到与全局时钟平齐的视频帧 。如果GOP中帧数比较大,当前PTS与全局时钟差距也比较大,这里的连续解码也会消耗过多的时间。这种情况下性能优先(质量可降低)时,可以快速跳转到平齐的P或B帧解码,但是这种情况下大概率会出现马赛克,直到下一个GOP才可以规避马赛克。
(3)当前视频帧的PTS <下一GOP开始时间点 < 基准时钟 :
找到全局时钟对应的GOP,从I帧开始开始依次解码,一直解码到与全局时钟平齐的视频帧。如果GOP中帧数比较大,当前PTS与全局时钟差距也比较大,这里的连续解码也会消耗过多的时间。这种情况下性能优先(质量可降低)时,可以快速跳转到平齐的P或B帧解码,但是这种情况下大概率会出现马赛克,直到下一个GOP才可以规避马赛克。
媒体播放器在播放时的处理逻辑:
1、音频帧正常顺序播放,音频播放时间作为基准时钟
2、视频帧在解码后,根据基准时钟来调整当前处理:
(2.1)视频帧PTS > 基准时钟: 根据时间差异计算视频帧显示延时
(2.2)视频帧PTS < 基准时钟 < 下一GOP点:丢弃当前帧,直接依次解码后续帧
(2.3)视频帧PTS < 下一GOP点 < 基准时钟:直接调整到下一GOP开始I帧,依次解码