FFmpeg的H.264解碼器源代碼簡單分析


本文簡單記錄FFmpeg中libavcodec的H.264解碼器(H.264 Decoder)的源代碼。這個H.264解碼器十分重要,可以說FFmpeg項目今天可以幾乎“壟斷”視音頻編解碼技術,很大一部分貢獻就來自於這個H.264解碼器。這個H.264解碼器一方面功能強大,性能穩定;另一方面源代碼也比較復雜,難以深入研究。本文打算梳理一下這個H.264解碼器的源代碼結構,以方便以后深入學習H.264使用。
PS:這部分代碼挺復雜的,還有不少地方還比較模糊,還需要慢慢學習......

 

函數調用關系圖

 

H.264解碼器的函數調用關系圖如下所示。

 

 

下面解釋一下圖中關鍵標記的含義。

  

作為接口的結構體

FFmpeg和H.264解碼器之間作為接口的結構體有2個:

ff_h264_parser:用於解析H.264碼流的AVCodecParser結構體。
ff_h264_decoder:用於解碼H.264碼流的AVCodec結構體。


函數背景色

函數在圖中以方框的形式表現出來。不同的背景色標志了該函數不同的作用:

白色背景的函數:普通內部函數。
粉紅色背景函數:解析函數(Parser)。這些函數用於解析SPS、PPS等信息。
紫色背景的函數:熵解碼函數(Entropy Decoding)。這些函數讀取碼流數據並且進行CABAC或者CAVLC熵解碼。
綠色背景的函數:解碼函數(Decode)。這些函數通過幀內預測、幀間預測、DCT反變換等方法解碼壓縮數據。
黃色背景的函數:環路濾波函數(Loop Filter)。這些函數對解碼后的數據進行濾波,去除方塊效應。
藍色背景函數:匯編函數(Assembly)。這些函數是做過匯編優化的函數。圖中主要畫出了這些函數的C語言版本,此外這些函數還包含MMX版本、SSE版本、NEON版本等。


箭頭線

箭頭線標志了函數的調用關系:

黑色箭頭線:不加區別的調用關系。
粉紅色的箭頭線:解析函數(Parser)之間的調用關系。
紫色箭頭線:熵解碼函數(Entropy Decoding)之間的調用關系。
綠色箭頭線:解碼函數(Decode)之間的調用關系。
黃色箭頭線:環路濾波函數(Loop Filter)之間的調用關系。

 

函數所在的文件

每個函數標識了它所在的文件路徑。

 

下文簡單記錄幾個關鍵的部分。

FFmpeg和H.264解碼器之間作為接口的結構體

 

FFmpeg和H.264解碼器之間作為接口的結構體有2個:ff_h264_parser和ff_h264_decoder。

ff_h264_parser
ff_h264_parser是用於解析H.264碼流的AVCodecParser結構體。AVCodecParser中包含了幾個重要的函數指針:

parser_init():初始化解析器。
parser_parse():解析。
parser_close():關閉解析器。

在ff_h264_parser結構體中,上述幾個函數指針分別指向下面幾個實現函數:

init():初始化H.264解析器。
h264_parse():解析H.264碼流。
close():關閉H.264解析器。

ff_h264_decoder
ff_h264_decoder是用於解碼H.264碼流的AVCodec結構體。AVCodec中包含了幾個重要的函數指針:

init():初始化解碼器。
decode():解碼。
close():關閉解碼器。

在ff_h264_decoder結構體中,上述幾個函數指針分別指向下面幾個實現函數:

ff_h264_decode_init():初始化H.264解碼器。
h264_decode_frame():解碼H.264碼流。
h264_decode_end():關閉H.264解碼器。


普通內部函數

普通內部函數指的是H.264解碼器中還沒有進行分類的函數。下面舉幾個例子。
ff_h264_decoder中ff_h264_decode_init()調用的初始化函數:

ff_h264dsp_init():初始化DSP相關的函數。包含了IDCT、環路濾波函數等。
ff_h264qpel_init():初始化四分之一像素運動補償相關的函數。
ff_h264_pred_init():初始化幀內預測相關的函數。
ff_h264_decode_extradata():解析AVCodecContext中的extradata。

ff_h264_decoder中h264_decode_frame()逐層調用的和解碼Slice相關的函數:

decode_nal_units(),ff_h264_execute_decode_slices(),decode_slice()等。

ff_h264_decoder中h264_decode_end()調用的清理函數:

ff_h264_remove_all_refs():移除所有參考幀。
ff_h264_free_context():釋放在初始化H.264解碼器的時候分配的內存。
ff_h264_parser中h264_parse()逐層調用的和解析Slice相關的函數:
h264_find_frame_end():查找NALU的結尾。
parse_nal_units():解析一個NALU。


解析函數(Parser)

解析函數(Parser)用於解析H.264碼流中的一些信息(例如SPS、PPS、Slice Header等)。在parse_nal_units()和decode_nal_units()中都調用這些解析函數完成了解析。下面舉幾個解析函數的例子。

ff_h264_decode_nal():解析NALU。這個函數是后幾個解析函數的前提。
ff_h264_decode_slice_header():解析Slice Header。
ff_h264_decode_sei():解析SEI。
ff_h264_decode_seq_parameter_set():解析SPS。
ff_h264_decode_picture_parameter_set():解析PPS。


熵解碼函數(Entropy Decoding)

熵解碼函數(Entropy Decoding)讀取碼流數據並且進行CABAC或者CAVLC熵解碼。CABAC解碼函數是ff_h264_decode_mb_cabac(),CAVLC解碼函數是ff_h264_decode_mb_cavlc()。熵解碼函數中包含了很多的讀取指數哥倫布編碼數據的函數,例如get_ue_golomb_long(),get_ue_golomb(),get_se_golomb(),get_ue_golomb_31()等等。
在獲取殘差數據的時候需要進行CAVLC/CABAC解碼。例如解碼CAVLC的時候,會調用decode_residual()函數,而decode_residual()會調用get_vlc2()函數,get_vlc2()會調用OPEN_READER(),UPDATE_CACHE(),GET_VLC(),CLOSE_READER()幾個函數讀取CAVLC格式的數據。
此外,在獲取運動矢量的時候,會調用pred_motion()以及類似的幾個函數獲取運動矢量相關的信息。

解碼函數(Decode)

解碼函數(Decode)通過幀內預測、幀間預測、DCT反變換等方法解碼壓縮數據。解碼函數是ff_h264_hl_decode_mb()。其中跟宏塊類型的不同,會調用幾個不同的函數,最常見的就是調用hl_decode_mb_simple_8()。
hl_decode_mb_simple_8()的定義是無法在源代碼中直接找到的,這是因為它實際代碼的函數名稱是使用宏的方式寫的(以后再具體分析)。hl_decode_mb_simple_8()的源代碼實際上就是FUNC(hl_decode_mb)()函數的源代碼。
FUNC(hl_decode_mb)()根據宏塊類型的不同作不同的處理:如果宏塊類型是INTRA,就會調用hl_decode_mb_predict_luma()進行幀內預測;如果宏塊類型不是INTRA,就會調用FUNC(hl_motion_422)()或者FUNC(hl_motion_420)()進行四分之一像素運動補償。
隨后FUNC(hl_decode_mb)()會調用hl_decode_mb_idct_luma()等幾個函數對數據進行DCT反變換工作。

環路濾波函數(Loop Filter)

環路濾波函數(Loop Filter)對解碼后的數據進行濾波,去除方塊效應。環路濾波函數是loop_filter()。其中調用了ff_h264_filter_mb()和ff_h264_filter_mb_fast()。ff_h264_filter_mb_fast()中又調用了h264_filter_mb_fast_internal()。而h264_filter_mb_fast_internal()中又調用了下面幾個函數進行濾波:

filter_mb_edgeh():亮度水平濾波
filter_mb_edgev():亮度垂直濾波
filter_mb_edgech():色度水平濾波

filter_mb_edgecv():色度垂直濾波

 

 

 

匯編函數(Assembly)

匯編函數(Assembly)是做過匯編優化的函數。為了提高效率,整個H.264解碼器中(主要在解碼部分和環路濾波部分)包含了大量的匯編函數。實際解碼的過程中,FFmpeg會根據系統的特性調用相應的匯編函數(而不是C語言函數)以提高解碼的效率。如果系統不支持匯編優化的話,FFmpeg才會調用C語言版本的函數。例如在幀內預測的時候,對於16x16亮度DC模式,有以下幾個版本的函數:

C語言版本的pred16x16_dc_8_c()
NEON版本的ff_pred16x16_dc_neon()
MMXEXT版本的ff_pred16x16_dc_8_mmxext()
SSE2版本的ff_pred16x16_dc_8_sse2()



至此FFmpeg的H.264解碼器的結構就大致梳理完畢了。

 

下面是C++源碼:

  1 //h264dec.h:
  2 //
  3 //[cpp]
  4 #pragma once 
  5 #include "tdll.h" 
  6 #include "avcodec.h" 
  7 #include "postprocess.h" 
  8 //#include "EMVideoCodec.h" 
  9  
 10 class h264dec /*: public IH264Decoder*/ 
 11 { 
 12 public: 
 13     virtual bool InitH264Deocder(int width,int height); 
 14     virtual bool H264Decode(unsigned char * inbuf, const int & inlen,unsigned char * outbuf,int & outlen); 
 15     virtual void StopH264Decoder(); 
 16     virtual void ReleaseConnection(); 
 17  
 18 public: 
 19     h264dec(void); 
 20     virtual ~h264dec(void); 
 21 private: 
 22     Tdll *dll; 
 23  
 24     bool LoadDllFun(); 
 25     void (*avcodec_init)(void); 
 26     void (*avcodec_register_all)(void); 
 27     AVCodecContext* (*avcodec_alloc_context)(void); 
 28     AVFrame* (*avcodec_alloc_frame)(void); 
 29     AVCodec *(*avcodec_find_decoder)(enum CodecID id); 
 30     int (*avcodec_decode_video)(AVCodecContext *avctx, AVFrame *picture,int *got_picture_ptr, 
 31                                 uint8_t *buf, int buf_size); 
 32     int  (*avcodec_open)(AVCodecContext *avctx, AVCodec *codec); 
 33     int  (*avcodec_close)(AVCodecContext *avctx); 
 34     void (*av_free)(void *ptr);      
 35      
 36     bool InitPostproc(int w,int h); 
 37     void ClosePostproc(); 
 38     pp_context_t *(*pp_get_context)(int width, int height, int flags); 
 39     void (*pp_free_context)(pp_context_t *ppContext); 
 40     void (*pp_free_mode)(pp_mode_t *mode); 
 41     pp_mode_t *(*pp_get_mode_by_name_and_quality)(char *name, int quality); 
 42     void  (*pp_postprocess)(uint8_t * src[3], int srcStride[3], 
 43                  uint8_t * dst[3], int dstStride[3], 
 44                  int horizontalSize, int verticalSize, 
 45                  QP_STORE_T *QP_store,  int QP_stride, 
 46          pp_mode_t *mode, pp_context_t *ppContext, int pict_type); 
 47 private: 
 48     AVCodec         *pdec; 
 49     AVCodecContext  *pdecContext; 
 50     AVFrame         *pdecFrame; 
 51     int             m_width; 
 52     int             m_height; 
 53  
 54     Tdll* prodll; 
 55     pp_context_t *pp_context; 
 56     pp_mode_t    *pp_mode; 
 57 }; 
 58 
 59 h264dec.cpp:
 60 [cpp] view plaincopy
 61 #include "StdAfx.h" 
 62 #include ".\h264dec.h" 
 63  
 64 h264dec::h264dec(void) 
 65 :dll(NULL) 
 66 ,pdec(NULL) 
 67 ,pdecContext(NULL) 
 68 ,pdecFrame(NULL) 
 69 ,pp_context(NULL) 
 70 ,pp_mode(NULL) 
 71 ,prodll(NULL) 
 72 { 
 73 } 
 74  
 75 h264dec::~h264dec(void) 
 76 { 
 77 } 
 78  
 79 bool h264dec::LoadDllFun() 
 80 { 
 81     dll=new Tdll(L"libavcodec.dll"); 
 82     dll->loadFunction((void**)&avcodec_init,"avcodec_init"); 
 83     dll->loadFunction((void**)&avcodec_register_all,"avcodec_register_all"); 
 84     dll->loadFunction((void**)&avcodec_alloc_context,"avcodec_alloc_context"); 
 85     dll->loadFunction((void**)&avcodec_alloc_frame,"avcodec_alloc_frame"); 
 86     dll->loadFunction((void**)&avcodec_find_decoder,"avcodec_find_decoder"); 
 87     dll->loadFunction((void**)&avcodec_open,"avcodec_open");  
 88     dll->loadFunction((void**)&avcodec_decode_video,"avcodec_decode_video"); 
 89     dll->loadFunction((void**)&avcodec_close,"avcodec_close"); 
 90     dll->loadFunction((void**)&av_free,"av_free"); 
 91     if (!dll->ok) 
 92         return false; 
 93  
 94     prodll = new Tdll(L"postproc.dll"); 
 95     prodll->loadFunction((void**)&pp_get_context,"pp_get_context"); 
 96     prodll->loadFunction((void**)&pp_free_context,"pp_free_context"); 
 97     prodll->loadFunction((void**)&pp_free_mode,"pp_free_mode"); 
 98     prodll->loadFunction((void**)&pp_get_mode_by_name_and_quality,"pp_get_mode_by_name_and_quality"); 
 99     prodll->loadFunction((void**)&pp_postprocess,"pp_postprocess"); 
100     if(!prodll->ok) 
101         return false; 
102  
103     avcodec_init(); 
104     avcodec_register_all(); 
105     return true; 
106 } 
107  
108 bool h264dec::InitH264Deocder(int width,int height) 
109 { 
110     if(!LoadDllFun()) 
111         return false; 
112     if(!InitPostproc(width,height)) 
113         return false; 
114  
115     m_width=width; 
116     m_height=height; 
117     pdec = avcodec_find_decoder(CODEC_ID_H264); 
118     if (pdec == NULL )   
119         return false; 
120  
121     pdecContext = avcodec_alloc_context(); 
122     pdecFrame = avcodec_alloc_frame(); 
123  
124     pdecContext->width  = width; 
125     pdecContext->height = height; 
126     pdecContext->pix_fmt = PIX_FMT_YUV420P; 
127     /* open it */ 
128     if (avcodec_open(pdecContext, pdec) < 0)  
129     { 
130         return false; 
131     } 
132     return true; 
133 } 
134  
135 bool h264dec::InitPostproc(int w,int h) 
136 { 
137     int i_flags = 0; 
138     i_flags |= PP_CPU_CAPS_MMX | PP_CPU_CAPS_MMX2 | PP_FORMAT_420; 
139     pp_context = pp_get_context( w, h, i_flags ); 
140     if(!pp_context) 
141         return false; 
142     pp_mode = pp_get_mode_by_name_and_quality( "default", 6 ); 
143     if(!pp_mode) 
144         return false; 
145     return true; 
146 } 
147  
148 bool h264dec::H264Decode(unsigned char * inbuf, const int & inlen,unsigned char * outbuf,int & outlen) 
149 { 
150     int got_frame; 
151     BYTE* showImage[3]; 
152     int showheight[3],showLx[3]; 
153  
154     int len; 
155     len=avcodec_decode_video(pdecContext, pdecFrame, &got_frame, inbuf, inlen); 
156     if(len < 0) 
157         return false; 
158  
159     if(got_frame) 
160     { 
161         showImage[0]=outbuf; 
162         showImage[1]=showImage[0]+m_width*m_height; 
163         showImage[2]=showImage[1]+m_width*m_height/4; 
164         showLx[0]=m_width;showLx[1]=m_width>>1;showLx[2]=m_width>>1; 
165         showheight[0]=m_height;showheight[1]=m_height>>1;showheight[2]=m_height>>1; 
166         pp_postprocess(pdecFrame->data,pdecFrame->linesize,showImage,showLx,m_width,m_height,pdecFrame->qscale_table, 
167             pdecFrame->qstride,pp_mode,pp_context,pdecFrame->pict_type); 
168         //GetImage( pdecFrame->data, 
169         //          showImage, 
170         //          pdecFrame->linesize, 
171         //          showLx, 
172         //          showheight); 
173         outlen=m_width*m_height*3/2; 
174     } 
175     else 
176     { 
177         outlen = 0; 
178     } 
179  
180     return true; 
181 } 
182  
183 void h264dec::StopH264Decoder() 
184 { 
185     if (pdecContext != NULL)  
186     {      
187         avcodec_close(pdecContext); 
188         av_free( pdecContext ); 
189         pdecContext = NULL; 
190         if(pdecFrame){ 
191             av_free(pdecFrame); 
192             pdecFrame = NULL; 
193         } 
194     } 
195     if(dll){ 
196         delete dll; 
197         dll=0; 
198     } 
199  
200     ClosePostproc(); 
201 } 
202  
203 void h264dec::ClosePostproc() 
204 { 
205     if(pp_mode){ 
206         pp_free_mode( pp_mode ); 
207         pp_mode=0; 
208     } 
209     if(pp_context){ 
210         pp_free_context(pp_context); 
211         pp_context=0; 
212     } 
213     if(prodll){ 
214         delete prodll; 
215         prodll=0; 
216     } 
217 } 
218  
219 void h264dec::ReleaseConnection() 
220 { 
221     delete this; 
222 } 
223 
224 tdll.h:
225 [cpp] 
226 #ifndef _TDLL_ 
227 #define _TDLL_ 
228  
229 class Tdll 
230 { 
231 private: 
232     HMODULE hdll; 
233     void loadDll(const char *dllName); 
234 public: 
235     bool ok; 
236     Tdll(const TCHAR *dllName1) 
237     { 
238         hdll=LoadLibrary(dllName1); 
239         if (!hdll) 
240         { 
241             hdll=NULL; 
242         } 
243         ok=(hdll!=NULL); 
244     }; 
245     ~Tdll() 
246     { 
247         if (hdll) 
248             FreeLibrary(hdll); 
249     } 
250     void loadFunction(void **fnc,const char *name) 
251     { 
252         *fnc=GetProcAddress(hdll,name); 
253         ok&=(*fnc!=NULL); 
254     }; 
255 };   
256  
257 #endif 
258 
259 main.cpp:
260 [cpp]
261 #include "stdafx.h" 
262 #include "h264dec.h" 
263 #include "postprocess.h" 
264  
265 #define INBUF_SIZE 100 * 1024; 
266  
267  
268 static int FindStartCode (unsigned char *Buf, int zeros_in_startcode) 
269 { 
270     int info; 
271     int i; 
272  
273     info = 1; 
274     for (i = 0; i < zeros_in_startcode; i++) 
275     { 
276         if(Buf[i] != 0) 
277             info = 0; 
278     } 
279  
280     if(Buf[i] != 1) 
281         info = 0; 
282     return info; 
283 } 
284  
285 static bool Check_StartCode(unsigned char *Buf, int pos) 
286 { 
287     int info3 = 0; 
288  
289     info3 = FindStartCode(&Buf[pos-4], 3); 
290     return info3 == 1; 
291  
292 } 
293  
294 static int getNextNal(FILE* inpf, unsigned char* Buf) 
295 { 
296     int pos = 0; 
297     int StartCodeFound = 0; 
298     int info2 = 0; 
299     int info3 = 0; 
300  
301     int nCount = 0; 
302     while(!feof(inpf) && ++nCount <= 4) 
303     { 
304         Buf[pos++]=fgetc(inpf); 
305     } 
306  
307     if(!Check_StartCode(Buf, pos)) 
308     { 
309         return 0; 
310     } 
311  
312  
313     while(!feof(inpf) && (Buf[pos++]=fgetc(inpf))==0); 
314  
315     while (!StartCodeFound) 
316     { 
317         if (feof (inpf)) 
318         { 
319             //          return -1; 
320             return pos-1; 
321         } 
322         Buf[pos++] = fgetc (inpf); 
323  
324         StartCodeFound = Check_StartCode(Buf, pos); 
325     } 
326  
327     fseek (inpf, -4, SEEK_CUR); 
328     return pos - 4; 
329 } 
330  
331 int main(int argc, char* argv[]) 
332 { 
333     if (argc != 5) 
334     { 
335         printf("please input: PP_Demo.exe filename1[input] Width Height filename2[output]\n"); 
336     } 
337      
338     //params set 
339     unsigned short usWidth = atoi(argv[2]); 
340     unsigned short usHeight = atoi(argv[3]); 
341      
342     //create dec&pp 
343     h264dec *pdec = new h264dec; 
344     if(!pdec->InitH264Deocder(usWidth, usHeight)) 
345     { 
346         return false; 
347     } 
348  
349  
350  
351     unsigned char *p_In_Frame = new unsigned char[usWidth * usHeight * 3/2]; 
352     unsigned char *p_Out_Frame = new unsigned char[usWidth * usHeight * 3/2]; 
353     FILE* ifp = fopen(argv[1],"rb"); 
354     FILE* ofp = fopen(argv[4],"wb"); 
355  
356     bool b_continue = true; 
357     int nReadUnit = usWidth * usHeight * 3/2; 
358     while(!feof(ifp)) 
359     { 
360         int nCount = getNextNal(ifp, p_In_Frame); 
361  
362         if(nCount == 0) 
363         { 
364             continue; 
365         } 
366          
367         unsigned char *ptr = p_In_Frame; 
368         int n_Outlen = 0; 
369         pdec->H264Decode(ptr, nCount, p_Out_Frame, n_Outlen); 
370          
371         if(n_Outlen > 0) 
372         { 
373             fwrite(p_Out_Frame, 1, n_Outlen, ofp); 
374         } 
375     } 
376  
377  
378     //realse 
379     delete []p_In_Frame; 
380     delete []p_Out_Frame; 
381     pdec->StopH264Decoder(); 
382     pdec->ReleaseConnection(); 
383     fclose(ifp); 
384     fclose(ofp); 
385  
386     return 0; 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM