1. 程式人生 > 實用技巧 >ffmpeg日誌,結構體管理

ffmpeg日誌,結構體管理

FFmpeg原始碼的其他部分目錄:

8.日誌輸出系統(av_log()等)

9.結構體成員管理系統AVOption

FFmpeg原始碼的其他部分

8.日誌輸出系統(av_log()等)

  av_log()是FFmpeg中輸出日誌的函式。隨便開啟一個FFmpeg的原始碼檔案,就會發現其中遍佈著av_log()函式。一般情況下FFmpeg類庫的原始碼中是不允許使用printf()這種的函式的,所有的輸出一律使用av_log()。

  函式的宣告為voidav_log(void *avcl,int level,constchar *fmt, ...)av_printf_format(3,4);

  函式最後一個引數是“…”。

在C語言中,在函式引數數量不確定的情況下使用“…”來代表引數。例如printf()的原型定義如:  intprintf(constchar*, ...);

  它的聲明後面有一個av_printf_format(3, 4)。有關這個地方的左右還沒有深入研究,網上資料中說它的作用是按照printf()的格式檢查av_log()的格式。
av_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。

當前系統存在著一個“Log級別”。所有嚴重程度高於該級別的Log資訊都會輸出出來。例如當前的Log級別是AV_LOG_WARNING,則會輸出AV_LOG_PANIC,AV_LOG_FATAL,AV_LOG_ERROR,AV_LOG_WARNING級別的資訊,而不會輸出AV_LOG_INFO級別的資訊。可以通過av_log_get_level()獲得當前Log的級別,通過另一個函式av_log_set_level()設定當前的Log級別。
  從程式碼中可以看出,以上兩個函式主要操作了一個靜態全域性變數av_log_level。該變數用於儲存當前系統Log的級別。它的定義如下所示。

  static int av_log_level = AV_LOG_INFO;

  下面回到av_log()函式的原始碼。它的原始碼位於libavutil\log.c,如下所示。

void av_log(void* avcl, int level, const char *fmt, ...)
{
    AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
    va_list vl;
    va_start(vl, fmt);
    if (avc && avc->version >= (50 << 16 | 15 << 8 | 2) &&
        avc->log_level_offset_offset && level >= AV_LOG_FATAL)
        level += *(int *) (((uint8_t *) avcl) + avc->log_level_offset_offset);
    av_vlog(avcl, level, fmt, vl);
    va_end(vl);
}

首先來提一下C語言函式中“…”引數的含義。與它相關還涉及到以下4個部分:

(1)va_list變數
(2)va_start()
(3)va_arg()
(4)va_end()

va_list是一個指向函式的引數的指標。va_start()用於初始化va_list變數。va_arg()用於返回可變引數。va_start()用於結束可變引數的獲取。有關它們的用法可以參考一個小demo,如下所示。

#include <stdio.h>
#include<stdarg.h>
void fun(int a,...){
    va_list pp;
    va_start(pp,a);
    do{
        printf("param =%d\n",a);
        a=va_arg(pp,int);//使 pp 指向下一個引數,將下一個引數的值賦給變數 a
    }
    while (a!=0);//直到引數為 0 時停止迴圈
}
void main(){
    fun(20,40,60,80,0);
}

有關這方面的知識很難用簡短的語言描述清楚,因此不再詳述。av_log()的原始碼中,在va_start()和va_end()之間,呼叫了另一個函式av_vlog()。

  av_vlog()是一個FFmpeg的API函式。它的宣告位於libavutil\log.h中,

  voidav_vlog(void *avcl,int level,constchar *fmt, va_list vl);

從宣告中可以看出,av_vlog()和av_log()的引數基本上是一模一樣的。唯一的不同在於av_log()中的“…”變成了av_vlog()中的va_list。

  av_vlog()的定義位於libavutil\log.c中,從定義中可以看出,av_vlog()簡單呼叫了一個函式指標av_log_callback。av_log_callback是一個全域性靜態變數, 

  從程式碼中可以看出,av_log_callback指標預設指向一個函式av_log_default_callback()。av_log_default_callback()即FFmpeg預設的Log函式。需要注意的是,這個Log函式是可以自定義的。按照指定的引數定義一個自定義的函式後,可以通過FFmpeg的另一個API函式av_log_set_callback()設定為Log函式。
  av_log_set_callback()的宣告為:voidav_log_set_callback(void (*callback)(void*,int,constchar*, va_list));

從宣告中可以看出,需要指定一個引數為(void*, int, const char*, va_list),返回值為void的函式作為Log函式。
av_log_set_callback()的定義很簡單,做了一個函式指標賦值的工作,

  從宣告中可以看出,需要指定一個引數為(void*, int, const char*, va_list),返回值為void的函式作為Log函式。
av_log_set_callback()的定義很簡單,做了一個函式指標賦值的工作,如下所示。

void av_log_set_callback(void (*callback)(void*, int, const char*, va_list))
{
av_log_callback = callback;
}
例如,我們可以指定一個my_logoutput()函式作為Log的輸出函式,就可以將Log資訊輸出到文字中(而不是螢幕上)。
void my_logoutput(void* ptr, int level, const char* fmt,va_list vl){
FILE *fp = fopen("my_log.txt","a+"); 
if(fp){ 
vfprintf(fp,fmt,vl);
fflush(fp);
fclose(fp);
} 
}

av_log_set_callback(my_logoutput);
編輯好函式之後,使用av_log_set_callback()函式設定該函式為Log輸出函式即可。
  
  FFmpeg的預設Log輸出函式av_log_default_callback(),

av_log_default_callback()的程式碼是比較複雜的。其實如果我們僅僅是希望把Log資訊輸出到螢幕上,遠不需要那麼多程式碼,只需要簡單列印一下就可以了。av_log_default_callback()之所以會那麼複雜,主要是因為他還包含了很多的功能,比如說根據Log級別的不同將輸出的文字設定成不同的顏色等等。

下面看一下av_log_default_callback()的原始碼大致的流程:
(1)如果輸入引數level大於系統當前的日誌級別av_log_level,表明不需要做任何處理,直接返回。
(2)呼叫format_line()設定Log的輸出格式。
(3)呼叫colored_fputs()設定Log的顏色。
  其他日誌內容包括有:format_line()用於設定Log的輸出格式,它本身並不是一個FFmpeg的API,但是FFmpeg有一個API函式av_log_format_line()呼叫了這個函式。

結構體AVBPrint,字串的函式av_bprintf(),位於libavutil\bprint.c;

colored_fputs()函式用於將輸出的文字“上色”並且輸出。在這裡有一點需要注意:Windows和Linux下控制檯程式上色的方法是不一樣的。Windows下是通過SetConsoleTextAttribute()方法給控制檯中的文字上色;Linux下則是通過新增一些ANSI控制碼完成上色。

  從colored_fputs()的原始碼中可以看出如下流程:
首先判定根據巨集定義系統的型別,如果系統型別是Windows,那麼就呼叫SetConsoleTextAttribute()方法設定控制檯文字的顏色,然後呼叫fputs()將Log記錄輸出到stderr(注意不是stdout);如果系統型別是Linux,則通過新增特定字串的方式設定控制檯文字的顏色,然後將Log記錄輸出到stderr。
至此FFmpeg的日誌輸出系統的原始碼就分析完畢了。

  其他日誌輸出細節見:https://blog.csdn.net/leixiaohua1020/article/details/44243155

9.結構體成員管理系統AVOption