1.java层调用
ijkMediaPlayer = new IjkMediaPlayer();
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
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);
}
2.走到jni层
//jni调用
static void
IjkMediaPlayer_setOptionLong(JNIEnv *env, jobject thiz, jint category, jobject name, jlong value)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
const char *c_name = NULL;
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: setOptionLong: null mp", LABEL_RETURN);
c_name = (*env)->GetStringUTFChars(env, name, NULL );
JNI_CHECK_GOTO(c_name, env, "java/lang/OutOfMemoryError", "mpjni: setOptionLong: name.string oom", LABEL_RETURN);
ijkmp_set_option_int(mp, category, c_name, value);
LABEL_RETURN:
if (c_name)
(*env)->ReleaseStringUTFChars(env, name, c_name);
ijkmp_dec_ref_p(&mp);
}
//ijkplayer.c
void ijkmp_set_option_int(IjkMediaPlayer *mp, int opt_category, const char *name, int64_t value)
{
assert(mp);
// MPTRACE("%s(%s, %"PRId64")\n", __func__, name, value);
pthread_mutex_lock(&mp->mutex);
ffp_set_option_int(mp->ffplayer, opt_category, name, value);
pthread_mutex_unlock(&mp->mutex);
// MPTRACE("%s()=void\n", __func__);
}
3.走到ffplay中,放入到ffp对应的opts中
//ff_play.c
void ffp_set_option_int(FFPlayer *ffp, int opt_category, const char *name, int64_t value)
{
if (!ffp)
return;
//查找需要放入的AVDictionary
AVDictionary **dict = ffp_get_opt_dict(ffp, opt_category);
//FFmpeg中字典存储
av_dict_set_int(dict, name, value, 0);
}
static AVDictionary **ffp_get_opt_dict(FFPlayer *ffp, int opt_category)
{
assert(ffp);
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;
//java层出入的值为FFP_OPT_CATEGORY_PLAYER
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;
}
}
可以在ijk中依然基于FFmpeg原本的方式进行相关参数的设置。那就需要了解一下FFmpeg中的相关结构体和操作方法。
//参数配置以key-value形式进行存储
typedef struct AVDictionaryEntry {
char *key;
char *value;
} AVDictionaryEntry;
//设置参数的方式
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags);
4.在开始准备播放时进行设置
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
assert(ffp);
assert(!ffp->is);
assert(file_name);
if (av_stristart(file_name, "rtmp", NULL) ||
av_stristart(file_name, "rtsp", NULL)) {
// There is total different meaning for 'timeout' option in rtmp
av_log(ffp, AV_LOG_WARNING, "remove 'timeout' option for rtmp.\n");
av_dict_set(&ffp->format_opts, "timeout", NULL, 0);
}
/* there is a length limit in avformat */
if (strlen(file_name) + 1 > 1024) {
av_log(ffp, AV_LOG_ERROR, "%s too long url\n", __func__);
if (avio_find_protocol_name("ijklongurl:")) {
av_dict_set(&ffp->format_opts, "ijklongurl-url", file_name, 0);
file_name = "ijklongurl:";
}
}
//将字典内容设置到opt中
av_opt_set_dict(ffp, &ffp->player_opts);
if (!ffp->aout) {
ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);
if (!ffp->aout)
return -1;
}
VideoState *is = stream_open(ffp, file_name, NULL);
if (!is) {
av_log(NULL, AV_LOG_WARNING, "ffp_prepare_async_l: stream_open failed OOM");
return EIJK_OUT_OF_MEMORY;
}
ffp->is = is;
ffp->input_filename = av_strdup(file_name);
return 0;
}
为什么我需要记录这个调用过程,在一开始的时候对FFmpg中的结构体完全不了解,去查找获取配置信息的时候我没有找到获取配置信息的地方,在看了半小时后才明白。
最关键的地方是需要明白**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);
在方法的注释上说得比较清楚了,设置字典中的所有选项,obj的起始地址必须是AVClass,这里传入的是FFplayer,它的结构体信息如下,可以看到这个是满足条件的。
typedef struct FFPlayer {
const AVClass *av_class;
..................
}
typedef struct AVClass {
/**
* The name of the class; usually it is the same name as the
* context structure type to which the AVClass is associated.
*/
const char* class_name;
/**
* A pointer to a function which returns the name of a context
* instance ctx associated with the class.
*/
const char* (*item_name)(void* ctx);
/**
* a pointer to the first option specified in the class if any or NULL
*
* @see av_set_default_options()
*/
const struct AVOption *option;
} AVClass;
其中ffp的av_class是在创建播放器的时候赋值的。
FFPlayer *ffp_create()
{
FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
if (!ffp)
return NULL;
msg_queue_init(&ffp->msg_queue);
ffp->af_mutex = SDL_CreateMutex();
ffp->vf_mutex = SDL_CreateMutex();
ffp_reset_internal(ffp);
//设置context
ffp->av_class = &ffp_context_class;
ffp->meta = ijkmeta_create();
av_opt_set_defaults(ffp);
return ffp;
}
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,
};
static const AVOption ffp_context_options[] = {
// original options in ffplay.c
// FFP_MERGE: x, y, s, fs
..................
// Android only options
{ "mediacodec", "MediaCodec: enable H264 (deprecated by 'mediacodec-avc')",
OPTION_OFFSET(mediacodec_avc), OPTION_INT(0, 0, 1) },
{ "mediacodec-auto-rotate", "MediaCodec: auto rotate frame depending on meta",
OPTION_OFFSET(mediacodec_auto_rotate), OPTION_INT(0, 0, 1) },
{ "mediacodec-all-videos", "MediaCodec: enable all videos",
OPTION_OFFSET(mediacodec_all_videos), OPTION_INT(0, 0, 1) },
{ "mediacodec-avc", "MediaCodec: enable H264",
OPTION_OFFSET(mediacodec_avc), OPTION_INT(0, 0, 1) },
{ "mediacodec-hevc", "MediaCodec: enable HEVC",
OPTION_OFFSET(mediacodec_hevc), OPTION_INT(0, 0, 1) },
{ "mediacodec-mpeg2", "MediaCodec: enable MPEG2VIDEO",
OPTION_OFFSET(mediacodec_mpeg2), OPTION_INT(0, 0, 1) },
{ "mediacodec-mpeg4", "MediaCodec: enable MPEG4",
OPTION_OFFSET(mediacodec_mpeg4), OPTION_INT(0, 0, 1) },
{ "mediacodec-handle-resolution-change", "MediaCodec: handle resolution change automatically",
OPTION_OFFSET(mediacodec_handle_resolution_change), 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) },
{ "mediacodec-sync", "mediacodec: use msg_queue for synchronise",
OPTION_OFFSET(mediacodec_sync), OPTION_INT(0, 0, 1) },
{ "mediacodec-default-name", "mediacodec default name",
OPTION_OFFSET(mediacodec_default_name), OPTION_STR(NULL) },
{ "ijkmeta-delay-init", "ijkmeta delay init",
OPTION_OFFSET(ijkmeta_delay_init), OPTION_INT(0, 0, 1) },
{ "render-wait-start", "render wait start",
OPTION_OFFSET(render_wait_start), OPTION_INT(0, 0, 1) },
{ NULL }
};
typedef struct AVOption {
const char *name;
/**
* short English help text
* @todo What about other languages?
*/
const char *help;
/**
* The offset relative to the context structure where the option
* value is stored. It should be 0 for named constants.
*/
int offset;
enum AVOptionType type;
/**
* the default value for scalar options
*/
union {
int64_t i64;
double dbl;
const char *str;
/* TODO those are unused now */
AVRational q;
} default_val;
double min; ///< minimum valid value for the option
double max; ///< maximum valid value for the option
int flags;
} AVOption;
到这里我们终于找到了默认的选项,但是针对于我们在java层设置的“mediacodec”参数,我们没有看到有哪个地方去获取,那它是如何拿到的呢?我们先上面的OPTION_OFFSET宏。
#define OPTION_OFFSET(x) offsetof(FFPlayer, x)
C 库宏 offsetof(type, member-designator) 会生成一个类型为 size_t 的整型常量,它是一个结构成员相对于结构开头的字节偏移量。成员是由 member-designator 给定的,结构的名称是在 type 中给定的。
这个宏的作用是返回结构体成员在这个结构体上的地址偏移。我们在调用av_opt_set_dict的时候,它就会将AVDictionary中的key查找出来,然后再到AVOption中匹配name,如果匹配到,那么进行赋值,这个时候将会赋值到地址偏移处,比如我们设置的mediacodec,将会赋值到FFplayer中的mediacodec_avc结构体成员之上。
typedef struct FFPlayer {
const AVClass *av_class;
..........
//解码相关配置
int mediacodec_all_videos;
int mediacodec_avc;
int mediacodec_hevc;
int mediacodec_mpeg2;
int mediacodec_mpeg4;
int mediacodec_handle_resolution_change;
int mediacodec_auto_rotate;
int opensles;
int soundtouch_enable;
........
} FFPlayer;
上面这些都是基于我自己的猜想,我也没有在网上搜索到相关有用信息,那么是真的如此么?下面来看一下FFmpeg中的方法实现。
//libavutil/opt.c
int av_opt_set_dict(void *obj, AVDictionary **options)
{
return av_opt_set_dict2(obj, options, 0);
}
int av_opt_set_dict2(void *obj, AVDictionary **options, int search_flags)
{
AVDictionaryEntry *t = NULL;
AVDictionary *tmp = NULL;
int ret = 0;
if (!options)
return 0;
//从options中取出一个个AVDictionary
while ((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX))) {
//设置参数
ret = av_opt_set(obj, t->key, t->value, search_flags);
if (ret == AVERROR_OPTION_NOT_FOUND)
ret = av_dict_set(&tmp, t->key, t->value, 0);
if (ret < 0) {
av_log(obj, AV_LOG_ERROR, "Error setting option %s to value %s.\n", t->key, t->value);
av_dict_free(&tmp);
return ret;
}
ret = 0;
}
av_dict_free(options);
*options = tmp;
return ret;
}
//libavutil/opt.c
int av_opt_set(void *obj, const char *name, const char *val, int search_flags)
{
int ret = 0;
void *dst, *target_obj;
//从obj中拿到AVOption,其实也就是从obj中的AVClass中options根据name拿到
const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);
if (!o || !target_obj)
return AVERROR_OPTION_NOT_FOUND;
if (!val && (o->type != AV_OPT_TYPE_STRING &&
o->type != AV_OPT_TYPE_PIXEL_FMT && o->type != AV_OPT_TYPE_SAMPLE_FMT &&
o->type != AV_OPT_TYPE_IMAGE_SIZE &&
o->type != AV_OPT_TYPE_DURATION && o->type != AV_OPT_TYPE_COLOR &&
o->type != AV_OPT_TYPE_CHANNEL_LAYOUT && o->type != AV_OPT_TYPE_BOOL))
return AVERROR(EINVAL);
if (o->flags & AV_OPT_FLAG_READONLY)
return AVERROR(EINVAL);
if (o->flags & AV_OPT_FLAG_DEPRECATED)
av_log(obj, AV_LOG_WARNING, "The \"%s\" option is deprecated: %s\n", name, o->help);
//有拿到的话target_obj=obj的地址,获取到地址偏移,进行变量设置
dst = ((uint8_t *)target_obj) + o->offset;
switch (o->type) {
case AV_OPT_TYPE_BOOL:
return set_string_bool(obj, o, val, dst);
case AV_OPT_TYPE_STRING:
return set_string(obj, o, val, dst);
case AV_OPT_TYPE_BINARY:
return set_string_binary(obj, o, val, dst);
case AV_OPT_TYPE_FLAGS:
case AV_OPT_TYPE_INT:
case AV_OPT_TYPE_INT64:
case AV_OPT_TYPE_UINT64:
case AV_OPT_TYPE_FLOAT:
case AV_OPT_TYPE_DOUBLE:
case AV_OPT_TYPE_RATIONAL:
return set_string_number(obj, target_obj, o, val, dst);
case AV_OPT_TYPE_IMAGE_SIZE:
return set_string_image_size(obj, o, val, dst);
case AV_OPT_TYPE_VIDEO_RATE: {
AVRational tmp;
ret = set_string_video_rate(obj, o, val, &tmp);
if (ret < 0)
return ret;
return write_number(obj, o, dst, 1, tmp.den, tmp.num);
}
case AV_OPT_TYPE_PIXEL_FMT:
return set_string_pixel_fmt(obj, o, val, dst);
case AV_OPT_TYPE_SAMPLE_FMT:
return set_string_sample_fmt(obj, o, val, dst);
case AV_OPT_TYPE_DURATION:
{
int64_t usecs = 0;
if (val) {
if ((ret = av_parse_time(&usecs, val, 1)) < 0) {
av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as duration\n", val);
return ret;
}
}
if (usecs < o->min || usecs > o->max) {
av_log(obj, AV_LOG_ERROR, "Value %f for parameter '%s' out of range [%g - %g]\n",
usecs / 1000000.0, o->name, o->min / 1000000.0, o->max / 1000000.0);
return AVERROR(ERANGE);
}
*(int64_t *)dst = usecs;
return 0;
}
case AV_OPT_TYPE_COLOR:
return set_string_color(obj, o, val, dst);
case AV_OPT_TYPE_CHANNEL_LAYOUT:
if (!val || !strcmp(val, "none")) {
*(int64_t *)dst = 0;
} else {
int64_t cl = av_get_channel_layout(val);
if (!cl) {
av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as channel layout\n", val);
ret = AVERROR(EINVAL);
}
*(int64_t *)dst = cl;
return ret;
}
break;
case AV_OPT_TYPE_DICT:
return set_string_dict(obj, o, val, dst);
}
av_log(obj, AV_LOG_ERROR, "Invalid option type.\n");
return AVERROR(EINVAL);
}
上面忽略了设置变量过程,因为我没有下载FFmpeg的代码,是直接在github上搜索的,单看这几个函数的相关细节已经大致验证了上面的猜想。
上面就是ijk在设置相关属性的流程,如有错误,欢迎指正。