1. Java layer calls
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);
}
Copy the code
2. Walk to the JNI layer
/ / the jni calls
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__);
}
Copy the code
3. Go to FFPlay and add it to the OPTS corresponding to FFP
//ff_play.c
void ffp_set_option_int(FFPlayer *ffp, int opt_category, const char *name, int64_t value)
{
if(! ffp)return;
// Find the AVDictionary you want to put in
AVDictionary **dict = ffp_get_opt_dict(ffp, opt_category);
// dictionary storage in 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;
// The Java layer input value is 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; }}Copy the code
You can set the parameters in iJK in the same way as FFmpeg. You need to know about the structures and operations in FFmpeg.
// Parameter configurations are stored in key-value format
typedef struct AVDictionaryEntry {
char *key;
char *value;
} AVDictionaryEntry;
// How to set parameters
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);
Copy the code
4. Set when you are ready to play
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:"; }}// Set the dictionary contents to 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;
}
Copy the code
Why do I need to record this call process? At the beginning, I had no idea about the structure in FFmpg. When I searched for the configuration information, I failed to find the place to obtain the configuration information.
The most important thing to understand is that **av_opt_set_dict(FFP, & FFP ->player_opts)** what this call does:
/** * 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);
Copy the code
To set all the options in the dictionary, obj must start with AVClass. In this case, FFplayer is passed in, and its structure is as follows. You can see that this satisfies the condition.
typedef struct FFPlayer {
constAVClass *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;
Copy the code
The AV_class of FFP is assigned when the player is created.
FFPlayer *ffp_create(a)
{
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);
/ / set the 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,
// Default configuration item
.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;
Copy the code
Here we finally find the default option, but for the “mediacodec” parameter we set in the Java layer, we don’t see anywhere to get it, so how did it get there? Let’s start with the OPTION_OFFSET macro above.
#define OPTION_OFFSET(x) offsetof(FFPlayer, x)The C library macro offsetof(type, member-Designator) generates a type ofsize_tInteger constant, which is the byte offset of a structure member relative to the beginning of the structure. Members are given by member-Designator, and the name of the structure is given in Type.Copy the code
This macro returns the address offset of a structure member on the structure. When we call av_opt_set_dict, it will look up the key in the AVDictionary, and then match the name in AVOption. If it matches, it will assign the value to the address offset, such as mediacodec. The value will be assigned to the mediacodec_avc structure member in FFplayer.
typedef struct FFPlayer {
constAVClass *av_class; .// Decode related configurations
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;
intsoundtouch_enable; . } FFPlayer;Copy the code
All of the above is based on my own guess, and I haven’t found any useful information on the Internet, so is it true? Let’s look at the method implementation in 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;
// Retrieve an AVDictionary from options
while ((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX))) {
// Set parameters
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;
// Get AVOption from obj (options from AVClass in obj) by 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 address, address offset, variable set
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);
returnret; }}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);
returnAVERROR(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);
}
Copy the code
The variable setting process is ignored above, because I did not download the FFmpeg code, but directly searched on Github. Just looking at the details of these functions has roughly verified the above conjecture.
The above is the process of IJK setting related attributes. If there is any error, please correct it.