目录
- 概述
- 日志输出实现
- 附录
参考
- [1] github.com/bilibili/ijkplayer
- [2] github.com/bilibili/ijkplayer/releases/tag/k0.8.8
- [3] stackoverflow/unused-flag-behavior-usage-gcc-with-objective-c
- [4] developer.android.com/ndk/reference/group/logging
- [5] [雷霄骅/FFmpeg源代码简单分析:日志输出系统(av_log()等)
1. 概述
ijkplayer中日志打印模块控制日志的输出方式:
- android平台:提供了多种日志级别(同android定义的级别),使用android native层提供的日志打印接口
__android_log_print
和__android_log_vprint
。 - 其他平台:使用
printf
和vprintf
进行打印,无日志级别控制。
对于依赖库FFmpeg的日志输出,提供了设置日志级别的接口和控制是否按照FFmpeg默认的格式输出的开关。由于FFmpeg和ijkplayer的定义的日志级别有差异,需要定义一个转换关系。
日志输出控制的接口:
-
void ijkmp_global_set_log_report(int use_report)
:use_report为true时按照FFmpeg默认的格式,会更详细一些。 -
void ijkmp_global_set_log_level(int log_level)
:控制FFmpeg的日志输出等级。
2. 日志输出实现
ijkplayer中日志输出实现主要涉及的源码文件如下所示:
├── ijkmedia
│ ├── ijkplayer
│ │ ├── ff_ffplay.c
│ └── ijksdl
│ ├── ijksdl_extra_log.c
│ ├── ijksdl_extra_log.h
│ ├── ijksdl_log.h
2.1 ijkplayer日志输出
- VLOGV, VLOGD, VLOGI, VLOGW, VLOGE:源码中没有使用。
- ALOGV, ALOGD, ALOGI, ALOGW, ALOGE:源码中都有使用,其中的TAG统一为"IJKMEDIA",使用示例如下所示:
//h264_nal.h
ALOGE( "PPS too small after processing SPS/PPS %u", i_data_size );
- ALOG:只有一个地方有引用,在FFmpeg日志回调函数中打印FFmpeg模块的日志。
//ff_ffplay.c
static void ffp_log_callback_report(void *ptr, int level, const char *fmt, va_list vl)
{
...
int ffplv __unused = log_level_av_to_ijk(level);
...
char line[1024];
...
ALOG(ffplv, IJK_LOG_TAG, "%s", line);
}
在ijksdl_log.h中屏蔽了各平台日志输出实现的差异。
- android平台:
- 定义了
EXTRA_LOG_PRINT
宏:使用JNI的方式J4AC_BLog__v__withCString__catchAll
(ijksdl_extra_log.c)调用Java的方法进行日志打印,项目中没有找到J4AC_BLog__v__withCString__catchAll
的实现。可以基于自己的需要去实现。 - 否则:
__android_log_vprint
,__android_log_print
。
- 定义了
- 非android平台:
vprintf
、printf
。
//ijksdl_log.h
#define IJK_LOG_DEBUG ANDROID_LOG_DEBUG
...
#ifdef EXTRA_LOG_PRINT
#define VLOG(level, TAG, ...) ffp_log_extra_vprint(level, TAG, __VA_ARGS__)
#define ALOG(level, TAG, ...) ffp_log_extra_print(level, TAG, __VA_ARGS__)
#else
#define VLOG(level, TAG, ...) ((void)__android_log_vprint(level, TAG, __VA_ARGS__))
#define ALOG(level, TAG, ...) ((void)__android_log_print(level, TAG, __VA_ARGS__))
#endif
#else
...
#define IJK_LOG_VERBOSE 2
...
#define VLOG(level, TAG, ...) ((void)vprintf(__VA_ARGS__))
#define ALOG(level, TAG, ...) ((void)printf(__VA_ARGS__))
#endif
#define IJK_LOG_TAG "IJKMEDIA"
#define VLOGV(...) VLOG(IJK_LOG_VERBOSE, IJK_LOG_TAG, __VA_ARGS__)
...
#define ALOGV(...) ALOG(IJK_LOG_VERBOSE, IJK_LOG_TAG, __VA_ARGS__)
...
#define LOG_ALWAYS_FATAL(...) do { ALOGE(__VA_ARGS__); exit(1); } while (0)
#endif
-
LOG_ALWAYS_FATAL
没有引用的地方。
2.2 FFmpeg的日志输出
- 设置libav库的日志级别(av_log_get_level),因为等级的定义不一样,需要进行ijkplayer到libav日志级别的转换。
- 通过av_log_set_callback()设置libav的日志回调函数,在日志回调函数中控制日志的打印格式,需要进行libav到ijkplayer日志级别转换。
-
ffp_global_set_log_report(int use_report)
的use_report参数控制libav的日志的打印样式,user_report为true时,libav中的日志会以默认的回调一样的格式打印。会有更多一些的信息,详见"附录#av_log_format_line"。
//ff_ffplay.c
inline static int log_level_av_to_ijk(int av_level)
{
int ijk_level = IJK_LOG_VERBOSE;
if (av_level <= AV_LOG_PANIC) ijk_level = IJK_LOG_FATAL;
else if (av_level <= AV_LOG_FATAL) ijk_level = IJK_LOG_FATAL;
else if (av_level <= AV_LOG_ERROR) ijk_level = IJK_LOG_ERROR;
else if (av_level <= AV_LOG_WARNING) ijk_level = IJK_LOG_WARN;
else if (av_level <= AV_LOG_INFO) ijk_level = IJK_LOG_INFO;
// AV_LOG_VERBOSE means detailed info
else if (av_level <= AV_LOG_VERBOSE) ijk_level = IJK_LOG_INFO;
else if (av_level <= AV_LOG_DEBUG) ijk_level = IJK_LOG_DEBUG;
else if (av_level <= AV_LOG_TRACE) ijk_level = IJK_LOG_VERBOSE;
else ijk_level = IJK_LOG_VERBOSE;
return ijk_level;
}
inline static int log_level_ijk_to_av(int ijk_level)
{
int av_level = IJK_LOG_VERBOSE;
if (ijk_level >= IJK_LOG_SILENT) av_level = AV_LOG_QUIET;
else if (ijk_level >= IJK_LOG_FATAL) av_level = AV_LOG_FATAL;
else if (ijk_level >= IJK_LOG_ERROR) av_level = AV_LOG_ERROR;
else if (ijk_level >= IJK_LOG_WARN) av_level = AV_LOG_WARNING;
else if (ijk_level >= IJK_LOG_INFO) av_level = AV_LOG_INFO;
// AV_LOG_VERBOSE means detailed info
else if (ijk_level >= IJK_LOG_DEBUG) av_level = AV_LOG_DEBUG;
else if (ijk_level >= IJK_LOG_VERBOSE) av_level = AV_LOG_TRACE;
else if (ijk_level >= IJK_LOG_DEFAULT) av_level = AV_LOG_TRACE;
else if (ijk_level >= IJK_LOG_UNKNOWN) av_level = AV_LOG_TRACE;
else av_level = AV_LOG_TRACE;
return av_level;
}
static void ffp_log_callback_brief(void *ptr, int level, const char *fmt, va_list vl)
{
if (level > av_log_get_level())
return;
int ffplv __unused = log_level_av_to_ijk(level);
VLOG(ffplv, IJK_LOG_TAG, fmt, vl);
}
static void ffp_log_callback_report(void *ptr, int level, const char *fmt, va_list vl)
{
if (level > av_log_get_level())
return;
int ffplv __unused = log_level_av_to_ijk(level);
va_list vl2;
char line[1024];
static int print_prefix = 1;
va_copy(vl2, vl);
// av_log_default_callback(ptr, level, fmt, vl);
av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix);
va_end(vl2);
ALOG(ffplv, IJK_LOG_TAG, "%s", line);
}
void ffp_global_set_log_report(int use_report)
{
if (use_report) {
av_log_set_callback(ffp_log_callback_report);
} else {
av_log_set_callback(ffp_log_callback_brief);
}
}
void ffp_global_set_log_level(int log_level)
{
int av_level = log_level_ijk_to_av(log_level);
av_log_set_level(av_level);
}
2.3 日志输出控制示例
iOS示例
//ijkplayer\ios\IJKMediaDemo\IJKMediaDemo\IJKMoviePlayerViewController.m
#ifdef DEBUG
[IJKFFMoviePlayerController setLogReport:YES];
[IJKFFMoviePlayerController setLogLevel:k_IJK_LOG_DEBUG];
#else
[IJKFFMoviePlayerController setLogReport:NO];
[IJKFFMoviePlayerController setLogLevel:k_IJK_LOG_INFO];
#endif
3. 附录
3.1. __unused
宏
__unused
宏实际上的扩展是GCC编译属性的__attribute__((unused))
,用于告诉编译器“如果没有使用这个变量,不要发出警告”。[3]
3.2. __android_log_print
和__android_log_vprint
函数的定义
__android_log_print(int prio, const char *tag, const char *fmt, ...);
__android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap);
-
...
表示可变参数列表,__VA_ARGS__
在预处理中,会被实际的参数集(实参列表)所替换。 -
va_list
类型用作在<cstdarg>中定义的宏的参数,与va_start
、va_arg
、va_end
配合使用来检索函数的其他参数。
3.3. FFmpeg中日志级别的定义
//avutil/log.h
#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
/**
* Extremely verbose debugging, useful for libav* development.
*/
#define AV_LOG_TRACE 56
3.4. av_log_format_line
void av_log_format_line ( void * ptr,
int level,
const char * fmt,
va_list vl,
char * line,
int line_size,
int * print_prefix
)
-
以与av_log()的默认回调相同的方式格式化日志行,会包含目标结构体的父结构体的名称,其打印格式形如“[%s @ %p]”,其中前面的“%s”对应父结构体的名称,“%p”对应其所在的地址。还有用于输出Log的级别。使用av_log()在控制台输出日志的效果如下图所示。[5]
- line:接收格式化行的缓冲区。
- line_size:缓冲区的大小
- print_prefix:用于存储是否必须打印前缀;必须指向初始设置为1的持久整数。