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

本文详细分析了A4ijkplayer(ijkplayer的CMake版)的初始化流程,包括loadLibrariesOnce()的动态库加载,initNativeOnce()的资源初始化,EventHandler的创建,以及ijkmp_native_setup()中涉及的视频渲染对象创建、FFmpeg相关管道设置等关键步骤。同时介绍了如何在A4ijkplayer项目中解决ijkplayer和ijksdl动态库的加载问题。
摘要由CSDN通过智能技术生成

1、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));
}

2、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()。

2.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;
}

2.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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值