2010年5月26日 星期三

mplayer音頻解碼分析

轉錄(http://dev.firnow.com/course/3_program/c++/cppjs/20090903/173578.html)
一.入口
    
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 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()函數 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_data.bps;
        
} 播放成功後就從 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 code和play函數。
    
順便說點題外話,看Mplayer的代碼,結構化模塊 化的特點很突出,通常是先定義一組 接口,然後具體的程序去實現這些接口,這樣子對
代碼的維護和擴展都很有好處~

沒有留言:

張貼留言