FFmpeg的封装专有的log日志系统,支持设置日志等级log level,也支持日志回调log callback,方便开发者调试与排查问题。
1、日志等级
log日志位于libavutil模块,log level的声明位于log.h:
// 静默模式,不打印日志
#define AV_LOG_QUIET -8
// 立即崩溃,退出程序
#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
// 跟踪日志
#define AV_LOG_TRACE 56
可以看到,每个日志等级间隔为8,等级越高数值越小。 设置日志等级的方法set_log_level()位于log.c,其中av_log_level是个静态全局变量,具体如下:
static int av_log_level = AV_LOG_INFO;
void av_log_set_level(int level)
{
av_log_level = level;
}
2、日志打印
常用的打印日志方法av_log(),声明位于log.h:
/**
* 发送特定消息到小于等于当前等级的日志,默认全部发送到stderr
*
* @param avcl 指向任意结构体的指针,结构体第一个变量为AVClass或NULL
* @param level 日志等级
* @param fmt 字符串格式
*/
void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
av_log()方法的实现如下,打印前调用va_start开始,然后打印日志,最后调用va_end结束:
void av_log(void* avcl, int level, const char *fmt, ...)
{
va_list vl;
va_start(vl, fmt);
av_vlog(avcl, level, fmt, vl);
va_end(vl);
}
av_log()内部调用av_vlog()方法,把参数avc1强转为AVClass指针,av_log_callback赋值到函数指针log_callback,计算日志等级,最后如果log_callback不为空则回调日志:
void av_vlog(void* avcl, int level, const char *fmt, va_list vl)
{
AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
void (*log_callback)(void*, int, const char*, va_list) = av_log_callback;
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);
if (log_callback)
log_callback(avcl, level, fmt, vl);
}
3、日志回调
设置日志回调的方法为set_log_level(),代码如下:
void av_log_set_callback(void (*callback)(void*, int, const char*, va_list))
{
av_log_callback = callback;
}
av_log_callback有个默认回调函数:
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[LINE_SZ];
AVBPrint part[4];
char line[LINE_SZ];
static int is_atty;
int type[2];
unsigned tint = 0;
if (level >= 0) {
tint = level & 0xff00;
level &= 0xff;
}
if (level > av_log_level)
return;
ff_mutex_lock(&mutex);
format_line(ptr, level, fmt, vl, part, &print_prefix, type);
snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
#if HAVE_ISATTY
if (!is_atty)
is_atty = isatty(2) ? 1 : -1;
#endif
if (print_prefix && (flags & AV_LOG_SKIP_REPEATED) && !strcmp(line, prev) &&
*line && line[strlen(line) - 1] != '\r'){
count++;
if (is_atty == 1)
fprintf(stderr, " Last message repeated %d times\r", count);
goto end;
}
if (count > 0) {
fprintf(stderr, " Last message repeated %d times\n", count);
count = 0;
}
strcpy(prev, line);
sanitize(part[0].str);
colored_fputs(type[0], 0, part[0].str);
sanitize(part[1].str);
colored_fputs(type[1], 0, part[1].str);
sanitize(part[2].str);
colored_fputs(av_clip(level >> 3, 0, NB_LEVELS - 1), tint >> 8, part[2].str);
sanitize(part[3].str);
colored_fputs(av_clip(level >> 3, 0, NB_LEVELS - 1), tint >> 8, part[3].str);
#if CONFIG_VALGRIND_BACKTRACE
if (level <= BACKTRACE_LOGLEVEL)
VALGRIND_PRINTF_BACKTRACE("%s", "");
#endif
end:
av_bprint_finalize(part+3, NULL);
ff_mutex_unlock(&mutex);
}
static void (*av_log_callback)(void*, int, const char*, va_list) =
av_log_default_callback;
看看Android平台设置的level与callback:
void ffmpeg_init() {
av_log_set_level(AV_LOG_INFO);
av_log_set_callback(log_callback);
}
log_callback()收到回调消息后,可以根据level等级打印对应msg:
void log_callback(void *ptr, int level, const char *format, va_list args) {
switch (level) {
case AV_LOG_DEBUG:
ALOGD(FFMPEG_TAG, format, args);
break;
case AV_LOG_INFO:
ALOGI(FFMPEG_TAG, format, args);
break;
case AV_LOG_ERROR:
ALOGE(FFMPEG_TAG, format, args);
break;
default:
break;
}
}