FFmpeg的常見結構體
除了幀結構,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的常見結構體