一、ijkplayer 初始化流程

本文是基于 A4ijkplayer 项目进行 ijkplayer 源码分析,该项目是将 ijkplayer 改成基于 CMake 编译,可导入 Android Studio 编译运行,方便代码查找、函数跳转、单步调试、调用栈跟踪等。
初始化完成的主要工作是创建播放器对象:IjkMediaPlayer 。的其提供了两种形式的构造函数,区别在于是否传参 IjkLibLoader。默认不传,即使用默认的 System.loadLibrary(libName),两种构造函数最终都是调用 initPlayer() 函数初始化播放器。该函数内做了四件事:
- 加载 native 动态库:loadLibrariesOnce()
加载动态库,动态注册 JNI 函数,以及其他一些初始化操作 - 初始化 native 资源:initNativeOnce()
ijkplayer 源码中在该方法中并没有做什么操作 - 创建事件处理的 EventHandler
创建 java 层的 handler ,用于接收和处理 native 层回调上来的消息事件 - 设置 native 资源:native_setup()
创建 native IjkMediaPlayer 实例,创建消息队列,指定 msg_loop,创建视频渲染对象 SDL_Vout,创建平台相关的 IJKFF_Pipeline(包含视频解码和音频输出)等
public IjkMediaPlayer() {
this(sLocalLibLoader);
}
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(new WeakReference<IjkMediaPlayer>(this));
}
1、loadLibrariesOnce()
该函数是静态方法,整个进程的生命周期内只调用一次,用于加载所需要依赖的 natvie 动态库。ijkplayer 源码是加载 ijkffmpeg、ijksdl、ijkplayer 这个三个动态库,我为了方便调试和跟进 ijkplayer 源码,将其改成 CMake 方式编译,在 CMakeList 中将 ijksdl 和 ijkplayer 的源码编译到了同一个动态库 a4ijkplayer 中,所以调用如下:
private static volatile boolean mIsLibLoaded = false;
public static void loadLibrariesOnce(IjkLibLoader libLoader) {
synchronized (IjkMediaPlayer.class) {
if (!mIsLibLoaded) {
if (libLoader == null)
libLoader = sLocalLibLoader;
libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("a4ijkplayer");
// libLoader.loadLibrary("ijksdl");
// libLoader.loadLibrary("ijkplayer");
mIsLibLoaded = true;
}
}
}
loadLibrary 加载动态库时,会调用每个库 JNI 的 JNI_OnLoad() 方法,卸载时会调用 JNI_UnLoad()。
1.1 调用 libLoader.loadLibrary(“ijksdl”)
在加载 libijksdl.so 动态库时,会调用 ijkmedia/ijksdl/android/ijksdl_android_jni.c 文件中 JNI_OnLoad() 方法。
注:我在 A4ijkplayer项目中将 ijksdl 和 ijkplayer 的源码编译到了同一个动态库 a4ijkplayer 中,不会调用 libLoader.loadLibrary(“ijksdl”) 因而不会调用 ijksdl_android_jni.c 中的 JNI_OnLoad() 方法。为解决该问题,将其改名为 SDL_JNI_OnLoad(),然后在 ijkplayer_jni.c 文件的 JNI_OnLoad() 方法调用改名后的 SDL_JNI_OnLoad() 从而保证 SDL 库的原有 JNI_OnLoad() 中的相关方法能被调用到,相关修改见:这个提交
JNIEXPORT jint JNICALL SDL_JNI_OnLoad(JavaVM *vm, void *reserved)
{
int retval;
JNIEnv* env = NULL;
g_jvm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
retval = J4A_LoadAll__catchAll(env);
JNI_CHECK_RET(retval == 0, env, NULL, NULL, -1);
return JNI_VERSION_1_4;
}
1.2 调用 libLoader.loadLibrary(“a4ijkplayer”)
Ijkplayer 原本代码时调用 libLoader.loadLibrary(“ijkplayer”) , A4ijkplayer 项目中是加载合二为一的 liba4ijkplayer.so ,接着会调用 ijkmedia/ijkplayer/android/ijkplayer_jni.c 文件中 JNI_OnLoad() 方法,如下:
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);
// 因为把 sdl 和 ijkplayer 和成一个 so 了,不会调用 sdl 原本的 JNI_OnLoad,所以需在这里主动调用
jint result = SDL_JNI_OnLoad(vm, reserved);
if (result < 0) {
return result;
}
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;
}
这里主要是动态注册 JNI 函数,映射关联 Java 和 jni 相关函数(RegisterNatives),以及其他一些初始化操作。
static JNINativeMethod g_methods[] = {
// ...
{
"_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
{
"_start", "()V", (void *) IjkMediaPlayer_start },
// ...
{
"native_init",

文章详细介绍了ijkplayer在Android环境下的初始化过程,包括加载动态库、注册JNI函数、创建EventHandler以及设置native资源。重点讨论了ijkplayer如何通过CMake集成到AndroidStudio,以及ijkplayer初始化时如何加载ijkffmpeg、ijksdl、ijkplayer动态库,如何调用JNI_OnLoad方法,以及在native_setup阶段创建播放器对象和相关组件。
最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



