本文简单记录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] "); 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;