还没有看过前置知识的同学可以先从下面的链接点过去:
音视频ijkplayer源码解析系列4--ijkplayer里面如何使用SDL渲染
我们在前面的文章中分析了ijkplayer
中的解码和渲染流程,但在ijkplayer
播放器中还有一个十分重要的环节:初始化部分,初始化的逻辑十分的多,其中关键的就是音频和视频解码器的初始化。
在进行初始化代码解析之前,我们需要澄清下几个同名的类,细心的同学会发现,在ijkplayer
播放器源码中出现了多个IjkMediaPlayer
的类
tv.danmaku.ijk.media.player.IjkMediaPlayer.java
:这个类是java层播放器的类,也是所有使用ijkplayer
播放器的关键类ijkmedia/ijkplayer/ijkplayer_internal.h
#IjkMediaPlayer:这个类是c层的播放器类,c层使用播放器都需要借助这个类ijkmedia/ijkj4a/j4a/class/tv/danmaku/ijk/media/player/IjkMediaPlayer.c#J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer
:这个类是jni层的中介类,它暴露相关成员变量的get和set属性,这些属性的地址就是java层的IjkMediaPlayer.java
里的成员变量,存的就是c层创建的播放器类。经过上述的描述,它的作用也显而易见:它连接了java层的IjkMediaPlayer.java
和c层的IjkMediaPlayer
,让c层创建出来的播放器实例以成员变量的形式,都存在java层的播放器内,这样任何使用想使用这些成员量或者想通信的时候,都可以借助这个jni层的中介,同时也让这些成员变量变的更好管理。
简单介绍完了这几个同名类,我们开始正式讲解播放器的初始化相关细节喽:
在java层播放器的类名IjkMediaPlayer
,在new这个IjkMediaPlayer
对象的时候,其实走的就是它的构建方法
cpp
public IjkMediaPlayer(IjkLibLoader libLoader) {
initPlayer(libLoader);
}
private void initPlayer(IjkLibLoader libLoader) {
loadLibrariesOnce(libLoader);
initNativeOnce();
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
/*
* Native setup requires a weak reference to our object. It's easier to
* create it here than in C++.
*/
native_setup(new WeakReference<IjkMediaPlayer>(this));
}
对应上述方法,其构建方法就是:
- 指定so库的加载:ijkffmpeg.so、ijksdl.so、ijkplayer.so
- 使用当前的
Looper
,创建EventHandler
对象,这个EventHandler
是播放器自定义的Handler类,用来传递播放器cpp层的消息至java层 - 执行
native_setup(new WeakReference<IjkMediaPlayer>(this))
方法,此时函数执行就来到了jni层的ijkplayer_jni#IjkMediaPlayer_native_setup
方法
接下来,我们展开研究下第三步的函数IjkMediaPlayer_native_setup
:
cpp
static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
jni_set_media_player(env, thiz, mp);
ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
从上面的源码中我们可以发现,其核心逻辑:
- 调用
ijkmp_android_create
创建IjkMediaPlayer
实例,这个对象就是c层播放器的实例 - 保存最新创建的播放器实例
- 保存缓存与引用
接下来我们将上述的三个步骤层层拆解
1 IjkMediaPlayer实例创建
IjkMediaPlayer
实例的创建是借助ijkmp_android_create
函数实现的,接下来我们拆解下这个函数:
cpp
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
IjkMediaPlayer *mp = ijkmp_create(msg_loop);
if (!mp)
goto fail;
mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
if (!mp->ffplayer->vout)
goto fail;
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
if (!mp->ffplayer->pipeline)
goto fail;
ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
return mp;
fail:
ijkmp_dec_ref_p(&mp);
return NULL;
}
对应上述的代码,我们发现其最主要的工作就是:
-
调用
ijkmp_create
创建IjkMediaPlayer
实例,其内部实现主要是:-
先是调用
mallocz
分配IjkMediaPlayer
对象空间:cppIjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
-
然后创建
FFPlayer
对象,并保存到mp中:cppmp->ffplayer = ffp_create(); // ffp_create其实关键逻辑就是mallocz创建FFPlayer的对象空间
-
最后将java层传递过来的looper保存在播放器实例里:
cppmp->msg_loop = msg_loop;
-
-
调用
SDL_VoutAndroid_CreateForAndroidSurface
创建SDL_Vout
实例,然后保存在mp->ffplayer
里面。那么SDL_Vout
是个什么东西呢,细心的同学会发现还有个SDL_Aout
对象,他是干什么用的呢 -
调用
ffpipeline_create_from_android
创建IJKFF_Pipeline
对象并保存在mp->ffplayer
里面。 -
最后调用
ffpipeline_set_vout
创建出来的SDL_Vout
实例的弱引用保存在pipeline->opaque->weak_vout
里面
经过了上述几个步骤,我们也就把IjkMediaPlayer
对象基本创建完成了。
2 保存播放器实例
这里我们要引入一个新的类ijkplayer-arm64/src/main/jni/ijkmedia/ijkj4a/j4a/class/tv/danmaku/ijk/media/player/IjkMediaPlayer.c
,
java层也有一个同名的播放器类名tv/danmaku/ijk/media/player/IjkMediaPlayer.java
,类名都是一样的,一个表示java层的播放器实例,而一个表示c层的播放器实例。后续这个类会频繁出现,我会标注清楚是c层的还是java层的,大家要注意分辨。
这个类的关键定义和实例化:
cpp
typedef struct J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer {
jclass id;
jfieldID field_mNativeMediaPlayer;
jfieldID field_mNativeMediaDataSource;
jfieldID field_mNativeAndroidIO;
jmethodID method_postEventFromNative;
jmethodID method_onSelectCodec;
jmethodID method_onNativeInvoke;
} J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer;
static J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer;
从cpp的写法来说,static J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer;
这种写法就是全局变量,在类加载的时候就创建了这个全部变量。这个全部变量里面成员变量的作用为:
- id:对应java层的
tv/danmaku/ijk/media/player/IjkMediaPlayer
类,是通过jni的findclass方法获取到的jclass类 - java层的成员变量映射关系:
- field_mNativeMediaPlayer:则是对应的了java层
IjkMediaPlayer
类里的成员变量mNativeMediaPlayer
- field_mNativeMediaDataSource:则是对应的了java层
IjkMediaPlayer
类里的成员变量mNativeMediaDataSource
- field_mNativeAndroidIO:对应了ava层
IjkMediaPlayer
类里的成员变量mNativeAndroidIO
- field_mNativeMediaPlayer:则是对应的了java层
- java层的方法映射关系:
- method_postEventFromNative:对应了java层
IjkMediaPlayer
类里的方法postEventFromNative
- method_onSelectCodec:对应了java层
IjkMediaPlayer
类里的方法onSelectCodec
- method_onNativeInvoke:对应了java层
IjkMediaPlayer
类里的方法onNativeInvoke
- method_postEventFromNative:对应了java层
我们在1.1 IjkMediaPlayer实例创建
中描述了创建c层IjkMediaPlayer
对象的完整过程,此时"保存播放器实例"到底是存到哪了呢,它的函数调用关系是这样的:
cpp
jni_set_media_player(env, thiz, mp);
->J4AC_IjkMediaPlayer__mNativeMediaPlayer__set__catchAll(env, thiz, (intptr_t) mp);
->J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__mNativeMediaPlayer__set
->(*env)->SetLongField(env, thiz, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.field_mNativeMediaPlayer, value);
我们可以发现在c层创建的ijkplayer_internal.h#IjkMediaPlayer
对象存放到了field_mNativeMediaPlayer
,其实也就是java层IjkMediaPlayer.java
对象的mNativeMediaPlayer
成员变量里
3 保存缓存与引用
在执行完上面两步以后,会将java层IjkMediaPlayer.java
对象的弱引用保存在c层IjkMediaPlayer
里面的这几个地方
mp->weak_thiz
,赋值代码:
cpp
ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
mp->ffp->inject_opaque
里,同时再进行ffmpeg环境的初始化,赋值代码:
cpp
ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
mp->ffp->ijkio_inject_opaque
里,赋值代码:
cpp
ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
最后还会将编码器的回调mediacodec_select_callback
和java层IjkMediaPlayer.java
对象的弱引用都保存在mp->ffplayer->pipeline
里,赋值代码如下:
cpp
ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));
至此初始化的部分处理完成。