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

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