1. 程式人生 > >ffmpeg原始碼簡析(九)av_log(),AVClass,AVOption

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相關的原始碼基本上就分析完畢了。