一.序
还是按部就班的来,这次主要分析一下Mplayer中音频解码流程,特别说明一下,这里
的音频解码包括后面会说的视频解码统统不涉及到具体的格式和解码算法,如果大伙对具
体文件格式和解码感兴趣可以在网上找相关资料看看~也可以留意popy的后续文章(广告~
)
二.入口
main函数中的入口如下~
/*========================== PLAY AUDIO ============================*/
if (mpctx->sh_audio)
if (!fill_audio_out_buffers())
// at eof, all audio at least written to ao
由mpctx->sh_audio引出,我想重点强调一下sh_audio_t结构,这是音频流头部结构,
要仔细看看(注释也别放过)~
// Stream headers:
typedef struct {
int aid;
demux_stream_t *ds;
struct codecs_st *codec;
unsigned int format;
int initialized;
float stream_delay; // number of seconds stream should be delayed (according
to dwStart or similar)
// output format:
int sample_format;
int samplerate;
int samplesize;
int channels;
int o_bps; // == samplerate*samplesize*channels (uncompr. bytes/sec)
int i_bps; // == bitrate (compressed bytes/sec)
// in buffers:
int audio_in_minsize; // max. compressed packet size (== min. in buffer size
)
char* a_in_buffer;
int a_in_buffer_len;
int a_in_buffer_size;
// decoder buffers:
int audio_out_minsize; // max. uncompressed packet size (==min. out buffsize
)
char* a_buffer;
int a_buffer_len;
int a_buffer_size;
// output buffers:
char* a_out_buffer;
int a_out_buffer_len;
int a_out_buffer_size;
struct af_stream_s *afilter; // the audio filter stream
struct ad_functions_s* ad_driver;
#ifdef DYNAMIC_PLUGINS
void *dec_handle;
#endif
// win32-compatible codec parameters:
AVIStreamHeader audio;
WAVEFORMATEX* wf;
// codec-specific:
void* context; // codec-specific stuff (usually HANDLE or struct pointer)
unsigned char* codecdata; // extra header da
int codecdata_len;
double pts; // last known pts value in output from decoder
int pts_bytes; // bytes output by decoder after last known pts
char* lang; // track language
int default_track;
} sh_audio_t;
三.step by step
1.下面我们来分析fill_audio_out_buffers()函数
static int fill_audio_out_buffers(void)
1.1查看音频驱动有多大的空间可以填充解码后的音频数据
bytes_to_write = mpctx->audio_out->get_space();
如果有空闲的空间,Mplayer会调用ao->play()函数来填充这个buffer,并播放音频数据。
这个音频驱动的buffer的大小要设置合适,太小会导致一帧图像还没播放完,buffer就已
经空了,会导致音视频严重不同步;太大会导致Mplayer需要不停地解析媒体文件来填充这
个音频buffer,而视频可能还来不及播放。。。
1.2 对音频流进行解码
if (decode_audio(sh_audio, playsize) < 0)
注:这里的decode_audio只是一个接口,并不是具体的解码api。
这个函数从sh_audio->a_out_buffer获得至少playsize bytes的解码或过滤后的音频数据
,成功返回0,失败返回-1
1.3 将解码后的音频数据进行播放
playsize = mpctx->audio_out->play(sh_audio->a_out_buffer, playsize, playflags)
;
注:从sh_audio->a_out_buffer中copy playsize bytes数据,注意这里一定要copy出来,
因为当play调用后,这部分数据就会被覆盖了。这些数据不一定要用完,返回的值是用掉
的数据长度。另外当flags|AOPLAY_FINAL_CHUNK的值是真时,说明音频文件快结束了。
1.4 if (playsize > 0) {
sh_audio->a_out_buffer_len -= playsize;
memmove(sh_audio->a_out_buffer, &sh_audio->a_out_buffer[playsize],
sh_audio->a_out_buffer_len);
mpctx->delay += playback_speed*playsize/(double)ao_da
}
播放成功后就从sh_audio->a_out_buffer中移出这段数据,同时减去
sh_audio->a_out_buffer_len的长度
2.刚才说了,decode_audio()函数并不是真正的解码函数,它只是提供一个接口,下面我
们就来看看这个函数到底做了哪些事情
int decode_audio(sh_audio_t *sh_audio, int minlen)
2.1 解码后的视频数据都被cut成大小相同的一个个区间~
int unitsize = sh_audio->channels * sh_audio->samplesize * 16;
2.2 如果解码器设置了audio_out_minsize,解码可以等价于
while (output_len < target_len) output_len += audio_out_minsize;
因此我们必需保证a_buffer_size大于我们需要的解码数据长度加上audio_out_minsize,
所以我们最大解码长度也就有了如下的限制:(是不是有点绕口?~~)
int max_decode_len = sh_audio->a_buffer_size - sh_audio->audio_out_minsize
;
2.3 如果a_out_buffer中没有我们需要的足够多的解码后数据,我们当然要继续解码撒~
只不过我们要注意,a_out_buffer中的数据是经过filter过滤了的(长度发生了变化),这
里会有一个过滤系数,我们反方向求过滤前的数据长度,所以要除以这个系数。
while (sh_audio->a_out_buffer_len < minlen) {
int declen = (minlen - sh_audio->a_out_buffer_len) / filter_multiplier
+ (unitsize << 5); // some extra for possible filter buffering
declen -= declen % unitsize;
if (filter_n_bytes(sh_audio, declen) < 0)
return -1;
}
3.我们来看看这个filter_n_bytes函数
static int filter_n_bytes(sh_audio_t *sh, int len)
3.1 还记得我们刚才说的那个最大解码长度吗? 这里是要确保不会发生解码后数据溢出。
assert(len-1 + sh->audio_out_minsize <= sh->a_buffer_size);
3.2 按需要解码更多的数据
while (sh->a_buffer_len < len) {
unsigned char *buf = sh->a_buffer + sh->a_buffer_len;
int minlen = len - sh->a_buffer_len;
int maxlen = sh->a_buffer_size - sh->a_buffer_len;
//这里才是调用之前确定的音频解码器进行真正音频解码
int ret = sh->ad_driver->decode_audio(sh, buf, minlen, maxlen);
if (ret <= 0) {
error = -1;
len = sh->a_buffer_len;
break;
}
//解码之后a_buffer_len增加
sh->a_buffer_len += ret;
}
3.3 在前面我们提到过过滤这个步骤,下面的图描述了整个过程
filter_output = af_play(sh->afilter, &filter_input);
注:这里实际上做的是过滤这部分的工作
------------ ---------- ------------
| | 解码 | | 过滤 | | 播放
| 未解码数据 | ----->|解码后数据| ------>| 播放的数据 | ------>
| a_in_buffer| | a_buffer | |a_out_buffer|
------------ ---------- ------------
3.4 if (sh->a_out_buffer_size < sh->a_out_buffer_len + filter_output->len)
注:如果过滤后的数据长度太长,需要扩展a_out_buffer_size
3.5 将过滤后的数据从decoder buffer移除:
sh->a_buffer_len -= len;
memmove(sh->a_buffer, sh->a_buffer + len, sh->a_buffer_len);
四.结语
回顾一下今天的内容,我们从sh_audio_t结构体出发,依次分析了fill_audio_out_b
uffers()函数,decode_audio()函数,filter_n_bytes()函数,但是并没有分析具体的de
co
顺便说点题外话,看Mplayer的代码,结构化模块化的特点很突出,通常是先定义一组
接口,然后具体的程序去实现这些接口,这样子对代码的维护和扩展都很有好处~
mplayer音视频同步浅度学习
mplayer播放时的大循环过程为:
while(!mpctx->eof){
fill_audio_out_buffers();//音频stream的读取,解码,播放
update_video(&blit_frame);//视频stream的读取,解码,过滤处理
sleep_until_update(&time_frame, &aq_sleep_time);//计算延迟时间并睡眠等待
mpctx->video_out->flip_page();//视频的播放
adjust_sync_and_print_status(frame_time_remaining, time_frame);//根据音视频的PTS做同步矫正处理
}
音视频同步方法为
1)音频播放playsize = mpctx->audio_out->play(sh_audio->a_out_buffer, playsize, playflags); 后,根据数据大小算出时间并累计mpctx->delay += playback_speed*playsize/(double)ao_da
2)视频解码前,用累计延迟时间剪掉本祯视频的时间mpctx->delay -= frame_time;
3)计算声音延迟时间*time_frame = delay - mpctx->delay / playback_speed;其中float delay = mpctx->audio_out->get_delay();为距当前声音OUTPUT BUF里数据被全部播放完为止所需的时间。
4)播放视频同步完成,所以视频的播放是完全根据声卡最后的数据输出来同步的。
5)计算出当前音视频PTS差double AV_delay = a_pts - audio_delay - v_pts;再算出矫正值x =(AV_delay + timing_error * playback_speed) *0.1f;最后把矫正的时间加到延迟累计中mpctx->delay+=x;。