视频播放简介
解封装是指将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类有很多,例如MP4、MKV、RMVB、TS、FLV、AVI等,其作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
1.解协议
解协议是指将流媒体协议的数据,解析为标准的封装格式数据。音视频在网络上传播的时候,常采用各种流媒体协议,例如HTTP、RTMP、RTMP、MMS等。这些协议在传输音视频数据的同时,也会传输一些信令数据。这些信令数据包括对播放的控制(播放、暂停、停止),或者对网络状态的描述等。在解协议的过程中会去除信令数据而只保留音视频数据。例如采用RTMP协议传输的数据,经过解协议操作后,输出FLV格式的数据。 注意:“文件”本身也是一种“协议”,常见的流媒体协议有HTTP、RTSP、RTMP等。
2.解封装
解封装是指将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类有很多,例如MP4、MKV、RMVB、TS、FLV、AVI等,其作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
3.解码
解码是指将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC、MP3、AC-3等,视频的压缩编码标准则包含H.264、MPEG-2、VC-1等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P、RGB等。压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。
4.音视频同步
根据解封装模块在处理过程中获取的参数信息,同步解码出来的视频和音频数据,被送至系统的显卡和声卡播放出来。为什么需要音视频同步呢?媒体数据经过解复用流程后,音频/视频解码便是独立的,也是独立播放的,而在音频流和视频流中,其播放速度是由相关信息指定的,例如视频是根据帧率,音频是根据采样率。从帧率及采样率即可知道视频/音频播放速度。声卡和显卡均是以一帧数据来作为播放单位,如果单纯依赖帧率及采样率进行播放,在理想条件下,应该是同步的,不会出现偏差。
下面以一个44.1kHz的AAC音频流和24f/s的视频流为例来说明。如果一个AAC音频frame每个声道包含1024个采样点,则一个frame的播放时长为(1024/44 100)×1000ms≈23.27ms,而一个视频frame播放时长为1000ms/24≈41.67ms。理想情况下,音视频完全同步,但实际情况下,如果用上面那种简单的方式,慢慢地就会出现音视频不同步的情况,要么是视频播放快了,要么是音频播放快了。可能的原因包括:一帧的播放时间难以精准控制;音视频解码及渲染的耗时不同,可能造成每一帧输出有一点细微差距,长久累计,不同步便越来越明显;音频输出是线性的,而视频输出可能是非线性的,从而导致有偏差;媒体流本身音视频有差距(特别是TS实时流,音视频能播放的第1个帧起点不同),所以解决音视频同步问题引入了时间戳,它包括几个特点:首先选择一个参考时钟(要求参考时钟上的时间是线性递增的),编码时依据参考时钟给每个音视频数据块都打上时间戳。播放时,根据音视频时间戳及参考时钟来调整播放,所以视频和音频的同步实际上是一个动态的过程,同步是暂时的,不同步则是常态。
ffplay是使用FFmpeg API开发的功能完善的开源播放器。在ffplay中各个线程如图5-45所示,扮演角色如下:read_thread线程扮演着图中Demuxer的角色;video_thread线程扮演着图中VideoDecoder的角色;audio_thread线程扮演着图中Audio Decoder的角色。主线程中的event_loop函数循环调用refresh_loop_wait_event则扮演着视频渲染的角色。回调函数sdl_audio_callback扮演着图中音频播放的角色。VideoState结构体变量则扮演着各个线程之间的信使。
- (1)read_thread线程负责读取文件内容,将video和audio内容分离出来后生成packet,将packet输出到packet队列中,包括Video Packet Queue和Audio Packet Queue,不考虑subtitle。
- (2)video_thread线程负责读取Video PacketsQueue队列,将video packet解码得到VideoFrame,将Video Frame输出到Video FrameQueue队列中。
- (3)audio_thread线程负责读取Audio PacketsQueue队列,将audio packet解码得到AudioFrame,将Audio Frame输出到Audio FrameQueue队列中。
- (4)主线程→event_loop→refresh_loop_wait_event负责读取Video Frame Queue中的video frame,调用SDL进行显示,其中包括了音视频同步控制的相关操作。
- (5)SDL的回调函数sdl_audio_callback负责读取Audio Frame Queue中的audio frame,对其进行处理后,将数据返回给SDL,然后SDL进行音频播放。
FFmpeg解码流程图:
- (1)注册所有容器格式和CODEC,使用av_register_all,最新版本中无须调用该函数。
- (2)打开文件av_open_input_file,最新版本为avformat_open_input。
- (3)从文件中提取流信息av_find_stream_info。
- (4)枚举所有流,查找的种类为CODEC_TYPE_VIDEO。
- (5)查找对应的解码器avcodec_find_decoder。
- (6)打开编解码器avcodec_open。
- (7)为解码帧分配内存avcodec_alloc_frame。
- (8)不停地从码流中提取帧数据av_read_frame。
- (9)判断帧的类型,对于视频帧则调用avcodec_decode_video。
- (10)解码完后,释放解码器avcodec_close。
- (11)关闭输入文件av_close_input_file。
注意:该流程图为FFmpeg 2.x的版本,最新的FFmpeg 4.x系列的流程图略有改动。