ijkplayer 源码分析(1):初始化流程

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

一、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",      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值