ijkplayer-日志输出

目录

  1. 概述
  2. 日志输出实现
  3. 附录

参考

  • [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
  • 其他平台:使用printfvprintf进行打印,无日志级别控制。

对于依赖库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平台:vprintfprintf
//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类型用作在中定义的宏的参数,与va_startva_argva_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]


    image.png
  • line:接收格式化行的缓冲区。
  • line_size:缓冲区的大小
  • print_prefix:用于存储是否必须打印前缀;必须指向初始设置为1的持久整数。

你可能感兴趣的:(ijkplayer-日志输出)