ffmpeg原始碼簡析(九)av_log(),AVClass,AVOption
1.av_log()
av_log()是FFmpeg中輸出日誌的函式。隨便開啟一個FFmpeg的原始碼檔案,就會發現其中遍佈著av_log()函式。一般情況下FFmpeg類庫的原始碼中是不允許使用printf()這種的函式的,所有的輸出一律使用av_log()。
av_log()的宣告位於libavutil\log.h,如下所示。
void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
這個函式的宣告有兩個地方比較特殊:
(1)函式最後一個引數是“…”。
在C語言中,在函式引數數量不確定的情況下使用“…”來代表引數。
v_log()每個欄位的含義如下:
avcl:指定一個包含AVClass的結構體。
level:log的級別
fmt:和printf()一樣。
由此可見,av_log()和printf()的不同主要在於前面多了兩個引數。其中第一個引數指定該log所屬的結構體,例如AVFormatContext、AVCodecContext等等。第二個引數指定log的級別,原始碼中定義瞭如下幾個級別。
/**
* Print no output.
*/
#define AV_LOG_QUIET -8
/**
* Something went really wrong and we will crash now.
*/
#define AV_LOG_PANIC 0
/**
* Something went wrong and recovery is not possible.
* For example, no header was found for a format which depends
* on headers or an illegal combination of parameters is used.
*/
#define AV_LOG_FATAL 8
/**
* Something went wrong and cannot losslessly be recovered.
* However, not all future data is affected.
*/
#define AV_LOG_ERROR 16
/**
* Something somehow does not look correct. This may or may not
* lead to problems. An example would be the use of '-vstrict -2'.
*/
#define AV_LOG_WARNING 24
/**
* Standard information.
*/
#define AV_LOG_INFO 32
/**
* Detailed information.
*/
#define AV_LOG_VERBOSE 40
/**
* Stuff which is only useful for libav* developers.
*/
#define AV_LOG_DEBUG 48
從定義中可以看出來,隨著嚴重程度逐漸下降,一共包含如下級別:AV_LOG_PANIC,AV_LOG_FATAL,AV_LOG_ERROR,AV_LOG_WARNING,AV_LOG_INFO,AV_LOG_VERBOSE,AV_LOG_DEBUG。每個級別定義的數值代表了嚴重程度,數值越小代表越嚴重。預設的級別是AV_LOG_INFO。此外,還有一個級別不輸出任何資訊,即AV_LOG_QUIET。
可以通過av_log_get_level()獲得當前Log的級別,通過另一個函式av_log_set_level()設定當前的Log級別。
int av_log_get_level(void)
{
return av_log_level;
}
void av_log_set_level(int level)
{
av_log_level = level;
}
2.AVClass
AVOption用於在FFmpeg中描述結構體中的成員變數。它最主要的作用可以概括為兩個字:“賦值”。一個AVOption結構體包含了變數名稱,簡短的幫助,取值等等資訊。
所有和AVOption有關的資料都儲存在AVClass結構體中。如果一個結構體(例如AVFormatContext或者AVCodecContext)想要支援AVOption的話,它的第一個成員變數必須是一個指向AVClass結構體的指標。該AVClass中的成員變數option必須指向一個AVOption型別的靜態陣列。
何為AVOption?
AVOption是用來設定FFmpeg中變數的值的結構體。可能說到這個作用有的人會奇怪:設定系統中變數的值,直接使用等於號“=”就可以,為什麼還要專門定義一個結構體呢?其實AVOption的特點就在於它賦值時候的靈活性。AVOption可以使用字串為任何型別的變數賦值。傳統意義上,如果變數型別為int,則需要使用整數來賦值;如果變數為double,則需要使用小數來賦值;如果變數型別為char *,才需要使用字串來賦值。而AVOption將這些賦值“歸一化”了,統一使用字串賦值。例如給int型變數qp設定值為20,通過AVOption需要傳遞進去一個內容為“20”的字串。
此外,AVOption中變數的名稱也使用字串來表示。結合上面提到的使用字串賦值的特性,我們可以發現使用AVOption之後,傳遞兩個字串(一個是變數的名稱,一個是變數的值)就可以改變系統中變數的值。
上文提到的這種方法的意義在哪裡?我個人感覺對於直接使用C語言進行開發的人來說,作用不是很明顯:完全可以使用等於號“=”就可以進行各種變數的賦值。但是對於從外部系統中呼叫FFmpeg的人來說,作用就很大了:從外部系統中只可以傳遞字串給內部系統。比如說對於直接呼叫ffmpeg.exe的人來說,他們是無法修改FFmpeg內部各個變數的數值的,這種情況下只能通過輸入“名稱”和“值”這樣的字串,通過AVOption改變FFmpeg內部變數的值。由此可見,使用AVOption可以使FFmpeg更加適應多種多樣的外部系統。
現在回到AVOption。其實除了可以對FFmpeg常用結構體AVFormatContext,AVCodecContext等進行賦值之外,還可以對它們的私有資料priv_data進行賦值。這個欄位裡通常儲存了各種編碼器特有的結構體。而這些結構體的定義在FFmpeg的SDK中是找不到的。例如使用libx264進行編碼的時候,通過AVCodecContext的priv_data欄位可以對X264Context結構體中的變數進行賦值,設定preset,profile等。使用libx265進行編碼的時候,通過AVCodecContext的priv_data欄位可以對libx265Context結構體中的變數進行賦值,設定preset,tune等。
何為AVClass?
AVClass最主要的作用就是給結構體(例如AVFormatContext等)增加AVOption功能的支援。換句話說AVClass就是AVOption和目標結構體之間的“橋樑”。AVClass要求必須宣告為目標結構體的第一個變數。
AVClass中有一個option陣列用於儲存目標結構體的所有的AVOption。
AVOption有關的API
AVOption常用的API可以分成兩類:用於設定引數的API和用於讀取引數的API。其中最有代表性的用於設定引數的API就是av_opt_set();而最有代表性的用於讀取引數的API就是av_opt_get()。除了記錄以上兩個函式之外,本文再記錄一個在FFmpeg的結構體初始化程式碼中最常用的用於設定預設值的函式av_opt_set_defaults()。
av_opt_set()
通過AVOption設定引數最常用的函式就是av_opt_set()了。該函式通過字串的方式(傳入的引數是變數名稱的字串和變數值的字串)設定一個AVOption的值。此外,還包含了它的一系列“兄弟”函式av_opt_set_XXX(),其中“XXX”代表了int,double這些資料型別。使用這些函式的時候,可以指定int,double這些型別的變數(而不是字串)作為輸入,設定相應的AVOption的值。
int av_opt_set (void *obj, const char *name, const char *val, int search_flags);
int av_opt_set_int (void *obj, const char *name, int64_t val, int search_flags);
int av_opt_set_double (void *obj, const char *name, double val, int search_flags);
int av_opt_set_q (void *obj, const char *name, AVRational val, int search_flags);
int av_opt_set_bin (void *obj, const char *name, const uint8_t *val, int size, int search_flags);
int av_opt_set_image_size(void *obj, const char *name, int w, int h, int search_flags);
int av_opt_set_pixel_fmt (void *obj, const char *name, enum AVPixelFormat fmt, int search_flags);
int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags);
int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int search_flags);
int av_opt_set_channel_layout(void *obj, const char *name, int64_t ch_layout, int search_flags);
有關av_opt_set_XXX()函式的定義不再詳細分析,在這裡詳細看一下av_opt_set()的原始碼。av_opt_set()的定義位於libavutil\opt.c,如下所示。
int av_opt_set(void *obj, const char *name, const char *val, int search_flags)
{
int ret = 0;
void *dst, *target_obj;
//查詢
const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);
if (!o || !target_obj)
return AVERROR_OPTION_NOT_FOUND;
if (!val && (o->type != AV_OPT_TYPE_STRING &&
o->type != AV_OPT_TYPE_PIXEL_FMT && o->type != AV_OPT_TYPE_SAMPLE_FMT &&
o->type != AV_OPT_TYPE_IMAGE_SIZE && o->type != AV_OPT_TYPE_VIDEO_RATE &&
o->type != AV_OPT_TYPE_DURATION && o->type != AV_OPT_TYPE_COLOR &&
o->type != AV_OPT_TYPE_CHANNEL_LAYOUT))
return AVERROR(EINVAL);
if (o->flags & AV_OPT_FLAG_READONLY)
return AVERROR(EINVAL);
//dst指向具體的變數
//注意:offset的作用
dst = ((uint8_t*)target_obj) + o->offset;
//根據AVOption不同的型別,呼叫不同的設定函式
switch (o->type) {
case AV_OPT_TYPE_STRING: return set_string(obj, o, val, dst);
case AV_OPT_TYPE_BINARY: return set_string_binary(obj, o, val, dst);
case AV_OPT_TYPE_FLAGS:
case AV_OPT_TYPE_INT:
case AV_OPT_TYPE_INT64:
case AV_OPT_TYPE_FLOAT:
case AV_OPT_TYPE_DOUBLE:
case AV_OPT_TYPE_RATIONAL: return set_string_number(obj, target_obj, o, val, dst);
case AV_OPT_TYPE_IMAGE_SIZE: return set_string_image_size(obj, o, val, dst);
case AV_OPT_TYPE_VIDEO_RATE: return set_string_video_rate(obj, o, val, dst);
case AV_OPT_TYPE_PIXEL_FMT: return set_string_pixel_fmt(obj, o, val, dst);
case AV_OPT_TYPE_SAMPLE_FMT: return set_string_sample_fmt(obj, o, val, dst);
case AV_OPT_TYPE_DURATION:
if (!val) {
*(int64_t *)dst = 0;
return 0;
} else {
if ((ret = av_parse_time(dst, val, 1)) < 0)
av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as duration\n", val);
return ret;
}
break;
case AV_OPT_TYPE_COLOR: return set_string_color(obj, o, val, dst);
case AV_OPT_TYPE_CHANNEL_LAYOUT:
if (!val || !strcmp(val, "none")) {
*(int64_t *)dst = 0;
} else {
#if FF_API_GET_CHANNEL_LAYOUT_COMPAT
int64_t cl = ff_get_channel_layout(val, 0);
#else
int64_t cl = av_get_channel_layout(val);
#endif
if (!cl) {
av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as channel layout\n", val);
ret = AVERROR(EINVAL);
}
*(int64_t *)dst = cl;
return ret;
}
break;
}
av_log(obj, AV_LOG_ERROR, "Invalid option type.\n");
return AVERROR(EINVAL);
}
從原始碼可以看出,av_opt_set()首先呼叫av_opt_find2()查詢AVOption。如果找到了,則根據AVOption的type,呼叫不同的函式(set_string(),set_string_number(),set_string_image_size()等等)將輸入的字串轉化為相應type的資料並對該AVOption進行賦值。如果沒有找到,則立即返回“沒有找到AVOption”的錯誤。
av_opt_find2() / av_opt_find()
av_opt_find2()本身也是一個API函式,用於查詢AVOption。它的宣告位於libavutil\opt.h中
av_opt_get()
av_opt_get()用於獲取一個AVOption變數的值。需要注意的是,不論是何種型別的變數,通過av_opt_get()取出來的值都是字串型別的。此外,還包含了它的一系列“兄弟”函式av_opt_get_XXX()(其中“XXX”代表了int,double這些資料型別)。通過這些“兄弟”函式可以直接取出int,double型別的數值。av_opt_get()的宣告如下所示。
int av_opt_get (void *obj, const char *name, int search_flags, uint8_t **out_val);
int av_opt_get_int (void *obj, const char *name, int search_flags, int64_t *out_val);
int av_opt_get_double (void *obj, const char *name, int search_flags, double *out_val);
int av_opt_get_q (void *obj, const char *name, int search_flags, AVRational *out_val);
int av_opt_get_image_size(void *obj, const char *name, int search_flags, int *w_out, int *h_out);
int av_opt_get_pixel_fmt (void *obj, const char *name, int search_flags, enum AVPixelFormat *out_fmt);
int av_opt_get_sample_fmt(void *obj, const char *name, int search_flags, enum AVSampleFormat *out_fmt);
int av_opt_get_video_rate(void *obj, const char *name, int search_flags, AVRational *out_val);
int av_opt_get_channel_layout(void *obj, const char *name, int search_flags, int64_t *ch_layout);
av_opt_get()的定義,如下所示。
int av_opt_get(void *obj, const char *name, int search_flags, uint8_t **out_val)
{
void *dst, *target_obj;
//查詢
const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);
uint8_t *bin, buf[128];
int len, i, ret;
int64_t i64;
if (!o || !target_obj || (o->offset<=0 && o->type != AV_OPT_TYPE_CONST))
return AVERROR_OPTION_NOT_FOUND;
//注意:offset的使用
dst = (uint8_t*)target_obj + o->offset;
//使用sprintf()轉換成字串,存入buf
buf[0] = 0;
switch (o->type) {
case AV_OPT_TYPE_FLAGS: ret = snprintf(buf, sizeof(buf), "0x%08X", *(int *)dst);break;
case AV_OPT_TYPE_INT: ret = snprintf(buf, sizeof(buf), "%d" , *(int *)dst);break;
case AV_OPT_TYPE_INT64: ret = snprintf(buf, sizeof(buf), "%"PRId64, *(int64_t*)dst);break;
case AV_OPT_TYPE_FLOAT: ret = snprintf(buf, sizeof(buf), "%f" , *(float *)dst);break;
case AV_OPT_TYPE_DOUBLE: ret = snprintf(buf, sizeof(buf), "%f" , *(double *)dst);break;
case AV_OPT_TYPE_VIDEO_RATE:
case AV_OPT_TYPE_RATIONAL: ret = snprintf(buf, sizeof(buf), "%d/%d", ((AVRational*)dst)->num, ((AVRational*)dst)->den);break;
case AV_OPT_TYPE_CONST: ret = snprintf(buf, sizeof(buf), "%f" , o->default_val.dbl);break;
case AV_OPT_TYPE_STRING:
if (*(uint8_t**)dst)
*out_val = av_strdup(*(uint8_t**)dst);
else
*out_val = av_strdup("");
return 0;
case AV_OPT_TYPE_BINARY:
len = *(int*)(((uint8_t *)dst) + sizeof(uint8_t *));
if ((uint64_t)len*2 + 1 > INT_MAX)
return AVERROR(EINVAL);
if (!(*out_val = av_malloc(len*2 + 1)))
return AVERROR(ENOMEM);
if (!len) {
*out_val[0] = '\0';
return 0;
}
bin = *(uint8_t**)dst;
for (i = 0; i < len; i++)
snprintf(*out_val + i*2, 3, "%02X", bin[i]);
return 0;
case AV_OPT_TYPE_IMAGE_SIZE:
//解析度
ret = snprintf(buf, sizeof(buf), "%dx%d", ((int *)dst)[0], ((int *)dst)[1]);
break;
case AV_OPT_TYPE_PIXEL_FMT:
//畫素格式
ret = snprintf(buf, sizeof(buf), "%s", (char *)av_x_if_null(av_get_pix_fmt_name(*(enum AVPixelFormat *)dst), "none"));
break;
case AV_OPT_TYPE_SAMPLE_FMT:
//取樣格式
ret = snprintf(buf, sizeof(buf), "%s", (char *)av_x_if_null(av_get_sample_fmt_name(*(enum AVSampleFormat *)dst), "none"));
break;
case AV_OPT_TYPE_DURATION:
//時長
i64 = *(int64_t *)dst;
ret = snprintf(buf, sizeof(buf), "%"PRIi64":%02d:%02d.%06d",
i64 / 3600000000, (int)((i64 / 60000000) % 60),
(int)((i64 / 1000000) % 60), (int)(i64 % 1000000));
break;
case AV_OPT_TYPE_COLOR:
ret = snprintf(buf, sizeof(buf), "0x%02x%02x%02x%02x",
(int)((uint8_t *)dst)[0], (int)((uint8_t *)dst)[1],
(int)((uint8_t *)dst)[2], (int)((uint8_t *)dst)[3]);
break;
case AV_OPT_TYPE_CHANNEL_LAYOUT:
i64 = *(int64_t *)dst;
ret = snprintf(buf, sizeof(buf), "0x%"PRIx64, i64);
break;
default:
return AVERROR(EINVAL);
}
if (ret >= sizeof(buf))
return AVERROR(EINVAL);
//拷貝
*out_val = av_strdup(buf);
return 0;
}
從av_opt_get()的定義可以看出,該函式首先通過av_opt_find2()查相應的AVOption,然後取出該變數的值,最後通過snprintf()將變數的值轉化為字串(各種各樣型別的變數都這樣處理)並且輸出出來。
至此FFmpeg中和AVOption相關的原始碼基本上就分析完畢了。