zoukankan      html  css  js  c++  java
  • 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]
    "); 
    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; 
  • 相关阅读:
    如何快速修改替换对象中的某个属性?
    element组件 MessageBox不能显示确认和取消按钮,记录正确使用方法!
    记录一下vue transition 过渡各状态()
    记录一下vue slot
    vue路由传参query和params的区别(详解!)
    一段话让你理解vuex的工作模式!
    vue+axios访问本地json数据踩坑点
    怎么构建vue-cli项目
    IO模型
    epoll真正实现高并发服务器
  • 原文地址:https://www.cnblogs.com/vijozsoft/p/5603298.html
Copyright © 2011-2022 走看看