IjkPlayer系列之播放器创建流程

今天介绍下 IjkPlayer 的播放器创建流程,本文开始将正式开始 IjkPlayer 的源码阅读之旅,阅读之前可以先看前面同系列文章:

主要内容如下:

  1. 初始化so
  2. Java层播放器创建
  3. IjkMediaPlayer结构体
  4. Native层播放器创建
  5. 调用流程图

初始化so

从 IjkPlayer 源码中VideoActivity查看 IjkPlayer 的初始化,关键代码如下:

1 // 加载so库
2 IjkMediaPlayer.loadLibrariesOnce(null);
3 // android-ndk-profiler性能分析
4 IjkMediaPlayer.native_profileBegin("libijkplayer.so");

查看loadLibrariesOnce源码如下:

 1 private static volatile boolean mIsLibLoaded = false;
 2 public 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,其定义如下:

1 public static native void native_profileBegin(String libName);
2 public static native void native_profileEnd();

上述方法分别调用了函数 monstartup 和 moncleanup,分别在程序运行开始和结束调用,关于 android-ndk-profiler 的这里不做过多介绍。在 IjkPlayer系列之JNI基础及源码目录介绍 这篇文章中介绍了 JNI 的注册方式,其中 IjkPlayer 使用的就是其动态注册方式,即当调用 System.loadLibrary 加载库的时候会去查找 JNI_OnLoad 这个函数,然后在该函数回调中进行注册,下面看下 ijkplayer 中的 JNI_OnLoad 实现,如下:

 1 JNIEXPORT 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 的创建

IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();

查看构造方法如下:

 1 public IjkMediaPlayer() {
 2    // sLocalLibLoader为默认的IjkLibLoader
 3    this(sLocalLibLoader);
 4 }
 5
 6 public IjkMediaPlayer(IjkLibLoader libLoader) {
 7    initPlayer(libLoader);
 8 }
 9
10 private 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<IjkMediaPlayer>(this));
28 }

可见初始化的时候加载了 ijkffmpeg、ijksdl、ijkplayer 库,创建了 mEventHandler 处理 Native 拋上来的播放事件,比如播放开始、播放完成、播放出错等事件,具体由 Native 层调用 Java 层的函数 postEventFromNative 来发送对应的事件,在阅读 C 层代码之前,先了解一下 IjkMediaPlayer 结构体。

IjkMediaPlayer结构体

IjkPlayer 对应 IjkMediaPlayer 结构体,其初始化主要是对该结构体进行初始化,其定义如下:

 1 struct 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 方法的具体实现如下:

 1 static void
 2 IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
 3 {
 4    MPTRACE("%sn", __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 函数如下:

 1 IjkMediaPlayer *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;
18 fail:
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 创建主要函数调用流程如下:图片其他细节会在后续的文章中介绍,下一篇将介绍 IjkPlayer 中消息循环机制。

作者: jzman,个人微信公众号:躬行之 ,可以关注一起交流学习.

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论