IjkPlayer系列之播放器创建流程

今天介绍下 IjkPlayer 的播放器创建流程，本文开始将正式开始 IjkPlayer 的源码阅读之旅，主要内容如下：

初始化 so

Java 层播放器创建

IjkMediaPlayer 结构体

Native 层播放器创建

调用流程图

阅读之前可以先看前面几篇文章：

如何在 Ubuntu 下编译 ijkplayer

IjkPlayer 系列之播放器创建流程

初始化 so

从 IjkPlayer 源码中VideoActivity查看 IjkPlayer 的初始化，关键代码如下：

Copy
// 加载so库
IjkMediaPlayer.loadLibrariesOnce(null);
// android-ndk-profiler性能分析
IjkMediaPlayer.native_profileBegin("libijkplayer.so");

查看loadLibrariesOnce源码如下：

Copy
private static volatile boolean mIsLibLoaded = false;
public static void loadLibrariesOnce(IjkLibLoader libLoader) {
synchronized (IjkMediaPlayer.class) {
// 保证只加载一次
if (!mIsLibLoaded) {
if (libLoader == null)
// sLocalLibLoader为默认的IjkLibLoader
libLoader = sLocalLibLoader;
libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("ijksdl");
libLoader.loadLibrary("ijkplayer");
mIsLibLoaded = true;
}
}
}

loadLibrariesOnce 加载相应的 so 库，native_profileBegin 则是 android-ndk-profiler 工具进行性能分析的函数，对应的还有native_profileEnd，其定义如下：

Copy
public static native void native_profileBegin(String libName);
public static native void native_profileEnd();

上述方法分别调用了函数 monstartup 和 moncleanup，分别在程序运行开始和结束调用，关于 android-ndk-profiler 的这里不做过多介绍。

在 IjkPlayer 系列之 JNI 基础及源码目录介绍 这篇文章中介绍了 JNI 的注册方式，其中 IjkPlayer 使用的就是其动态注册方式，即当调用 System.loadLibrary 加载库的时候会去查找 JNI_OnLoad 这个函数，然后在该函数回调中进行注册，下面看下 ijkplayer 中的 JNI_OnLoad 实现，如下：

Copy
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
// 保存全局的JavaVM
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 );
// 将IjkMediaPlayer转换为一个全局引用
IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
// 注册Native方法与Java方法相对应
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
// 初始化：注册编解码器、解复用器、协议
ijkmp_global_init();
// 设置回调，对应Java层的onNativeInvoke回调函数
ijkmp_global_set_inject_callback(inject_callback);
// 注册av_base64_encode与FFmpegApi_av_base64_encode的对应关系
FFmpegApi_global_init(env);
return JNI_VERSION_1_4;
}

其中数组 g_methods 定义了 Native 函数与 Java 方法的一一对应关系，如下：

Copy
static JNINativeMethod g_methods[] = {
{
"_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
(void *) IjkMediaPlayer_setDataSourceAndHeaders
},
{ "_setDataSourceFd", "(I)V", (void *) IjkMediaPlayer_setDataSourceFd },
{ "_setDataSource", "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
{ "_setAndroidIOCallback", "(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },

{ "_setVideoSurface", "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
{ "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
{ "_start", "()V", (void *) IjkMediaPlayer_start },
{ "_stop", "()V", (void *) IjkMediaPlayer_stop },
{ "seekTo", "(J)V", (void *) IjkMediaPlayer_seekTo },
{ "_pause", "()V", (void *) IjkMediaPlayer_pause },
{ "isPlaying", "()Z", (void *) IjkMediaPlayer_isPlaying },
{ "getCurrentPosition", "()J", (void *) IjkMediaPlayer_getCurrentPosition },
{ "getDuration", "()J", (void *) IjkMediaPlayer_getDuration },
{ "_release", "()V", (void *) IjkMediaPlayer_release },
{ "_reset", "()V", (void *) IjkMediaPlayer_reset },
{ "setVolume", "(FF)V", (void *) IjkMediaPlayer_setVolume },
{ "getAudioSessionId", "()I", (void *) IjkMediaPlayer_getAudioSessionId },
{ "native_init", "()V", (void *) IjkMediaPlayer_native_init },
{ "native_setup", "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
{ "native_finalize", "()V", (void *) IjkMediaPlayer_native_finalize },

{ "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
{ "_setOption", "(ILjava/lang/String;J)V", (void *) IjkMediaPlayer_setOptionLong },

{ "_getColorFormatName", "(I)Ljava/lang/String;", (void *) IjkMediaPlayer_getColorFormatName },
{ "_getVideoCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getVideoCodecInfo },
{ "_getAudioCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getAudioCodecInfo },
{ "_getMediaMeta", "()Landroid/os/Bundle;", (void *) IjkMediaPlayer_getMediaMeta },
{ "_setLoopCount", "(I)V", (void *) IjkMediaPlayer_setLoopCount },
{ "_getLoopCount", "()I", (void *) IjkMediaPlayer_getLoopCount },
{ "_getPropertyFloat", "(IF)F", (void *) ijkMediaPlayer_getPropertyFloat },
{ "_setPropertyFloat", "(IF)V", (void *) ijkMediaPlayer_setPropertyFloat },
{ "_getPropertyLong", "(IJ)J", (void *) ijkMediaPlayer_getPropertyLong },
{ "_setPropertyLong", "(IJ)V", (void *) ijkMediaPlayer_setPropertyLong },
{ "_setStreamSelected", "(IZ)V", (void *) ijkMediaPlayer_setStreamSelected },

{ "native_profileBegin", "(Ljava/lang/String;)V", (void *) IjkMediaPlayer_native_profileBegin },
{ "native_profileEnd", "()V", (void *) IjkMediaPlayer_native_profileEnd },

{ "native_setLogLevel", "(I)V", (void *) IjkMediaPlayer_native_setLogLevel },
{ "_setFrameAtTime", "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
};

上述代码主要工作如下：

注册 Native 方法与 Java 方法之间的对应关系。

注册编解码器、解复用器、协议等。

设置 onNativeInvoke 回调供 Native 层调用，主要关心其返回值，比如断网重连等。

Java 层播放器创建

看下 Java 层 IjkMediaPlayer 的创建

Copy
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();

查看构造方法如下：

Copy
public IjkMediaPlayer() {
// sLocalLibLoader为默认的IjkLibLoader
this(sLocalLibLoader);
}

public IjkMediaPlayer(IjkLibLoader libLoader) {
initPlayer(libLoader);
}

private void initPlayer(IjkLibLoader libLoader) {
// 尝试加载库，避免之前从未加载过so
loadLibrariesOnce(libLoader);
// 对应c层IjkMediaPlayer_native_init方法，暂时为空实现
initNativeOnce();

// 用来处理Iik底层传递上来的消息
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;
}

// IjkMediaPlayer包装成弱引用传递到native层
native_setup(new WeakReference<IjkMediaPlayer>(this));
}

可见初始化的时候加载了 ijkffmpeg、ijksdl、ijkplayer 库，创建了 mEventHandler 处理 Native 拋上来的播放事件，比如播放开始、播放完成、播放出错等事件，具体由 Native 层调用 Java 层的函数 postEventFromNative 来发送对应的事件，在阅读 C 层代码之前，先了解一下 IjkMediaPlayer 结构体。

IjkMediaPlayer 结构体

IjkPlayer 对应 IjkMediaPlayer 结构体，其初始化主要是对该结构体进行初始化，其定义如下：

Copy
struct IjkMediaPlayer {
/* IjkMediaPlayer创建一次则ref_count计数加一次 */
volatile int ref_count;
/* 保护接口调用的锁*/
pthread_mutex_t mutex;
/* FFPlayer是原ffplayer里的结构体，有被ijk扩展 */
FFPlayer *ffplayer;
/* 用于ijkPlayer回调给应用层的一个消息循环函数 */
int (*msg_loop)(void*);
/* 消息线程 */
SDL_Thread *msg_thread;
SDL_Thread _msg_thread;
/* 播放器状态 */
int mp_state;
/* 播放地址 */
char *data_source;
/* Java层IjkMediaPlayer对应弱引用对象 */
void *weak_thiz;
/* 是否重新播放 */
int restart;
/* restart是否从头开始 */
int restart_from_beginning;
/* 标识用户是不是seek了进度条 */
int seek_req;
/* seek的毫秒值 */
long seek_msec;
};

整个播放流程中涉及到的参数基本都是 IjkMediaPlayer 结构体中的成员变量，后面 Native 层 IjkPlayer 的创建都是在初始化上述结构体中的成员变量。

Native 层播放器创建

Native 层播放器的创建主要是结构体 IjkMediaPlayer 的创建及初始化，这里接着上文继续查看 native_setup 方法的具体实现如下：

Copy
static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
MPTRACE("%s\n", __func__);
// 创建C层对应的IjkMediaPlayer
IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
// Java层mNativeMediaPlayer初始化
jni_set_media_player(env, thiz, mp);
// 初始化mp->weak_thiz
ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
// 初始化ffp->inject_opaque等
ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
// 初始化ffp->ijkio_inject_opaque等
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);
}

上述代码主要是开始创建 C 层的 IjkMediaPlayer 、其对应结构体部分属性的初始化及解码器回调，mediacodec_select_callback 会通过调用 Java 层的函数 onSelectCodec 获取合适的解码器信息，可在 IjkMediaCodecInfo 中进行解码器的适配。

继续查看 ijkmp_android_create 函数如下：

Copy
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
// 填充IjkMediaPlayer结构体
IjkMediaPlayer *mp = ijkmp_create(msg_loop);
if (!mp)
goto fail;
// 初始化SDL_Vout，表示IJK中的显示上下文
mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
if (!mp->ffplayer->vout)
goto fail;
// 初始化IJKFF_Pipeline，解码器、音频输出
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
if (!mp->ffplayer->pipeline)
goto fail;
// 绑定SDL_Vout到IJKFF_Pipeline_Opaque
ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
return mp;
fail:
ijkmp_dec_ref_p(&mp);
return NULL;
}

其中 ijkmp_create 主要是创建了 FFPlayer，并将 msg_loop 进行赋值，msg_loop 事件循环相关调用将在后续文章中介绍，继续看下后面几个函数：

SDL_VoutAndroid_CreateForAndroidSurface：初始化 IjkPlayer 的 显示上下文 SDL_Vout。

ffpipeline_create_from_android: 初始化IJKFF_Pipeline，解码器、音频输出。

ffpipeline_set_vout：绑定SDL_Vout到IJKFF_Pipeline_Opaque。

调用流程图

IjkPlayer 创建主要函数调用流程如下：

sequenceDiagram IjkMediaPlayer.java->>ijkplayer_jni.c: IjkMediaPlayer_native_setup ijkplayer_jni.c->>ijkplayer_android.c: ijkmp_android_create ijkplayer_jni.c->>ijkplayer_android.c: ijkmp_xxx ijkplayer_android.c->>ijkplayer.c:ijkmp_create ijkplayer_android.c->>ijkplayer.c:SDL_VoutAndroid_CreateForAndroidSurface ijkplayer_android.c->>ijkplayer.c:ffpipeline_create_from_android ijkplayer_android.c->>ijkplayer.c:ffpipeline_set_vout ijkplayer.c->>ff_ffplay.c:ffp_create

其他细节会在后续的文章中介绍，下一篇将介绍 IjkPlayer 中消息循环机制。