IjkPlayer系列之播放器创建流程

躬行之

共 10263字,需浏览 21分钟

 ·

2022-04-01 21:17

7c140c186a54f1470b999f0987fa2873.webp今天介绍下 IjkPlayer 的播放器创建流程,本文开始将正式开始 IjkPlayer 的源码阅读之旅,阅读之前可以先看前面同系列文章:
主要内容如下:
  1. 初始化so

  2. Java层播放器创建

  3. IjkMediaPlayer结构体

  4. Native层播放器创建

  5. 调用流程图

初始化so

从 IjkPlayer 源码中VideoActivity查看 IjkPlayer 的初始化,关键代码如下:
1// 加载so库
2IjkMediaPlayer.loadLibrariesOnce(null);
3// android-ndk-profiler性能分析
4IjkMediaPlayer.native_profileBegin("libijkplayer.so");
查看loadLibrariesOnce源码如下:
 1private static volatile boolean mIsLibLoaded = false;
2public static void loadLibrariesOnce(IjkLibLoader libLoader) {
3    synchronized (IjkMediaPlayer.class) {
4        // 保证只加载一次
5        if (!mIsLibLoaded) {
6            if (libLoader == null)
7                // sLocalLibLoader为默认的IjkLibLoader
8                libLoader = sLocalLibLoader;
9            libLoader.loadLibrary("ijkffmpeg");
10            libLoader.loadLibrary("ijksdl");
11            libLoader.loadLibrary("ijkplayer");
12            mIsLibLoaded = true;
13        }
14    }
15}
loadLibrariesOnce 加载相应的 so 库,native_profileBegin 则是 android-ndk-profiler 工具进行性能分析的函数,对应的还有native_profileEnd,其定义如下:
1public static native void native_profileBegin(String libName);
2public static native void native_profileEnd();
上述方法分别调用了函数 monstartupmoncleanup,分别在程序运行开始和结束调用,关于 android-ndk-profiler 的这里不做过多介绍。在 IjkPlayer系列之JNI基础及源码目录介绍 这篇文章中介绍了 JNI 的注册方式,其中 IjkPlayer 使用的就是其动态注册方式,即当调用 System.loadLibrary 加载库的时候会去查找 JNI_OnLoad 这个函数,然后在该函数回调中进行注册,下面看下 ijkplayer 中的 JNI_OnLoad 实现,如下:
 1JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
2
{
3    JNIEnv* env = NULL;
4    // 保存全局的JavaVM
5    g_jvm = vm;
6    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
7        return -1;
8    }
9    assert(env != NULL);
10    // 互斥锁的初始化
11    pthread_mutex_init(&g_clazz.mutex, NULL );
12    // 将IjkMediaPlayer转换为一个全局引用
13    IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
14    // 注册Native方法与Java方法相对应
15    (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
16    // 初始化:注册编解码器、解复用器、协议
17    ijkmp_global_init();
18    // 设置回调,对应Java层的onNativeInvoke回调函数
19    ijkmp_global_set_inject_callback(inject_callback);
20    // 注册av_base64_encode与FFmpegApi_av_base64_encode的对应关系
21    FFmpegApi_global_init(env);
22    return JNI_VERSION_1_4;
23}
其中数组 g_methods 定义了 Native 函数与 Java 方法的一一对应关系,如下:
 1static JNINativeMethod g_methods[] = {
2    {
3        "_setDataSource",
4        "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
5        (void *) IjkMediaPlayer_setDataSourceAndHeaders
6    },
7    { "_setDataSourceFd",       "(I)V",     (void *) IjkMediaPlayer_setDataSourceFd },
8    { "_setDataSource",         "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
9    { "_setAndroidIOCallback",  "(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },
10
11    { "_setVideoSurface",       "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
12    { "_prepareAsync",          "()V",      (void *) IjkMediaPlayer_prepareAsync },
13    { "_start",                 "()V",      (void *) IjkMediaPlayer_start },
14    { "_stop",                  "()V",      (void *) IjkMediaPlayer_stop },
15    { "seekTo",                 "(J)V",     (void *) IjkMediaPlayer_seekTo },
16    { "_pause",                 "()V",      (void *) IjkMediaPlayer_pause },
17    { "isPlaying",              "()Z",      (void *) IjkMediaPlayer_isPlaying },
18    { "getCurrentPosition",     "()J",      (void *) IjkMediaPlayer_getCurrentPosition },
19    { "getDuration",            "()J",      (void *) IjkMediaPlayer_getDuration },
20    { "_release",               "()V",      (void *) IjkMediaPlayer_release },
21    { "_reset",                 "()V",      (void *) IjkMediaPlayer_reset },
22    { "setVolume",              "(FF)V",    (void *) IjkMediaPlayer_setVolume },
23    { "getAudioSessionId",      "()I",      (void *) IjkMediaPlayer_getAudioSessionId },
24    { "native_init",            "()V",      (void *) IjkMediaPlayer_native_init },
25    { "native_setup",           "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
26    { "native_finalize",        "()V",      (void *) IjkMediaPlayer_native_finalize },
27
28    { "_setOption",             "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
29    { "_setOption",             "(ILjava/lang/String;J)V",                  (void *) IjkMediaPlayer_setOptionLong },
30
31    { "_getColorFormatName",    "(I)Ljava/lang/String;",    (void *) IjkMediaPlayer_getColorFormatName },
32    { "_getVideoCodecInfo",     "()Ljava/lang/String;",     (void *) IjkMediaPlayer_getVideoCodecInfo },
33    { "_getAudioCodecInfo",     "()Ljava/lang/String;",     (void *) IjkMediaPlayer_getAudioCodecInfo },
34    { "_getMediaMeta",          "()Landroid/os/Bundle;",    (void *) IjkMediaPlayer_getMediaMeta },
35    { "_setLoopCount",          "(I)V",                     (void *) IjkMediaPlayer_setLoopCount },
36    { "_getLoopCount",          "()I",                      (void *) IjkMediaPlayer_getLoopCount },
37    { "_getPropertyFloat",      "(IF)F",                    (void *) ijkMediaPlayer_getPropertyFloat },
38    { "_setPropertyFloat",      "(IF)V",                    (void *) ijkMediaPlayer_setPropertyFloat },
39    { "_getPropertyLong",       "(IJ)J",                    (void *) ijkMediaPlayer_getPropertyLong },
40    { "_setPropertyLong",       "(IJ)V",                    (void *) ijkMediaPlayer_setPropertyLong },
41    { "_setStreamSelected",     "(IZ)V",                    (void *) ijkMediaPlayer_setStreamSelected },
42
43    { "native_profileBegin",    "(Ljava/lang/String;)V",    (void *) IjkMediaPlayer_native_profileBegin },
44    { "native_profileEnd",      "()V",                      (void *) IjkMediaPlayer_native_profileEnd },
45
46    { "native_setLogLevel",     "(I)V",                     (void *) IjkMediaPlayer_native_setLogLevel },
47    { "_setFrameAtTime",        "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
48};
上述代码主要工作如下:
  • 注册 Native 方法与 Java 方法之间的对应关系。

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

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

Java层播放器创建

看下 Java 层 IjkMediaPlayer 的创建
1IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
查看构造方法如下:
 1public IjkMediaPlayer() {
2    // sLocalLibLoader为默认的IjkLibLoader
3    this(sLocalLibLoader);
4}
5
6public IjkMediaPlayer(IjkLibLoader libLoader) {
7    initPlayer(libLoader);
8}
9
10private void initPlayer(IjkLibLoader libLoader) {
11    // 尝试加载库,避免之前从未加载过so
12    loadLibrariesOnce(libLoader);
13    // 对应c层IjkMediaPlayer_native_init方法,暂时为空实现
14    initNativeOnce();
15
16    // 用来处理Iik底层传递上来的消息
17    Looper looper;
18    if ((looper = Looper.myLooper()) != null) {
19        mEventHandler = new EventHandler(this, looper);
20    } else if ((looper = Looper.getMainLooper()) != null) {
21        mEventHandler = new EventHandler(this, looper);
22    } else {
23        mEventHandler = null;
24    }
25
26    // IjkMediaPlayer包装成弱引用传递到native层
27    native_setup(new WeakReference(this));
28}
可见初始化的时候加载了 ijkffmpeg、ijksdl、ijkplayer 库,创建了 mEventHandler 处理 Native 拋上来的播放事件,比如播放开始、播放完成、播放出错等事件,具体由 Native 层调用 Java 层的函数 postEventFromNative 来发送对应的事件,在阅读 C 层代码之前,先了解一下 IjkMediaPlayer 结构体。

IjkMediaPlayer结构体

IjkPlayer 对应 IjkMediaPlayer 结构体,其初始化主要是对该结构体进行初始化,其定义如下:
 1struct IjkMediaPlayer {
2    /* IjkMediaPlayer创建一次则ref_count计数加一次 */
3    volatile int ref_count;
4    /* 保护接口调用的锁*/
5    pthread_mutex_t mutex;
6    /* FFPlayer是原ffplayer里的结构体,有被ijk扩展 */
7    FFPlayer *ffplayer;
8    /* 用于ijkPlayer回调给应用层的一个消息循环函数 */
9    int (*msg_loop)(void*);
10    /* 消息线程 */
11    SDL_Thread *msg_thread;
12    SDL_Thread _msg_thread;
13    /* 播放器状态 */
14    int mp_state;
15    /* 播放地址 */
16    char *data_source;
17    /* Java层IjkMediaPlayer对应弱引用对象 */
18    void *weak_thiz;
19    /* 是否重新播放 */
20    int restart;
21    /* restart是否从头开始 */
22    int restart_from_beginning;
23    /* 标识用户是不是seek了进度条 */
24    int seek_req;
25    /* seek的毫秒值 */
26    long seek_msec;
27};
整个播放流程中涉及到的参数基本都是 IjkMediaPlayer 结构体中的成员变量,后面 Native 层 IjkPlayer 的创建都是在初始化上述结构体中的成员变量。

Native层播放器创建

Native 层播放器的创建主要是结构体 IjkMediaPlayer 的创建及初始化,这里接着上文继续查看 native_setup 方法的具体实现如下:
 1static void
2IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
3
{
4    MPTRACE("%s\n", __func__);
5    // 创建C层对应的IjkMediaPlayer
6    IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
7    JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError""mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
8    // Java层mNativeMediaPlayer初始化
9    jni_set_media_player(env, thiz, mp);
10    // 初始化mp->weak_thiz
11    ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
12    // 初始化ffp->inject_opaque等
13    ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
14    // 初始化ffp->ijkio_inject_opaque等
15    ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
16    // 设置解码器选择回调
17    ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));
18
19LABEL_RETURN:
20    ijkmp_dec_ref_p(&mp);
21}
上述代码主要是开始创建 C 层的 IjkMediaPlayer 、其对应结构体部分属性的初始化及解码器回调,mediacodec_select_callback 会通过调用 Java 层的函数 onSelectCodec 获取合适的解码器信息,可在 IjkMediaCodecInfo 中进行解码器的适配。继续查看 ijkmp_android_create 函数如下:
 1IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
2
{
3    // 填充IjkMediaPlayer结构体
4    IjkMediaPlayer *mp = ijkmp_create(msg_loop);
5    if (!mp)
6        goto fail;
7    // 初始化SDL_Vout,表示IJK中的显示上下文
8    mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
9    if (!mp->ffplayer->vout)
10        goto fail;
11    // 初始化IJKFF_Pipeline,解码器、音频输出
12    mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
13    if (!mp->ffplayer->pipeline)
14        goto fail;
15    // 绑定SDL_Vout到IJKFF_Pipeline_Opaque
16    ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
17    return mp;
18fail:
19    ijkmp_dec_ref_p(&mp);
20    return NULL;
21}
其中 ijkmp_create 主要是创建了 FFPlayer,并将 msg_loop 进行赋值,msg_loop 事件循环相关调用将在后续文章中介绍,继续看下后面几个函数:
  • SDL_VoutAndroid_CreateForAndroidSurface:初始化 IjkPlayer 的 显示上下文 SDL_Vout

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

  • ffpipeline_set_vout:绑定SDL_VoutIJKFF_Pipeline_Opaque

调用流程图

IjkPlayer 创建主要函数调用流程如下:daa7722f9982208d0c0028018473173e.webp其他细节会在后续的文章中介绍,下一篇将介绍 IjkPlayer 中消息循环机制。推荐阅读:

浏览 9
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报