FFmpeg學習1:視頻解碼


在視頻解碼前,先了解以下幾個基本的概念:

  • 編解碼器(CODEC):能夠進行視頻和音頻壓縮(CO)與解壓縮(DEC),是視頻編解碼的核心部分。
  • 容器/多媒體文件(Container/File):沒有了解視頻的編解碼之前,總是錯誤的認為平常下載的電影的文件的后綴(avi,mkv,rmvb等)就是視頻的編碼方式。事實上,剛才提到的幾種文件的后綴
    並不是視頻的編碼方式,只是其封裝的方式。一個視頻文件通常有視頻數據、音頻數據以及字幕等,封裝的格式決定這些數據在文件中是如何的存放的,封裝在一起音頻、視頻等數據組成的多媒體文件,也可以叫做容器(其中包含了視音頻數據)。所以,只看多媒體文件的后綴名是難以知道視音頻的編碼方式的。
  • 流數據 Stream,例如視頻流(Video Stream),音頻流(Audio Stream)。流中的數據元素被稱為幀Frame

FFmpeg視頻解碼過程

通常來說,FFmpeg的視頻解碼過程有以下幾個步驟:

  1. 注冊所支持的所有的文件(容器)格式及其對應的CODEC av_register_all()
  2. 打開文件 avformat_open_input()
  3. 從文件中提取流信息 avformat_find_stream_info()
  4. 在多個數據流中找到視頻流 video stream(類型為 MEDIA_TYPE_VIDEO
  5. 查找video stream 相對應的解碼器 avcodec_find_decoder
  6. 打開解碼器 avcodec_open2()
  7. 為解碼幀分配內存 av_frame_alloc()
  8. 從流中讀取讀取數據到Packet中 av_read_frame()
  9. 對video 幀進行解碼,調用 avcodec_decode_video2()

解碼過程的具體說明

1. 注冊

av_register_all該函數注冊支持的所有的文件格式(容器)及其對應的CODEC,只需要調用一次,故一般放在main函數中。也可以注冊某個特定的容器格式,但通常來說不需要這么做。

2. 打開文件

avformat_open_input該函數讀取文件的頭信息,並將其信息保存到AVFormatContext結構體中。其調用如下

AVFormatContext* pFormatCtx = nullptr;  
avformat_open_input(&pFormatCtx, filenName, nullptr, nullptr)  

第一個參數是AVFormatContext結構體的指針,第二個參數為文件路徑;第三個參數用來設定輸入文件的格式,如果設為null,將自動檢測文件格式;第四個參數用來填充AVFormatContext一些字段以及Demuxer的private選項。
AVFormatContext包含有較多的碼流信息參數,通常由avformat_open_input創建並填充關鍵字段。

3. 獲取必要的CODEC參數

avformat_open_input通過解析多媒體文件或流的頭信息及其他的輔助數據,能夠獲取到足夠多的關於文件、流和CODEC的信息,並將這些信息填充到AVFormatContext結構體中。但任何一種多媒體格式(容器)提供的信息都是有限的,而且不同的多媒體制作軟件對頭信息的設置也不盡相同,在制作多媒體文件的時候難免會引入一些錯誤。也就是說,僅僅通過avformat_open_input並不能保證能夠獲取所需要的信息,所以一般要使用

avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)

avformat_find_stream_info主要用來獲取必要的CODEC參數,設置到ic->streams[i]->codec
在解碼的過程中,首先要獲取到各個stream所對應的CODEC類型和id,CODEC的類型和id是兩個枚舉值,其定義如下:

enum AVMediaType { 
    AVMEDIA_TYPE_UNKNOWN = -1,     
    AVMEDIA_TYPE_VIDEO,     
    AVMEDIA_TYPE_AUDIO,     
    AVMEDIA_TYPE_DATA, 
    AVMEDIA_TYPE_SUBTITLE,    
    AVMEDIA_TYPE_ATTACHMENT,     
    AVMEDIA_TYPE_NB
 }; 

enum CodecID { 
    CODEC_ID_NONE,     /* video codecs */ 
    CODEC_ID_MPEG1VIDEO, 
    CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding     
    CODEC_ID_MPEG2VIDEO_XVMC,     
    CODEC_ID_H261,     
    CODEC_ID_H263, 
...
}

通常,如果多媒體文件具有完整而正確的頭信息,通過avformat_open_input即可用獲得這兩個參數。

4. 打開解碼器

經過上面的步驟,已經將文件格式信息讀取到了AVFormatContext中,要打開流數據相應的CODEC需要經過下面幾個步驟

  • 找到視頻流 video stream
    一個多媒體文件包含有多個原始流,例如 movie.mkv這個多媒體文件可能包含下面的流數據
  • 原始流 1 h.264 video
  • 原始流 2 aac audio for Chinese
  • 原始流 3 aac audio for English
  • 原始流 4 Chinese Subtitle
  • 原始流 5 English Subtitle

要解碼視頻,首先要在AVFormatContext包含的多個流中找到CODEC類型為AVMEDIA_TYPE_VIDEO,代碼如下:

    //查找視頻流 video stream
    int videoStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1)
        return -1; // 沒有找到視頻流video stream  

結構體AVFormatContext中的streams字段是一個AVStream指針的數組,包含了文件所有流的描述,上述上述代碼在該數組中查找CODEC類型為
AVMEDIA_TYPE_VIDEO的流的下標。

  • 根據codec_id找到相應的CODEC,並打開
    結構體AVCodecContext描述了CODEC上下文,包含了眾多CODEC所需要的參數信息。
AVCodecContext* pCodecCtxOrg = nullptr; 
AVCodec* pCodec = nullptr;
pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
// 找到video stream的 decoder
pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id); 
 // open codec
 if (avcodec_open2(pCodecCtxOrg , pCodec, nullptr) < 0)
     return -1; // Could open codec  

上述代碼,首先通過codec_id找到相應的CODEC,然后調用avcodec_open2打開相應的CODEC。

5. 讀取數據幀並解碼

已經有了相應的解碼器,下面的工作就是將數據從流中讀出,並解碼為沒有壓縮的原始數據

AVPacket packet; 
while (av_read_frame(pFormatCtx, &packet) >= 0)
{
        if (packet.stream_index == videoStream)
        {
            int frameFinished = 0;
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
            if (frameFinished)
            {
                doSomething();
            }
        }
        
    } 

上述代碼調用av_read_frame將數據從流中讀取數據到packet中,並調用avcodec_decode_video2對讀取的數據進行解碼。

6. 關閉

需要關閉avformat_open_input打開的輸入流,avcodec_open2打開的CODEC

    avcodec_close(pCodecCtxOrg);
    avformat_close_input(&pFormatCtx);  

補充

在配置好FFmpeg的開發環境后,在C++中使用FFmpeg的庫函數,會出現解析不出函數的名稱鏈接錯誤,這是由於FFmpeg庫是C語言實現,要在C++調用C函數需要 extern "C"的聲明。

extern "C"
{
    # include <libavcodec\avcodec.h>
    # include <libavformat\avformat.h>
    # include <libswscale\swscale.h>
}

哎,博客園的Markdown用着好不方便啊,不知道怎么畫流程圖....

代碼 FFmpeg0.cpp


免責聲明!

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



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