今天介绍下 IjkPlayer 的播放器创建流程,本文开始将正式开始 IjkPlayer 的源码阅读之旅,主要内容如下:
- 初始化 so
- Java 层播放器创建
- IjkMediaPlayer 结构体
- Native 层播放器创建
- 调用流程图
阅读之前可以先看前面几篇文章:
初始化 so#
从 IjkPlayer 源码中VideoActivity
查看 IjkPlayer 的初始化,关键代码如下:
// 加载so库
IjkMediaPlayer.loadLibrariesOnce(null);
// android-ndk-profiler性能分析
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
查看loadLibrariesOnce
源码如下:
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
,其定义如下:
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
实现,如下:
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 方法的一一对应关系,如下:
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
的创建
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
查看构造方法如下:
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
结构体,其初始化主要是对该结构体进行初始化,其定义如下:
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
方法的具体实现如下:
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
函数如下:
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 创建主要函数调用流程如下:
其他细节会在后续的文章中介绍,下一篇将介绍 IjkPlayer 中消息循环机制。