ijkplayer 源码分析(3):setOption 流程及常用参数设置

引言

在使用 ijkplayer 时我们可以对其做一些参数配置,用以开启或关闭某些功能模块,或选择使用某种方式。比如通过参数配置使用硬解码还是软解码,Android 音频播放使用 AudioTrack 还是 OpenSL,是否启用 SoundTouch 等等。那 ijkplayer 有哪些参数配置?各个参数有什么作用?有哪些常用的参数配置呢?

本文是基于 A4ijkplayer 项目进行 ijkplayer 源码分析,该项目是将 ijkplayer 改成基于 CMake 编译,可导入 Android Studio 编译运行,方便代码查找、函数跳转、单步调试、调用栈跟踪等。

本文主要内容结构:

  • 一、ijkplayer 常用参数设置
  • 二、ijkplayer 参数设置流程
  • 三、ijkplayer 的五类参数设置及各参数作用

一、ijkplayer 常用参数设置

// OPT_CATEGORY_PLAYER 类的参数设置

// 是否开启 Mediacodec 硬解,1 为开启,0 为关闭
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
// 关闭 Mediacodec 硬解,使用 FFmpeg 软解
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);

// 音频播放是否使用 openSL,1:使用 openSL,0:使用 AudioTrack
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);

// 当 CPU 处理不过来的时候的丢帧帧数,默认为 0,参数范围是 [-1, 120],详情见:ff_ffplay_options.h
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 5);

// 在资源准备好后是否自动播放,1:自动播放,0:准备好后不自动播放
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);

// 使用 mediacodec 时是否根据 meta 信息自动旋转视频
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);

// 使用 mediacodec 时是否处理分辨率改变
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0);

// 设置视频显示格式
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);

// 是否开启精准 seek,默认关闭
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1)

// 是否启用 soundtouch,配合 setSpeed 实现变速播放
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", 1)
ijkMediaPlayer.setSpeed(0.5f);


// OPT_CATEGORY_CODEC 类的参数设置

// 是否开启跳过 loop filter,0 为开启,画面质量高,但解码开销大,48 为关闭,画面质量稍差,但解码开销小
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 0);


// OPT_CATEGORY_FORMAT 类的参数设置

// 设置探测输入的分析时长,单位:微秒,详情见:libavformat/options_table.h,通常设置 1 达到首屏秒开效果
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1);

// 在每个数据包之后启用 I/O 上下文的刷新
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);

二、ijkplayer 参数设置流程

先看 Java 层 IjkMediaPlayer.java 中参数设置接口的定义:

    public void setOption(int category, String name, String value) {
        _setOption(category, name, value);
    }

    public void setOption(int category, String name, long value) {
        _setOption(category, name, value);
    }

    private native void _setOption(int category, String name, String value);
    private native void _setOption(int category, String name, long value);

上面两个接口类似,都是 category、key、value 的形式,区别在于 value 是 String 型还是 long 型。可以看出它有三个参数:

  • category:要设置的参数的大的分类,ijkplayer 中一共定义了五类,后文会详细介绍:
    • format: FFP_OPT_CATEGORY_FORMAT 1
    • codec: FFP_OPT_CATEGORY_CODEC 2
    • sws: FFP_OPT_CATEGORY_SWS 3
    • player: FFP_OPT_CATEGORY_PLAYER 4
    • swr: FFP_OPT_CATEGORY_SWR 5
  • key:大类下要设置的参数的 key
  • value:大类下要设置的参数的 value

2.1 保存上层设置的参数

Java 层调用 _setOption 后会调到 ijkmedia/ijkplayer/android/ijkplayer_jni.c 中的 IjkMediaPlayer_setOption() 方法,如下:

static void
IjkMediaPlayer_setOption(JNIEnv *env, jobject thiz, jint category, jobject name, jobject value)
{
    // ...
    c_name = (*env)->GetStringUTFChars(env, name, NULL );
    c_value = (*env)->GetStringUTFChars(env, value, NULL );
    // ...
    ijkmp_set_option(mp, category, c_name, c_value);
    // ...
}

static void
IjkMediaPlayer_setOptionLong(JNIEnv *env, jobject thiz, jint category, jobject name, jlong value)
{
    // ...
    c_name = (*env)->GetStringUTFChars(env, name, NULL );
    // ...
    ijkmp_set_option_int(mp, category, c_name, value);
}

接着会调到 ijkmedia/ijkplayer/ijkplayer.cijkmp_set_option() 方法:

void ijkmp_set_option(IjkMediaPlayer *mp, int opt_category, const char *name, const char *value)
{
    pthread_mutex_lock(&mp->mutex);
    ffp_set_option(mp->ffplayer, opt_category, name, value);
    pthread_mutex_unlock(&mp->mutex);
}

void ijkmp_set_option_int(IjkMediaPlayer *mp, int opt_category, const char *name, int64_t value)
{
    pthread_mutex_lock(&mp->mutex);
    ffp_set_option_int(mp->ffplayer, opt_category, name, value);
    pthread_mutex_unlock(&mp->mutex);
}

再调到 ijkmedia/ijkplayer/ff_ffplay.cffp_set_option() 方法,实际是调用 ijkmedia/ijkplayer/ff_ffplay.c 中的 ffp_get_opt_dict() 获取 FFPlayer 中对应大类的字典然后把参数按照分类保存起来:

void ffp_set_option(FFPlayer *ffp, int opt_category, const char *name, const char *value)
{
    AVDictionary **dict = ffp_get_opt_dict(ffp, opt_category);
    av_dict_set(dict, name, value, 0);
}

void ffp_set_option_int(FFPlayer *ffp, int opt_category, const char *name, int64_t value)
{
    AVDictionary **dict = ffp_get_opt_dict(ffp, opt_category);
    av_dict_set_int(dict, name, value, 0);
}

static AVDictionary **ffp_get_opt_dict(FFPlayer *ffp, int opt_category)
{
    switch (opt_category) {
        case FFP_OPT_CATEGORY_FORMAT:   return &ffp->format_opts;
        case FFP_OPT_CATEGORY_CODEC:    return &ffp->codec_opts;
        case FFP_OPT_CATEGORY_SWS:      return &ffp->sws_dict;
        case FFP_OPT_CATEGORY_PLAYER:   return &ffp->player_opts;
        case FFP_OPT_CATEGORY_SWR:      return &ffp->swr_opts;
        default:
            av_log(ffp, AV_LOG_ERROR, "unknown option category %d\n", opt_category);
            return NULL;
    }
}

2.2 应用设置的参数

setOption() 执行完后只是将参数保存了,那在什么地方用,怎么用呢?

1 FFP_OPT_CATEGORY_FORMAT 参数的应用

从上一节可以看出 format 相关参数是保存在 ffp->format_opts 中,其应用是在 prepareAsync() 准备资源,调用 FFmpeg 的 avformat_open_input() 打开输入流时设置给 FFmpeg 内部用。

static int read_thread(void *arg)
{
	// ...
	err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
	// ...
}

2 FFP_OPT_CATEGORY_CODEC 参数的应用

从上一节可以看出 codec 相关参数是保存在 ffp->codec_opts 中,其应用是在 prepareAsync() 准备资源,调用 FFmpeg 打开解码器以及获取流信息时使用。

static int stream_component_open(FFPlayer *ffp, int stream_index)
{
	// ...
	opts = filter_codec_opts(ffp->codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);
	// ...
	if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {
        goto fail;
    }
}
static int read_thread(void *arg)
{
	// ...
	if (ffp->find_stream_info) {
        AVDictionary **opts = setup_find_stream_info_opts(ic, ffp->codec_opts);
        // ...
        do {
            // ...
            err = avformat_find_stream_info(ic, opts);
        } while(0);
    }
	// ...
}

3 FFP_OPT_CATEGORY_SWS 参数的应用

从上一节可以看出 sws 相关参数是保存在 ffp->sws_dict 中,该类参数在 ijkplayer 中并没有真正使用,只在 prepareAsync() 准备资源时做了参数打印输出。

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
	// ...
	av_log(NULL, AV_LOG_INFO, "===== options =====\n");
    ffp_show_dict(ffp, "player-opts", ffp->player_opts);
    ffp_show_dict(ffp, "format-opts", ffp->format_opts);
    ffp_show_dict(ffp, "codec-opts ", ffp->codec_opts);
    ffp_show_dict(ffp, "sws-opts   ", ffp->sws_dict);
    ffp_show_dict(ffp, "swr-opts   ", ffp->swr_opts);
	// ...
}

4 FFP_OPT_CATEGORY_PLAYER 参数的应用

从上一节可以看出 player 相关参数是保存在 ffp->player_opts 中,其应用是在 prepareAsync() 准备资源时保存到 FFplayer 对应的成员变量中:

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
	// ...
	av_log(NULL, AV_LOG_INFO, "===== options =====\n");
    ffp_show_dict(ffp, "player-opts", ffp->player_opts);
    // ...
    av_opt_set_dict(ffp, &ffp->player_opts);
	// ...
}

/**
 * Set all the options from a given dictionary on an object.
 *
 * @param obj a struct whose first element is a pointer to AVClass
 * @param options options to process. This dictionary will be freed and replaced
 *                by a new one containing all options not found in obj.
 *                Of course this new dictionary needs to be freed by caller
 *                with av_dict_free().
 *
 * @return 0 on success, a negative AVERROR if some option was found in obj,
 *         but could not be set.
 *
 * @see av_dict_copy()
 */
int av_opt_set_dict(void *obj, struct AVDictionary **options);

av_opt_set_dict() 是 FFmpeg 的 libavutil/opt.h 中定义的函数,从其函数说明可以看到,它是把字典中的所有 options 设置到传入的对象中,这里也就是 FFPlayer ,其他模块的 options 实际上也是通过该函数保存到各自的对象中的。那它们是怎么保存的呢?

可以看该函数第一个参数的说明,通过传入对象结构的第一个元素 AVClass

@param obj a struct whose first element is a pointer to AVClass

那我们看下 FFPlayer 结构体,在 ijkmedia/ijkplayer/ff_ffplay_def.h 中定义:

typedef struct FFPlayer {
    const AVClass *av_class;
    // ...
} FFPlayer;

在看 av_class 的使用,在 ijkmedia/ijkplayer/ff_ffplay.c 文件中:

FFPlayer *ffp_create()
{
    // ...
    ffp->av_class = &ffp_context_class;
	// ...
}

const AVClass ffp_context_class = {
    .class_name       = "FFPlayer",
    .item_name        = ffp_context_to_name,
    .option           = ffp_context_options,
    .version          = LIBAVUTIL_VERSION_INT,
    .child_next       = ffp_context_child_next,
    .child_class_next = ffp_context_child_class_next,
};

这里终于看到了 option 的使用:

.option           = ffp_context_options,

接着看 ffp_context_options ,在 ijkmedia/ijkplayer/ff_ffplay_options.h 中定义,可以看到我们常用的参数的定义和说明(完整参数请看后文内容):

static const AVOption ffp_context_options[] = {
    // original options in ffplay.c
    { "an",                             "disable audio", OPTION_OFFSET(audio_disable),   OPTION_INT(0, 0, 1) },
    { "vn",                             "disable video", OPTION_OFFSET(video_disable),   OPTION_INT(0, 0, 1) },
    // ...
    { "framedrop",                      "drop frames when cpu is too slow", OPTION_OFFSET(framedrop),       OPTION_INT(0, -1, 120) },
    // ...
    { "start-on-prepared",                  "automatically start playing on prepared", OPTION_OFFSET(start_on_prepared),   OPTION_INT(1, 0, 1) },
    // ...
    { "enable-accurate-seek",                      "enable accurate seek", OPTION_OFFSET(enable_accurate_seek),       OPTION_INT(0, 0, 1) },
    // ...
    { "mediacodec",                             "MediaCodec: enable H264 (deprecated by 'mediacodec-avc')", OPTION_OFFSET(mediacodec_avc),          OPTION_INT(0, 0, 1) },
    // ...
    { "opensles",                           "OpenSL ES: enable", OPTION_OFFSET(opensles),            OPTION_INT(0, 0, 1) },
    { "soundtouch",                           "SoundTouch: enable", OPTION_OFFSET(soundtouch_enable),OPTION_INT(0, 0, 1) },
    // ...
}

其中每一行的结构如下:
{name, help, offset, type, default_val, min, max, …}

  • name:就是要设置的参数的 key
  • help:该参数的帮助说明
  • offset:当前参数对应的成员变量在结构体中的偏移,这样就可以在调用 av_opt_set_dict() 时把字典中 key 对应的参数值存入到结构体对象对应的成员变量中。比如,如果外面设置了 “opensles” 的 option,那么就可以根据偏移 OPTION_OFFSET(opensles) 映射到 FFplayer 结构体中的 opensles 成员变量,FFmpeg 中参数设置都是这种模式。
    typedef struct FFPlayer {
        const AVClass *av_class;
        // ...
        int opensles;
    	int soundtouch_enable;
    	// ...
    } FFPlayer;
    
  • type:参数的数据类型
  • default_val:参数的默认值
  • min:参数最小值
  • max:参数最大值

5 FFP_OPT_CATEGORY_SWR 参数的应用

从上一节可以看出 sws 相关参数是保存在 ffp->swr_opts 中,该类参数在 ijkplayer 中并没有真正使用,而且 Java 上层的 IjkMediaPlayer 中并没有定义该类型。

三、ijkplayer 的五类参数设置及各参数作用

FFP_OPT_CATEGORY_FORMATFFP_OPT_CATEGORY_CODECFFP_OPT_CATEGORY_SWSFFP_OPT_CATEGORY_PLAYERFFP_OPT_CATEGORY_SWR 这五类其实只有 FFP_OPT_CATEGORY_PLAYER 是 ijkplayer 自己参考 FFmpeg 参数结构设计的,其他四个都是 FFmpeg 原有的参数。

1 FFP_OPT_CATEGORY_FORMAT 完整参数

是 FFmpeg libavformat 模块相关参数,其完整参数见 FFmpeg 源码中的:libavformat/options_table.h

https://github.com/bilibili/FFmpeg/blob/ff4.0--ijk0.8.8--20210426--001/libavformat/options_table.h

static const AVOption avformat_options[] = {
{"avioflags", NULL, OFFSET(avio_flags), AV_OPT_TYPE_FLAGS, {.i64 = DEFAULT }, INT_MIN, INT_MAX, D|E, "avioflags"},
{"direct", "reduce buffering", 0, AV_OPT_TYPE_CONST, {.i64 = AVIO_FLAG_DIRECT }, INT_MIN, INT_MAX, D|E, "avioflags"},
{"probesize", "set probing size", OFFSET(probesize), AV_OPT_TYPE_INT64, {.i64 = 5000000 }, 32, INT64_MAX, D},
{"formatprobesize", "number of bytes to probe file format", OFFSET(format_probesize), AV_OPT_TYPE_INT, {.i64 = PROBE_BUF_MAX}, 0, INT_MAX-1, D},
{"packetsize", "set packet size", OFFSET(packet_size), AV_OPT_TYPE_INT, {.i64 = DEFAULT }, 0, INT_MAX, E},
// ...
}

2 FFP_OPT_CATEGORY_CODEC 完整参数

是 FFmpeg libavcodec 模块相关参数,其完整参数见 FFmpeg 源码中的:libavcodec/options_table.h

https://github.com/bilibili/FFmpeg/blob/ff4.0--ijk0.8.8--20210426--001/libavcodec/options_table.h

static const AVOption avcodec_options[] = {
{"b", "set bitrate (in bits/s)", OFFSET(bit_rate), AV_OPT_TYPE_INT64, {.i64 = AV_CODEC_DEFAULT_BITRATE }, 0, INT64_MAX, A|V|E},
{"ab", "set bitrate (in bits/s)", OFFSET(bit_rate), AV_OPT_TYPE_INT64, {.i64 = 128*1000 }, 0, INT_MAX, A|E},
// ...
{"skip_loop_filter", "skip loop filtering process for the selected frames", OFFSET(skip_loop_filter), AV_OPT_TYPE_INT, {.i64 = AVDISCARD_DEFAULT }, INT_MIN, INT_MAX, V|D, "avdiscard"},
{"skip_idct"       , "skip IDCT/dequantization for the selected frames",    OFFSET(skip_idct),        AV_OPT_TYPE_INT, {.i64 = AVDISCARD_DEFAULT }, INT_MIN, INT_MAX, V|D, "avdiscard"},
{"skip_frame"      , "skip decoding for the selected frames",               OFFSET(skip_frame),       AV_OPT_TYPE_INT, {.i64 = AVDISCARD_DEFAULT }, INT_MIN, INT_MAX, V|D, "avdiscard"},

// ...
}

3 FFP_OPT_CATEGORY_SWS 完整参数

是 FFmpeg libswscale 模块相关参数,其完整参数见 FFmpeg 源码中的:libswscale/options.c

https://github.com/bilibili/FFmpeg/blob/ff4.0--ijk0.8.8--20210426--001/libswscale/options.c
static const AVOption swscale_options[] = {
    { "sws_flags",       "scaler flags",                  OFFSET(flags),     AV_OPT_TYPE_FLAGS,  { .i64  = SWS_BICUBIC        }, 0,      UINT_MAX,        VE, "sws_flags" },
    { "fast_bilinear",   "fast bilinear",                 0,                 AV_OPT_TYPE_CONST,  { .i64  = SWS_FAST_BILINEAR  }, INT_MIN, INT_MAX,        VE, "sws_flags" },
    { "bilinear",        "bilinear",                      0,                 AV_OPT_TYPE_CONST,  { .i64  = SWS_BILINEAR       }, INT_MIN, INT_MAX,        VE, "sws_flags" },
    // ...
}

4 FFP_OPT_CATEGORY_PLAYER 完整参数

是 ijkplayer 自身配置的相关参数,其完整参数见 ijkplayer 源码中的:ijkmedia/ijkplayer/ff_ffplay_options.h

https://github.com/Bilibili/ijkplayer/blob/k0.8.8/ijkmedia/ijkplayer/ff_ffplay_options.h
static const AVOption ffp_context_options[] = {
    // original options in ffplay.c
    { "an",                             "disable audio", OPTION_OFFSET(audio_disable),   OPTION_INT(0, 0, 1) },
    { "vn",                             "disable video", OPTION_OFFSET(video_disable),   OPTION_INT(0, 0, 1) },
    // ...
    { "framedrop",                      "drop frames when cpu is too slow", OPTION_OFFSET(framedrop),       OPTION_INT(0, -1, 120) },
    // ...
    { "start-on-prepared",                  "automatically start playing on prepared", OPTION_OFFSET(start_on_prepared),   OPTION_INT(1, 0, 1) },
    // ...
    { "enable-accurate-seek",                      "enable accurate seek", OPTION_OFFSET(enable_accurate_seek),       OPTION_INT(0, 0, 1) },
    // ...
    { "mediacodec",                             "MediaCodec: enable H264 (deprecated by 'mediacodec-avc')", OPTION_OFFSET(mediacodec_avc),          OPTION_INT(0, 0, 1) },
    // ...
    { "opensles",                           "OpenSL ES: enable", OPTION_OFFSET(opensles),            OPTION_INT(0, 0, 1) },
    { "soundtouch",                           "SoundTouch: enable", OPTION_OFFSET(soundtouch_enable),OPTION_INT(0, 0, 1) },
    // ...
}

5 FFP_OPT_CATEGORY_SWR 完整参数

是 FFmpeg libswresample 模块相关参数,其完整参数见 FFmpeg 源码中的:libswresample/options.c

https://github.com/bilibili/FFmpeg/blob/ff4.0--ijk0.8.8--20210426--001/libswresample/options.c
static const AVOption options[]={
{"ich"                  , "set input channel count"     , OFFSET(user_in_ch_count  ), AV_OPT_TYPE_INT, {.i64=0                    }, 0      , SWR_CH_MAX, PARAM},
{"in_channel_count"     , "set input channel count"     , OFFSET(user_in_ch_count  ), AV_OPT_TYPE_INT, {.i64=0                    }, 0      , SWR_CH_MAX, PARAM},
{"och"                  , "set output channel count"    , OFFSET(user_out_ch_count ), AV_OPT_TYPE_INT, {.i64=0                    }, 0      , SWR_CH_MAX, PARAM},
{"out_channel_count"    , "set output channel count"    , OFFSET(user_out_ch_count ), AV_OPT_TYPE_INT, {.i64=0                    }, 0      , SWR_CH_MAX, PARAM},
// ...
}

你可能感兴趣的:(ijkplayer,FFmpeg,音视频,ffmpeg,android,常用参数设置,ijk,Option,参数配置)