在使用 ijkplayer 时我们可以对其做一些参数配置,用以开启或关闭某些功能模块,或选择使用某种方式。比如通过参数配置使用硬解码还是软解码,Android 音频播放使用 AudioTrack 还是 OpenSL,是否启用 SoundTouch 等等。那 ijkplayer 有哪些参数配置?各个参数有什么作用?有哪些常用的参数配置呢?
本文是基于 A4ijkplayer 项目进行 ijkplayer 源码分析,该项目是将 ijkplayer 改成基于 CMake 编译,可导入 Android Studio 编译运行,方便代码查找、函数跳转、单步调试、调用栈跟踪等。
本文主要内容结构:
// 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);
先看 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 型。可以看出它有三个参数:
FFP_OPT_CATEGORY_FORMAT 1
FFP_OPT_CATEGORY_CODEC 2
FFP_OPT_CATEGORY_SWS 3
FFP_OPT_CATEGORY_PLAYER 4
FFP_OPT_CATEGORY_SWR 5
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.c
的 ijkmp_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.c
的 ffp_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;
}
}
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, …}
OPTION_OFFSET(opensles)
映射到 FFplayer 结构体中的 opensles 成员变量,FFmpeg 中参数设置都是这种模式。typedef struct FFPlayer {
const AVClass *av_class;
// ...
int opensles;
int soundtouch_enable;
// ...
} FFPlayer;
5 FFP_OPT_CATEGORY_SWR 参数的应用
:从上一节可以看出 sws 相关参数是保存在 ffp->swr_opts
中,该类参数在 ijkplayer 中并没有真正使用,而且 Java 上层的 IjkMediaPlayer 中并没有定义该类型。
FFP_OPT_CATEGORY_FORMAT
、FFP_OPT_CATEGORY_CODEC
、FFP_OPT_CATEGORY_SWS
、FFP_OPT_CATEGORY_PLAYER
、FFP_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},
// ...
}