Ffmpeg框架結構解讀

1、    FFMEPG結構說明
    1.1》介紹
    ffmpeg(Fast Forward Moving Pictures Experts Group)是音視頻的分離,轉換,編碼解碼及流媒體的完全解決方案,其中最重要的就是libavcodec庫,是一個集錄制、轉換、音/視頻編碼解碼功能爲一體的完整的開源解決方案。ffmpeg的開發是基於Linux操作系統,但是可以在大多數操作系統中編譯和使用。FFmpeg支持MPEG、DivX、MPEG4、AC3、DV、FLV等40多種編碼,AVI、MPEG、OGG、Matroska、ASF等90多種解碼. TCPMP, VLC, MPlayer等開源播放器都用到了FFmpeg。
ffmpeg主目錄下主要有libavcodec、libavformat和libavutil等子目錄。其中
    libavcodec用於存放各個encode/decode模塊,CODEC其實是Coder/Decoder的縮寫,也就是編碼解碼器;用於各種類型聲音/圖像編解碼
    libavformat用於存放muxer/demuxer模塊,對音頻視頻格式的解析;用於各種音視頻封裝格式的生成和解析,包括獲取解碼所需信息以生成解碼上下文結構和讀取音視頻幀等功能;
其中庫 libavcodec,libavformat用於對媒體文件進行處理,如格式的轉換;
    libavutil集項工具,包含一些公共的工具函數;用於存放內存操作等輔助性模塊,是一個通用的小型函數庫,該庫中實現了CRC校驗碼的產生,128位整數數學,最大公約數,整數開方,整數取對數,內存分配,大端小端格式的轉換等功能
    libavdevice:對輸出輸入設備的支持;
    libpostproc:用於後期效果處理;
    libswscale:用於視頻場景比例縮放、色彩映射轉換;
    ffmpeg:該項目提供的一個工具,可用於格式轉換、解碼或電視卡即時編碼等;
    fsever:一個 HTTP 多媒體即時廣播串流服務器;
    ffplay:是一個簡單的播放器,使用ffmpeg 庫解析和解碼,通過SDL顯示;
    ffmpeg軟件包經編譯過後將生成三個可執行文件,ffmpeg,ffserver,ffplay。其中ffmpeg用於對媒體文件進行處理,ffserver是一個http的流媒體服務器,ffplay是一個基於SDL的簡單播放器。
   
說明:
    muxer/demuxer和encoder/decoder的區別:
    最大的差別是muxer 和demuxer分別是不同的結構AVOutputFormat與AVInputFormat;
    而encoder和decoder都是用的AVCodec 結構。
    muxer/demuxer是分別保存在全局變量AVOutputFormat *first_oformat與AVInputFormat *first_iformat中的。encoder/decoder都是保存在全局變量AVCodec *first_avcodec中的。
    muxer/demuxer和encoder/decoder的相同之處:
    都是在main()開始的av_register_all()函數內初始化的
    都是以鏈表的形式保存在全局變量中的
    都用函數指針的方式作爲開放的公共接口

    1.2》下載與編譯
    官方下載網址http://ffmpeg.org/download.html
    編譯./configure
        #make
        #make install
安裝到/usr/local/bin、/usr/local/include(包含各個頭文件)、/usr/local/lib(生成.a文件),編譯完畢後
    A》執行./ffmpeg,結果如下:
FFmpeg version SVN-r17579, Copyright (c) 2000-2009 Fabrice Bellard, et al.
  configuration:
  libavutil     49.15. 0 / 49.15. 0
  libavcodec    52.19. 0 / 52.19. 0
  libavformat   52.30. 0 / 52.30. 0
  libavdevice   52. 1. 0 / 52. 1. 0
  built on Mar 25 2011 17:30:17, gcc: 4.3.4
At least one output file must be specified
    B》執行./ffplay,結果如下:
FFplay version SVN-r17579, Copyright (c) 2003-2009 Fabrice Bellard, et al.
  configuration:
  libavutil     49.15. 0 / 49.15. 0
  libavcodec    52.19. 0 / 52.19. 0
  libavformat   52.30. 0 / 52.30. 0
  libavdevice   52. 1. 0 / 52. 1. 0
  built on Mar 25 2011 17:30:17, gcc: 4.3.4
An input file must be specified
    C》執行./ffserver,結果如下:
FFserver version SVN-r17579, Copyright (c) 2000-2009 Fabrice Bellard, et al.
  configuration:
  libavutil     49.15. 0 / 49.15. 0
  libavcodec    52.19. 0 / 52.19. 0
  libavformat   52.30. 0 / 52.30. 0
  libavdevice   52. 1. 0 / 52. 1. 0
  built on Mar 25 2011 17:30:17, gcc: 4.3.4
/etc/ffserver.conf: No such file or directory
Incorrect config file - exiting.
說明:如果缺少fserver.conf文件,需在/etc/中增加ffserver.conf文件。

2、    Ffmpeg編碼、解碼
    2.1》主要流程如下:
    輸入流初始化input streams initializing
    輸出流初始化output streams initializing
    編碼器和解碼器初始化encoders and decoders initializing
    如有需要的情況下,設置來自輸入文件的Meta數據信息set meta data information from input file if required.
    寫輸出文件頭文件write output files header
    循環處理每個數據單元loop of handling each frame(frame是指Stream中的一個數據單元)
    從輸入文件中讀取數據單元read frame from input file:
    解碼數據單元內數據decode frame data
    編碼數據單元內數據encode new frame data
    寫新的數據單元到輸出文件中write new frame to output file
    寫輸出文件的尾文件write output files trailer
    關閉每個編碼器和解碼器close each encoder and decoder
說明:
    av_encode函數是FFMpeg中最重要的函數,編碼/解碼和輸出等大部分功能都在此函數完成。ffmpeg.c中av_encode(AVFormatContext **output_files,
                     int nb_output_files,
                     AVFormatContext **input_files,
                     int nb_input_files,
                     AVStreamMap *stream_maps, int nb_stream_maps)

    AVFormatContext是FFMpeg格式轉換過程中實現輸入和輸出功能、保存相關數據的主要結構。每一個輸入和輸出文件,都在如下定義的指針數組全局變量中有對應的實體。
    static AVFormatContext *output_files[MAX_FILES];
    static AVFormatContext *input_files[MAX_FILES];
    對於輸入和輸出,因爲共用的是同一個結構體,所以需要分別對該結構中如下定義的iformat或oformat成員賦值。
    struct AVInputFormat *iformat;
    struct AVOutputFormat *oformat;
    對一個AVFormatContext來說,這二個成員不能同時有值,即一個AVFormatContext不能同時含有demuxer和muxer。在main( )函數開頭的parse_options( )函數中找到了匹配的muxer和demuxer之後,根據傳入的argv參數,初始化每個輸入和輸出的AVFormatContext結構,並保存在相應的output_files和input_files指針數組中。在av_encode( )函數中,output_files和input_files是作爲函數參數傳入後,在其他地方就沒有用到了。
 
    AVCodecContext保存AVCodec指針和與codec相關數據,如video的width、height,audio的sample rate等。AVCodecContext中的codec_type,codec_id二個變量對於encoder/decoder的匹配來說,最爲重要。
    enum CodecType codec_type;     /* see CODEC_TYPE_xxx */
    enum CodecID codec_id;         /* see CODEC_ID_xxx */
    codec_type保存的是CODEC_TYPE_VIDEO,CODEC_TYPE_AUDIO等媒體類型,codec_id保存的是CODEC_ID_FLV1,CODEC_ID_VP6F等編碼方式。
 
    AVStream結構保存與數據流相關的編解碼器,數據段等信息。比較重要的有如下二個成員:
    AVCodecContext *codec; /**< codec context */
    void *priv_data;
    其中codec指針保存的就是encoder或decoder結構。priv_data指針保存的是和具
體編解碼流相關的數據。

    AVInputStream/ AVOutputStream根據輸入和輸出流的不同,前述的AVStream結構都是封裝在AVInputStream和AVOutputStream結構中,在av_encode( )函數中使用。AVInputStream中還保存的有與時間有關的信息。AVOutputStream中還保存有與音視頻同步等相關的信息。   

    2.2》視頻文件解碼流程
    A》初始化 libavcodec庫,並註冊所有容器格式(format)、編解碼器CODEC、,解析器(parsers)以及碼流過濾器(bitstream filters),打開一個文件時,自動選擇相應的文件格式和編碼器:
    avcodec_register_all();
    avdevice_register_all();
    av_register_all();
    avformat_alloc_context();分配播放avformat的上下文,分配輸出媒體內容。

    B》打開文件: av_open_input_file()
    int av_open_input_file(AVFormatContext **ic_ptr, const char *filename,
                       AVInputFormat *fmt,
                       int buf_size,
                       AVFormatParameters *ap)
    {
           ......
        if (!fmt) {
            /* guess format if no file can be opened */
            fmt = av_probe_input_format(pd, 0);
        }
           ......
        err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);
           ......
    }
    主要是兩件事情:
    偵測容器文件格式(是在AVFormatContext定義中);
    從容器文件獲取Stream的信息,就是調用特定文件的demuxer以分離Stream的過程,描述如下:
av_open_input_file
    av_probe_input_format2()從first_iformat中遍歷註冊的所有demuxer以調用相應的probe函數
    av_open_input_stream()調用指定demuxer的read_header函數以獲取相關流的信息ic->iformat->read_header

    C》從文件中提取流信息: av_find_stream_info()用有效的信息把 AVFormatContext 的流域(streams field)填滿。對於音頻/視頻每個Packet包含完整的或多個複合的frame。從文件中讀取packet,從Packet中解碼相應的frame。
    av_find_stream_info(AVFormatContext *ic)主要是兩部分:
    一部分是使用av_open_input_file()解複用(demuxer)
    然後是使用av_read_frame(AVFormatContext *s, AVPacket *pkt)和 avcodec_decode_video() 解碼(decode)

    D》遍歷所有的流,查找其中種類爲CODEC_TYPE_VIDEO,描敘如下:
int i;
AVCodecContext *pCodecCtx;

// Find the first video stream
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
  if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
    videoStream=i;
    break;
  }
if(videoStream==-1)
  return -1; // Didn't find a video stream

// Get a pointer to the codec context for the video stream
pCodecCtx=pFormatCtx->streams[videoStream]->codec;

    E》查找對應的解碼器: avcodec_find_decoder();若成功後,打開解碼器 avcodec_open()用給定的 AVCodec來初始化AVCodecContext,描敘如下:
AVCodec *pCodec;

// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
  return -1; // Codec not found
}
// Open codec
if(avcodec_open(pCodecCtx, pCodec)<0)
  return -1; // Could not open codec

    F》爲解碼幀分配內存: avcodec_alloc_frame(),用於存在幀數據

    G》不停地從解碼流中提取中幀數據: av_read_frame()
int frameFinished;
AVPacket packet;

i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
  // Is this a packet from the video stream?
  if(packet.stream_index==videoStream) {
    // Decode video frame
    avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
                         packet.data, packet.size);
   
    // Did we get a video frame?
    if(frameFinished) {
    // Convert the image from its native format to RGB32
        img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB32,
            (AVPicture*)pFrame, pCodecCtx->pix_fmt,
            pCodecCtx->width, pCodecCtx->height);
   
        // Save the frame to disk
           ......
    }
  }
   
  // Free the packet that was allocated by av_read_frame
  av_free_packet(&packet);
}
   
    H》判斷幀的類型,對於視頻幀調用指定Codec的解碼函數: avcodec_decode_video()
    I》解碼完後,釋放解碼器: avcodec_close()
    J》關閉輸入文件:av_close_input_file()

3、    代碼標記Log
    根據2.2》項中所描述的視頻解碼流程,作Log標記(用printf()方法輸出)、跟蹤視頻解碼過程。從ffmpeg自帶的ffplay播放器着手,跟蹤ffplay.c的主函數main()中涉及的調用函數。
/* Called from the main */
int main(int argc, char **argv)
{
    /* register all codecs, demux and protocols */
    avcodec_register_all();
    avdevice_register_all();
    av_register_all();
    ......
    avformat_opts = avformat_alloc_context();
    sws_opts = sws_getContext(16,16,0, 16,16,0, sws_flags, NULL,NULL,NULL);
    show_banner();
    parse_options(argc, argv, options, opt_input_file);
    ......
    cur_stream = stream_open(input_filename, file_iformat);
event_loop();

    /* never returns */
    return 0;
}
    跟蹤結果如下:
root@localhost /work/ffmpeg>ffplay /work/test/avi/output.avi
beginning avcodec_register_all... _by jay remarked
beginning avdevice_register_all... _by jay remarked
beginning av_register_all... _by jay remarked
registering MuxDemux MP3... _by jay remarked
returning av_register_all's initialized

avctx_opts[0]
avctx_opts[1]
avctx_opts[2]
avctx_opts[3]
avctx_opts[4]
returning avformat_alloc_context value..._by jay remarked
returning sws_getContex value..._by jay remarked

showing version banner..._by jay remarked
FFplay version SVN-r17579 _by Jay remarked, Copyright (c) 2003-2009 Fabrice Bellard, et al.
  configuration:
  libavutil     49.15. 0 / 49.15. 0
  libavcodec    52.19. 0 / 52.19. 0
  libavformat   52.30. 0 / 52.30. 0
  libavdevice   52. 1. 0 / 52. 1. 0
  built on Apr  1 2011 09:29:06, gcc: 4.3.4

beginning parse_options... _by jay remarked
returning optindex=[2]
beginning av_init_packet... _by jay remarked
beginning cur_stream... _by jay remarked
returning av_open_input_file's pd->filename=[T]
[mp3 @ 0x9b26d20]mdb:432, lastbuf:0 skipping granule 0
    Last message repeated 1 times
[mp3 @ 0x9b26d20]mdb:432, lastbuf:0 skipping granule 1
    Last message repeated 1 times
[mp3 @ 0x9b26d20]mdb:460, lastbuf:216 skipping granule 0
    Last message repeated 1 times
[mp3 @ 0x9b26d20]mdb:460, lastbuf:216 skipping granule 1
returning av_close_input_file successful

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章