这篇文章我们看下java层设置了选项,底层是如何流转的。
android的上层,有个接口文件
android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java
IMediaPlayer.java提供了ijk可以使用的操作与回调。
看到里面对播放源地址的设置有3个函数
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException;
void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
IjkPlayer.java实现了IMediaPlayer.java
@Override
public void setDataSource(String path, Map<String, String> headers) {
try {
Uri uri = Uri.parse(path);
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
RawDataSourceProvider rawDataSourceProvider = RawDataSourceProvider.create(mAppContext, uri);
mMediaPlayer.setDataSource(rawDataSourceProvider);
} else {
//处理UA问题
if (headers != null) {
String userAgent = headers.get("User-Agent");
if (!TextUtils.isEmpty(userAgent)) {
mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent);
// 移除header中的User-Agent,防止重复
headers.remove("User-Agent");
}
}
mMediaPlayer.setDataSource(mAppContext, uri, headers);
}
} catch (Exception e) {
mPlayerEventListener.onError();
}
}
我们看到了对UA的操作
//处理UA问题
if (headers != null) {
String userAgent = headers.get("User-Agent");
if (!TextUtils.isEmpty(userAgent)) {
mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent);
// 移除header中的User-Agent,防止重复
headers.remove("User-Agent");
}
}
我们看下 mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent);
IjkMediaPlayer.java
public void setOption(int category, String name, String 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);
进入ijk内核
src/main/jni/ijkmedia/ijkplayer/android/ijkplayer_jni.c
#define JNI_CLASS_IJKPLAYER "tv/danmaku/ijk/media/player/IjkMediaPlayer"
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
g_jvm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
pthread_mutex_init(&g_clazz.mutex, NULL );
// FindClass returns LocalReference
IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
ijkmp_global_init();
ijkmp_global_set_inject_callback(inject_callback);
FFmpegApi_global_init(env);
return JNI_VERSION_1_4;
}
// ----------------------------------------------------------------------------
static JNINativeMethod g_methods[] = {
......
{ "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
{ "_setOption", "(ILjava/lang/String;J)V", (void *) IjkMediaPlayer_setOptionLong },
......
};
最后走到了
IjkMediaPlayer_setOption(JNIEnv *env, jobject thiz, jint category, jobject name, jobject value)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
const char *c_name = NULL;
const char *c_value = NULL;
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: setOption: null mp", LABEL_RETURN);
if (!name) {
goto LABEL_RETURN;
}
c_name = (*env)->GetStringUTFChars(env, name, NULL );
JNI_CHECK_GOTO(c_name, env, "java/lang/OutOfMemoryError", "mpjni: setOption: name.string oom", LABEL_RETURN);
JNI_CHECK_GOTO(mp, env, NULL, "mpjni: IjkMediaPlayer_setOption: null name", LABEL_RETURN);
if (value) {
c_value = (*env)->GetStringUTFChars(env, value, NULL );
JNI_CHECK_GOTO(c_name, env, "java/lang/OutOfMemoryError", "mpjni: setOption: name.string oom", LABEL_RETURN);
}
ijkmp_set_option(mp, category, c_name, c_value);
LABEL_RETURN:
if (c_name)
(*env)->ReleaseStringUTFChars(env, name, c_name);
if (c_value)
(*env)->ReleaseStringUTFChars(env, value, c_value);
ijkmp_dec_ref_p(&mp);
}
再走到了
ijkplayer.c
void ijkmp_set_option(IjkMediaPlayer *mp, int opt_category, const char *name, const char *value)
{
assert(mp);
// MPTRACE("%s(%s, %s)\n", __func__, name, value);
pthread_mutex_lock(&mp->mutex);
ffp_set_option(mp->ffplayer, opt_category, name, value);
pthread_mutex_unlock(&mp->mutex);
// MPTRACE("%s()=void\n", __func__);
}
这里也就是加锁然后进行设置
ff_ffplay.c
void ffp_set_option(FFPlayer *ffp, int opt_category, const char *name, const char *value)
{
if (!ffp)
return;
AVDictionary **dict = ffp_get_opt_dict(ffp, opt_category);
av_dict_set(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;
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;
}
}
我们从这里可以看到FFPlayer有几大类option
/* format/codec options */
AVDictionary *format_opts;
格式option,这个用在avformat_open_input打开流的时候
AVDictionary *codec_opts; 解码option
AVDictionary *sws_dict; 图像格式转化
AVDictionary *player_opts; 播放option
AVDictionary *swr_opts; 音频重采样
AVDictionary *swr_preset_opts; 音频预设option
我们通过上面的代买已经将UA设置到了FFPlayer的format_opts
那么什么时候使用呢。
我们通过ijkplayer源码---解封装(remuxing)_qq_15255121的专栏-CSDN博客
最后走到了read_thread
static int read_thread(void *arg)
{
......
err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
......
}
这样我们就把设置的参数传到了ffmpeg当中了。