每个后台进程的日志很重要的,实现的文件在log.c/log.h。FFmpeg的日志在实现上有一些小细节是我以前从来没有考虑过的,譬如颜色,是否重复等。但还是很简单滴。
先上使用方式:
av_log(obj, AV_LOG_ERROR, "Value %lf for parameter '%s' out of range\n", num, name);
声明:
/**
* @param avcl A pointer to an arbitrary struct of which the first field is a
* pointer to an AVClass struct.
* @param level The importance level of the message, lower values signifying
* higher importance.
* @param fmt The format string (printf-compatible) that specifies how
* subsequent arguments are converted to output.
* @see av_vlog
*/
void av_log(void *avcl, int level, const char *fmt, ...);
avcl是指向AVClass的指针,这个和其他的日志有区别。这是为了区分打印的AVClass,但是没打印时间戳哦。level是日志级别。
#define AV_LOG_PANIC 0
#define AV_LOG_FATAL 8
#define AV_LOG_ERROR 16
#define AV_LOG_WARNING 24
#define AV_LOG_INFO 32
#define AV_LOG_VERBOSE 40
#define AV_LOG_DEBUG 48
级别越高数据越小。但是这些宏的值为什么不连续呢?在av_log中有level的运算逻辑,这谁能看懂啊。不管啦。
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);
}
void av_vlog(void* avcl, int level, const char *fmt, va_list vl)
{
av_log_callback(avcl, level, fmt, vl);
}
void av_log_set_callback(void (*callback)(void*, int, const char*, va_list))
{
av_log_callback = callback;
}
真正实现打印的是回调函数,回调函数是可以设置的。那就看默认的回调函数是什么呢?
av_log_default_callback是默认的回调函数。逻辑上主要三部分:是否打印,也就是level判断;后进行内容拷贝,最后进行打印。
第一部分变量声明,后进行level判断。大于av_log_level才能打印。av_log_level是可以设置的,默认的是:AV_LOG_INFO。宗旨是:级别越高的才打印。在调试的时候,需要把av_log_level设置成debug。对于debug有时打印不出来,必须在config的时候打印debug,nginx就是这样。ffmpeg好像不是哦。如果把av_log_level设置成debug后还不出现debug,则需要看一看在configure时是否需要打开debug选项了。
void av_log_default_callback(void* ptr, int level, const char* fmt, va_list vl)
{
static int print_prefix=1;
static int count;
static char prev[1024];
char line[1024];
static int is_atty;
AVClass* avc= ptr ? *(AVClass**)ptr : NULL;
if(level>av_log_level)
return;
line[0]=0;
cpy内容,最前面有AVClass的item_name。怎么没打印时间呢?FFmpeg对于觉得时间不重要的吗?
#undef fprintf
if(print_prefix && avc) {
if (avc->parent_log_context_offset) {
AVClass** parent= *(AVClass***)(((uint8_t*)ptr) + avc->parent_log_context_offset);
if(parent && *parent){
snprintf(line, sizeof(line), "[%s @ %p] ", (*parent)->item_name(parent), parent);
}
}
snprintf(line + strlen(line), sizeof(line) - strlen(line), "[%s @ %p] ", avc->item_name(ptr), ptr);
}
vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, vl);
print_prefix = strlen(line) && line[strlen(line)-1] == '\n';
这才真正的打印。首先判断了是否去掉重复的日志,sanitize函数对于无法打印的字符用问号来代替。colored_fputs输出带有颜色的字符。重点看看这个颜色。
if(print_prefix && (flags & AV_LOG_SKIP_REPEATED) && !strcmp(line, prev)){
count++;
if(is_atty==1)
fprintf(stderr, " Last message repeated %d times\r", count);
return;
}
if(count>0){
fprintf(stderr, " Last message repeated %d times\n", count);
count=0;
}
strcpy(prev, line);
sanitize(line);
colored_fputs(av_clip(level>>3, 0, 6), line);
}
颜色打印的实现。我还第一次看这个的实现啦。分成三步:先判断是否需要user color,后set color再fputs,最后reset。
static void colored_fputs(int level, const char *str){
if(use_color<0){
use_color= getenv("FFMPEG_FORCE_COLOR") && !getenv("NO_COLOR") && !getenv("FFMPEG_FORCE_NOCOLOR");
if(use_color){
set_color(level);
}
fputs(str, stderr);
if(use_color){
reset_color();
}
}
最后看一下set color,linux和win是不同的实现。下面的是linux的:
static const uint8_t color[]={0x41,0x41,0x11,0x03,9,9,9};
#define set_color(x) fprintf(stderr, "\033[%d;3%dm", color[x]>>4, color[x]&15)
#define reset_color() fprintf(stderr, "\033[0m")
那这个默认的功能有了,那如果要输出到文件中呢?新版本的ffplay是可以输出到文件中的,我分析的代码是ffmpeg-0.8,没有打印到文件中。如果想输出到文件,需要重新设置日志打印函数。