1. 程式人生 > >FFmpeg的常見結構體

FFmpeg的常見結構體

FFmpeg 結構體

小程之前介紹過FFmpeg的幀的結構(AVPacket跟AVFrame),幀會在一些流程中使用到。

除了幀結構,FFmpeg還有其它一些結構會在流程中使用到。

FFmpeg還有哪些常見的結構呢?先來看一下這個截圖:
技術分享圖片

這張圖中的主角,是AVFormatContext。AVFormatContext,是FFmpeg的基本結構之一,對應於封裝格式(或容器格式)。

圍繞FFmpeg的“格式場景”,本文介紹FFmpeg常見的數據結構

按照上圖,小程依次介紹圖中的幾個結構體。

(一)AVCodec

AVCodec是FFmpeg設計上的一個結構體,用來保存編×××的信息,也就是說,AVCodec是編碼器或×××。

小程還是以調試的辦法,具體看一下AVCodec變量中的內容。

(1)演示代碼

演示代碼的目錄結構是這樣的:
技術分享圖片

其中的FFmpeg靜態庫是事先編譯好的(這裏是macos版本,因為小程用了mac電腦),編譯的辦法可以參考之前的文章,讀者可以關註“廣州小程”微信公眾號,並在相應的菜單項中找到文章。

moments.mp4 是試用的視頻文件(mp4封裝格式)。對於封裝格式,在公眾號的“音視頻”之“基礎概念與流程”中也可以找到,小程有專門介紹過媒體格式。

makefile是編譯腳本,用來編譯演示代碼,當然也可以直接用gcc來編譯。

show_avcodec.c就是演示代碼了,內容如下:

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"

void show_avcodec(const char* filepath) {
    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);
    AVFormatContext* formatContext = avformat_alloc_context();
    int status = 0;
    int success = 0;
    int videostreamidx = -1;
    AVCodecContext* codecContext = NULL;
    status = avformat_open_input(&formatContext, filepath, NULL, NULL);
    if (status == 0) {
        status = avformat_find_stream_info(formatContext, NULL);
        if (status >= 0) {
            for (int i = 0; i < formatContext->nb_streams; i ++) {
                if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
                    videostreamidx = i;
                    break;
                }
            }
            if (videostreamidx > -1) {
                codecContext = formatContext->streams[videostreamidx]->codec;
                AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
                if (codec) {
                    status = avcodec_open2(codecContext, codec, NULL);
                    if (status == 0) {
                        success = 1;
                    }
                }
            }
        }
        else {
            av_log(NULL, AV_LOG_DEBUG, "avformat_find_stream_info error\n");
        }
        avformat_close_input(&formatContext);
    }
    avformat_free_context(formatContext);
}

int main(int argc, char *argv[])
{
    show_avcodec("moments.mp4");
    return 0;
}

(2)編譯與調試

makefile的內容:

exe=showavcodec
srcs=show_avcodec.c 
$(exe):$(srcs)
    gcc -o $(exe) $(srcs) -Iffmpeg/include/ -Lffmpeg -lffmpeg -liconv -lz -g
clean:
    rm -f $(exe) *.o

直接執行make來編譯,編譯後會生成符號表文件即showavcodec.dSYM。

這裏只是簡單看一下AVCodec的內容,用gdb來調試即可:

gdb showavcodec
b 25
r

在斷點的地方,看一下AVCodec變量中的值:
技術分享圖片

(3)AVCodec結構內容

AVCodec是編×××的結構體,在libavcodec/avcodec.h中定義。

在這個示例中,AVCodec是一個×××。

AVCodec結構中的一些變量,從它的名字或者FFmpeg詳細的註釋中,可以知道是什麽含義。

比如name是編解碼的名稱,而long_name就是長的名稱,等等。

在設計上,AVCodec是編×××的抽象,所以,編×××是有相應的具體實現的。

事實上,每一個編×××都有具體的實現。

比如h264的×××(libavcodec/h264.c):
技術分享圖片

比如mp3lame的編碼器(libavcodec/libmp3lame.c)
技術分享圖片

FFmpeg會使用上這些具體的編×××的實現,以完成編解碼等功能。

(二)AVCodecContext

AVCodecContext可以簡單理解為AVCodec的使用場景,而實際上AVCodecContext包括的內容,除了關聯AVCodec,還有其它信息。

跟調試AVCodec變量一樣,直接使用上面的演示代碼就可以調試AVCodecContext,部分代碼如下 :

if (videostreamidx > -1) {
    codecContext = formatContext->streams[videostreamidx]->codec;
    AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
    if (codec) {
        status = avcodec_open2(codecContext, codec, NULL);
        if (status == 0) {
            success = 1;
        }
    }
}

同樣用gdb來調試就可以了,在拿到codecContext後下斷點,可以看到AVCodecContext的部分內容如下:
技術分享圖片

其中有一些變量應該引起註意,比如:

width/height 視頻的寬與高
codec_id 編×××的id,根據它可以找到對應的編×××
extradata 對於h264編碼的視頻,保存了pps與sps的參數信息
profile 視頻編碼復雜等級
sample_rate 音頻的采樣率
channels 音頻的聲道數
sample_fmt 音頻的采樣格式

跟AVCodec一樣,AVCodecContext結構體在libavcodec/avcodec.h中定義。

(三)AVStream

上面介紹了AVCodec、AVCodecContext,現在介紹AVStream。

這三者的大概關系是這樣的:

技術分享圖片

AVStream對應音頻流、視頻流、字幕等媒體流。

FFmepg以流的概念來封裝不同的媒體。

調試AVStream的示例代碼與編譯,可以查看上面AVCodec調試的介紹。大概如下:
技術分享圖片

下斷點,可以看到AVStream中的內容,比如:
技術分享圖片

AVStream中的一些變量:

index,流的索引
codec,流對應的avcodeccontext
time_base,時間基準(比例)
duration,流的時長
metadata,流的元信息
nb_frames,流中幀的數量

AVStream結構,在libavformat/avformat.h中定義。

(四)AVFormatContext

AVFormatContext是主角,表示為格式的場景,對應於封裝格式(或容器格式)。

同樣,使用之前的示例代碼,在avformat_open_input函數後下斷點:
技術分享圖片

可以查看avformatcontext結構中的變量值:
技術分享圖片

AVFormatContext中的metadata記錄了多媒體文件的一些信息(比如作者、專輯之類),可以這樣取得裏面的信息:

if (formatCtx->metadata) {
    AVDictionaryEntry *item = NULL;
    while((item = av_dict_get(formatCtx->metadata, "", item, AV_DICT_IGNORE_SUFFIX))){
        printf("key:%s value:%s \n", item->key, item->value);
    }

    // 或者這樣:
    AVDictionaryEntry *tag = NULL;
    tag = av_dict_get(formatCtx->metadata, "artist", NULL, 0);
    if (tag) {
        std::string artist = (char*)tag->value;
    }
}

AVFormatContext的一些變量說明:

iformat/oformat,輸入/輸出格式,在解復用(解封裝)或復用(封裝)時使用。
pb,輸入或輸出場景,提供數據操作接口(比如讀寫、seek等)。
nb_streams,流的個數(以流的方式來復用)。
streams,流的數組。
filename,文件名。
start_time,流的起始時間,以AV_TIME_BASE為單位(除以AV_TIME_BASE轉為秒)。
duration,流的時長,以AV_TIME_BASE為單位。
bit_rate,比特率。
probesize,在檢測容器格式時,最大的探測大小,在avformat_open_input之前設置(或不設置使用默認值)。
max_analyze_duration,最大的分析數據的時長,在檢測編碼格式時使用,在avformat_find_stream_info前設置(或不設置),越大越耗時。
metadata,元信息。

AVFormatContext結構,在libavformat/avformat.h中定義。

(五)AVIOContext

AVIOContext是輸入輸出信息的結構體,它在FFmpeg結構體系中的位置是這樣的:
技術分享圖片

可以看到,AVIOContext是AVFormatContext的一個成員,叫作pb。

pb是提供數據的變量,既用於讀(解碼)也用於寫(編碼)。

(1)解碼時

在解碼時,pb提供解碼的原始數據,一般在調用avio_alloc_context創建aviocontext時,指定read與seek函數(自定義的實現,提供讀數據、跳轉位置的功能),然後把創建的aviocontext(即pb)直接設置給AVFormatContext。比如這樣:

pb = avio_alloc_context(readBuf, readBufLen, 0, this, myReadFunc, NULL, mySeekFunc);
mFormatCtx->pb = pb;

或者,在解碼時,這樣使用aviocontext:

mIOContext.read_packet = myReadFunc;
mIOContext.seek = mySeekFunc;
const int MAX_PRO_SIZE = 321024;
unsigned char
probuf = (unsigned char*)av_malloc(MAX_PRO_SIZE);
mIOContext.buffer = probuf;
mIOContext.buf_ptr = probuf;
mIOContext.buffer_size = MAX_PRO_SIZE;
mIOContext.buf_end = probuf + MAX_PRO_SIZE;
mIOContext.max_packet_size = MAX_PRO_SIZE;
formatContext->pb = &mIOContext;

其中,函數myReadFunc與mySeekFunc,按照結構體AVIOContext中的格式說明(參照頭文件說明)來定義即可。

解碼時,pb的設置,要在avformat_open_input調用前完成。

(2)編碼時

在編碼寫文件時,pb提供寫到文件的數據(編碼後的數據,對應AVPacket),比如可以直接用avio_open2來打開pb:

if (!(mFormatContext->flags & AVFMT_NOFILE)) {
    err = avio_open2(&mFormatContext->pb, url, AVIO_FLAG_WRITE, &mFormatContext->interrupt_callback, NULL);
    // ...
}

寫文件時,可以用av_write_frame來寫入一個packet,也可以用avio_write往pb中寫入數據。

編碼寫文件時,pb的設置,要在avformat_write_header調用前完成。

(3)AVIOContext的變量

這裏只列表一部分:

buffer,AVIOContext緩存數據的buffer,起始地址。
buffer_size,buffer的大小。
buf_ptr,操作buffer的當前位置。
buf_end,數據的結束位置,有可能未到buffer的未端。
opaque,指向URLContext,提供讀、寫、seek等接口,可以讓它為空,從而使用自定義的接口。
read_packet/write_packet/seek,讀寫與seek的接口,可以在avio_alloc_context時指定,從而自定義。

AVIOContext結構體在libavformat/avio.h中定義。

至此,FFmpeg常見的幾個結構體就介紹完畢了。


總結一下,本文介紹了FFmpeg的常見結構體,包括AVFormatContext、AVIOContext、AVStream、AVCodecContext、AVCodec等,並且以調試的方式查看了結構體的一些變量值。

FFmpeg的常見結構體