Mplayer 音频解码分析
一.序
? ? 還是按部就班的來,這次主要分析一下Mplayer中音頻解碼流程,特別說明一下,這里
的音頻解碼包括后面會說的視頻解碼統(tǒng)統(tǒng)不涉及到具體的格式和解碼算法,如果大伙對具
體文件格式和解碼感興趣可以在網(wǎng)上找相關(guān)資料看看~也可以留意popy的后續(xù)文章(廣告~
)
二.入口
? ? main函數(shù)中的入口如下~
/*========================== PLAY AUDIO ============================*/
if (mpctx->sh_audio)
? ? if (!fill_audio_out_buffers())
? ? // at eof, all audio at least written to ao
? ? 由mpctx->sh_audio引出,我想重點(diǎn)強(qiáng)調(diào)一下sh_audio_t結(jié)構(gòu),這是音頻流頭部結(jié)構(gòu),
要仔細(xì)看看(注釋也別放過)~
// 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 data passed from demuxer to codec
??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()函數(shù)
static int fill_audio_out_buffers(void)
1.1查看音頻驅(qū)動(dòng)有多大的空間可以填充解碼后的音頻數(shù)據(jù)
bytes_to_write = mpctx->audio_out->get_space();
如果有空閑的空間,Mplayer會調(diào)用ao->play()函數(shù)來填充這個(gè)buffer,并播放音頻數(shù)據(jù)。
這個(gè)音頻驅(qū)動(dòng)的buffer的大小要設(shè)置合適,太小會導(dǎo)致一幀圖像還沒播放完,buffer就已
經(jīng)空了,會導(dǎo)致音視頻嚴(yán)重不同步;太大會導(dǎo)致Mplayer需要不停地解析媒體文件來填充這
個(gè)音頻buffer,而視頻可能還來不及播放。。。
1.2 對音頻流進(jìn)行解碼
if (decode_audio(sh_audio, playsize) < 0)
注:這里的decode_audio只是一個(gè)接口,并不是具體的解碼api。
這個(gè)函數(shù)從sh_audio->a_out_buffer獲得至少playsize bytes的解碼或過濾后的音頻數(shù)據(jù)
,成功返回0,失敗返回-1
1.3 將解碼后的音頻數(shù)據(jù)進(jìn)行播放
playsize = mpctx->audio_out->play(sh_audio->a_out_buffer, playsize, playflags)
;
注:從sh_audio->a_out_buffer中copy playsize bytes數(shù)據(jù),注意這里一定要copy出來,
因?yàn)楫?dāng)play調(diào)用后,這部分?jǐn)?shù)據(jù)就會被覆蓋了。這些數(shù)據(jù)不一定要用完,返回的值是用掉
的數(shù)據(jù)長度。另外當(dāng)flags|AOPLAY_FINAL_CHUNK的值是真時(shí),說明音頻文件快結(jié)束了。
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_data.bps;
? ? ? ? }
播放成功后就從sh_audio->a_out_buffer中移出這段數(shù)據(jù),同時(shí)減去
sh_audio->a_out_buffer_len的長度
2.剛才說了,decode_audio()函數(shù)并不是真正的解碼函數(shù),它只是提供一個(gè)接口,下面我
們就來看看這個(gè)函數(shù)到底做了哪些事情
int decode_audio(sh_audio_t *sh_audio, int minlen)
2.1 解碼后的視頻數(shù)據(jù)都被cut成大小相同的一個(gè)個(gè)區(qū)間~
? ? int unitsize = sh_audio->channels * sh_audio->samplesize * 16;
2.2 如果解碼器設(shè)置了audio_out_minsize,解碼可以等價(jià)于
? ? while (output_len < target_len) output_len += audio_out_minsize;
因此我們必需保證a_buffer_size大于我們需要的解碼數(shù)據(jù)長度加上audio_out_minsize,
所以我們最大解碼長度也就有了如下的限制:(是不是有點(diǎn)繞口?~~)
? ? int max_decode_len = sh_audio->a_buffer_size - sh_audio->audio_out_minsize
;
2.3 如果a_out_buffer中沒有我們需要的足夠多的解碼后數(shù)據(jù),我們當(dāng)然要繼續(xù)解碼撒~
只不過我們要注意,a_out_buffer中的數(shù)據(jù)是經(jīng)過filter過濾了的(長度發(fā)生了變化),這
里會有一個(gè)過濾系數(shù),我們反方向求過濾前的數(shù)據(jù)長度,所以要除以這個(gè)系數(shù)。
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.我們來看看這個(gè)filter_n_bytes函數(shù)
static int filter_n_bytes(sh_audio_t *sh, int len)
3.1 還記得我們剛才說的那個(gè)最大解碼長度嗎? 這里是要確保不會發(fā)生解碼后數(shù)據(jù)溢出。
assert(len-1 + sh->audio_out_minsize <= sh->a_buffer_size);
3.2 按需要解碼更多的數(shù)據(jù)
? ? 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;
? ?? ???//這里才是調(diào)用之前確定的音頻解碼器進(jìn)行真正音頻解碼
? ? ? ? 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 在前面我們提到過過濾這個(gè)步驟,下面的圖描述了整個(gè)過程
filter_output = af_play(sh->afilter, &filter_input);
注:這里實(shí)際上做的是過濾這部分的工作
------------? ?? ?? ?----------? ?? ?? ? ------------
|? ?? ?? ?? ?|??解碼 |? ?? ?? ? |??過濾??|? ?? ?? ?? ?|??播放
| 未解碼數(shù)據(jù) | ----->|解碼后數(shù)據(jù)| ------>| 播放的數(shù)據(jù) | ------>
| a_in_buffer|? ?? ? | a_buffer |? ?? ???|a_out_buffer|
------------? ?? ?? ?----------? ?? ?? ? ------------
3.4 if (sh->a_out_buffer_size < sh->a_out_buffer_len + filter_output->len)
注:如果過濾后的數(shù)據(jù)長度太長,需要擴(kuò)展a_out_buffer_size
3.5 將過濾后的數(shù)據(jù)從decoder buffer移除:
? ? sh->a_buffer_len -= len;
? ? memmove(sh->a_buffer, sh->a_buffer + len, sh->a_buffer_len);
四.結(jié)語
? ? 回顧一下今天的內(nèi)容,我們從sh_audio_t結(jié)構(gòu)體出發(fā),依次分析了fill_audio_out_b
uffers()函數(shù),decode_audio()函數(shù),filter_n_bytes()函數(shù),但是并沒有分析具體的de
code和play函數(shù)。
? ? 順便說點(diǎn)題外話,看Mplayer的代碼,結(jié)構(gòu)化模塊化的特點(diǎn)很突出,通常是先定義一組
接口,然后具體的程序去實(shí)現(xiàn)這些接口,這樣子對代碼的維護(hù)和擴(kuò)展都很有好處~
?
??mplayer音視頻同步淺度學(xué)習(xí)
?mplayer播放時(shí)的大循環(huán)過程為:
while(!mpctx->eof){
??fill_audio_out_buffers();//音頻stream的讀取,解碼,播放
? update_video(&blit_frame);//視頻stream的讀取,解碼,過濾處理
??sleep_until_update(&time_frame, &aq_sleep_time);//計(jì)算延遲時(shí)間并睡眠等待
??mpctx->video_out->flip_page();//視頻的播放
??adjust_sync_and_print_status(frame_time_remaining, time_frame);//根據(jù)音視頻的PTS做同步矯正處理
}
音視頻同步方法為
1)音頻播放playsize = mpctx->audio_out->play(sh_audio->a_out_buffer, playsize,? playflags);? 后,根據(jù)數(shù)據(jù)大小算出時(shí)間并累計(jì)mpctx->delay?+=?playback_speed*playsize/(double)ao_data.bps;
2)視頻解碼前,用累計(jì)延遲時(shí)間剪掉本禎視頻的時(shí)間mpctx->delay -= frame_time;
3)計(jì)算聲音延遲時(shí)間*time_frame = delay - mpctx->delay / playback_speed;其中float delay = mpctx->audio_out->get_delay();為距當(dāng)前聲音OUTPUT BUF里數(shù)據(jù)被全部播放完為止所需的時(shí)間。
4)播放視頻同步完成,所以視頻的播放是完全根據(jù)聲卡最后的數(shù)據(jù)輸出來同步的。
5)計(jì)算出當(dāng)前音視頻PTS差double AV_delay = a_pts - audio_delay - v_pts;再算出矯正值x =(AV_delay + timing_error * playback_speed) *0.1f;最后把矯正的時(shí)間加到延遲累計(jì)中mpctx->delay+=x;。
轉(zhuǎn)載于:https://www.cnblogs.com/weinyzhou/archive/2012/12/13/2868682.html
總結(jié)
以上是生活随笔為你收集整理的Mplayer 音频解码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SLAM方向国内有哪些优秀的公司?
- 下一篇: 第六集 MSF构思阶段项目团队的组建