ijkplayer-日志输出

目录

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

参考

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类型用作在<cstdarg>中定义的宏的参数,与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的持久整数。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342