-
Notifications
You must be signed in to change notification settings - Fork 264
libav
#AVPlayer 之libav模块
##libav模块简介
Libav是在基于ffmpeg之上的一个封装模块(不是ffmpeg分裂出来的那个libav), 采用c编写, 以提供少量API, 就可以组织成一个播放器. 整个模块以avplay结构为核心, media_source为媒体数据输入, audio_render为音频播放, video_render为视频渲染, 其中除avplay之外都需要在libav模块外实现, 这样方便制定各种需求.
libav模块的基本流程是, 以read_pkt_thrd为视频数据读取线程函数, 将读取的音视频packet(未解码), 投递到未解码的队列中(m_audio_q, m_video_q), 并通知视频和音频解码线程(video_dec_thrd, audio_dec_thrd)去从队列中获取packet并解码, 音视频解码线程在收到通知获得packet, 便开始解码, 解码后将解码frame投递到音视频解码队列(m_audio_dq, m_video_dq)中, 并通知音视频渲染线程(audio_render_thrd, video_render_thrd)去队列中获得解码frame并渲染.
整个如下图所示:
+----------------+ +------------------+
+-------------+ +---> | video_dec_thrd | --> | video_render_thrd|-->口 视频输出
|read_pkt_thrd|---| +----------------+ +------------------+
+-------------+ +---> | audio_dec_thrd | --> | audio_render_thrd|-->( 音频输出
+----------------+ +------------------+
##read_pkt_thrd线程 在外部调用initialize之后(initialize将创建各线程, 并打开解码器, 初始化各队列, 创建m_format_ctx), read_pkt_thrd线程将循环调用av_read_frame, 将根据读取的packet的stream_index投递到对应的队列中, 在put_quque函数中将会通知解码线程获得packet去解码.
##audio_dec_thrd线程
音频解码线程循环调用get_queue来获得read_pkt_thrd线程所读取的packet, 并将packet解码, 解码完成后copy为一份新的frame, 保证不与解码buf冲突, 在audio_copy函数中, 判断如果是多于2声道的音频, 将重采样成2声道的音频, 如果不是16位的采样也将会重采样统一成16位. 接下来计算它的pts, 计算音频的pts方法非常简单, 就是根据channels和sample_rate以及数据大小来计算的, 比如一个packet解码后有1024byte, 2channels, 16bit, 44100sample rate, 那么这个frame的播放时长=1024/441002(16/2)=0.0058秒, 那么pts就是m_audio_clock, 那么下一个音频的pts就是m_audio_clock += 0.0058;
需要注意的是, 在avplay播放时和视频同步使用的时间戳是通过audio_clock函数来计算的, 在这个函数中, 首先得到m_audio_clock值(也就是所有已解码数据播放完成时的pts, 但此时实际上音频播放器还只播放了一小部分数据), 根据m_audio_clock和已播放完成的字节数计算出当前的准确播放位置, 如下图所示:
如下图所示:
若下面方括号中这个块是已解码的音频帧, 那么对应关系是:
已播 未播 m_audio_clock
0.2s 0.8s |
[----+----------------] 共1s
begin 假设'+'这个位置是当前播放的位置.
未完待续...