ijkplayer系列6:流程分析-初始化IjkMediaPlayer对象

初始化对象流程如下图
在这里插入图片描述

IjkMediaPlayer提供了两个构造函数,分别如下:

IjkMediaPlayer()
IjkMediaPlayer(IjkLibLoader libLoader)

第二个构造函数的参数表示本地库的加载器,如非特殊情况我们都使用默认的,也就是传null或者直接使用第一个构造函数。构造函数中就调用了个内部方法initPlayer()进行初始化工作,initPlayer()的代码如下:

 private void initPlayer(IjkLibLoader libLoader) {
        // 加载本地库,不管有多少个MediaPlayer对象,都只会加载一次
        loadLibrariesOnce(libLoader);
        // 初始化Native信息,内部调用native_init()本地方法,不管有多少个MediaPlayer对象,都只会初始化一次
        initNativeOnce();

        // 初始化mEventHandler对象,mEventHandler用来将Native回调上来的信息post出去,防止阻塞Native流程
        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 requires a weak reference to our object. It's easier to * create it here than in C++. */
        // 将当前对象的弱引用设置到Native中,Native会保存这个引用
        native_setup(new WeakReference<IjkMediaPlayer>(this));
    }

loadLibrariesOnce()和initNativeOnce()都是静态方法,整个进程生命周期内仅会调用一次,前者是加载所需的动态库,即ffmpeg、sdl和ijkplayer三个,后者是初始化native信息,会调用到ijkplayer_jni.c中的IjkMediaPlayer_native_init()方法,但其实这个方法什么也没做,仅仅只是打印了句log,如果有涉及到源码的修改,可以在这里做一些初始化的工作。

在这里插入图片描述
在这里插入图片描述
如上我们就可以找到java层的nativie方法名在C层的映射的方法名。至于具体怎么去映射怎么找方法名,可以参考下面这篇文章,这里不多说这个:
Android JNI原理分析
http://gityuan.com/2016/05/28/android-jni/

接着,我们来看下native_setup()方法的走向,对象的初始化工作都在这个方法里。native_setup()对应的jni方法是ijkplayer_jni.c中的IjkMediaPlayer_native_setup()方法,代码如下:

static void IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) {
    MPTRACE("%s\n", __func__);
    // 创建IjkMediaPlayer对象,如果创建失败,抛出OOM
    IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
    JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);

    /* 将mp保存到IjkMediaPlayer(Java类)对象的mNativeMediaPlayer字段中。 * 相反地,调用jni_get_media_player()方法从Java层中获取IjkMediaPlayer对象。 */
    jni_set_media_player(env, thiz, mp);
    
    // 保存IjkMediaPlayer(Java类)对象的弱引用到mp->weak_thiz中
    ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
    
    ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));

    /* 设置选择MediaCodec(Android上的硬编解码器)时的回调,在创建硬编解码器时触发回调, * 创建方法见ffpipenode_create_video_decoder_from_android_mediacodec(), * 最终调用到IjkMediaPlayer(Java类)的静态方法onSelectCodec() */
    ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);
}

这个方法里面做的事还是挺多的,我们只深入ijkmp_android_create()这个方法,这个方法的作用就是创建一个IjkMediaPlayer对象,至于它的参数message_loop先记着就好,后面会提到。其它代码的用处见注释。
我们来看下ijkmp_android_create()的代码:

 IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
    IjkMediaPlayer *mp = ijkmp_create(msg_loop);
    if (!mp)
        goto fail;

    mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
    if (!mp->ffplayer->vout)
        goto fail;

    mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
    if (!mp->ffplayer->pipeline)
        goto fail;

    ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);

    return mp;

fail:
    ijkmp_dec_ref_p(&mp);
    return NULL;
}

从代码中可以看出,这里先调用ijkmp_create()方法创建了IjkMediaPlayer对象mp,同时创建了vout和pipline对象。逐步深入ffpipeline_create_from_android()的源码,可以发现有关video解码使用硬解(MediaCodec)还是软解(ffmpeg)、aout使用opensles还是AudioTrack的相关信息,这些源码很容易阅读,这里不再详细分析。
我们继续来看下ijkmp_create()的代码:

 IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
    IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
    if (!mp)
        goto fail;

    mp->ffplayer = ffp_create();
    if (!mp->ffplayer)
        goto fail;

    mp->msg_loop = msg_loop;

    ijkmp_inc_ref(mp);
    pthread_mutex_init(&mp->mutex, NULL);

    return mp;

    fail:
    ijkmp_destroy_p(&mp);
    return NULL;
}

一路走来,IjkMediaPlayer对象mp终于创建了,创建完mp后,又创建了mp->ffplayer,事实上,这个才是核心的播放器对象。同时,message_loop参数也在这里赋给了mp->msg_loop。
下面,我们再来看下ffp_create()方法,了解下FFPlayer对象是如何创建的:

FFPlayer *ffp_create() {
    av_log(NULL, AV_LOG_INFO, "av_version_info: %s\n", av_version_info());
    av_log(NULL, AV_LOG_INFO, "ijk_version_info: %s\n", ijk_version_info());

    FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
    if (!ffp)
        return NULL;

    msg_queue_init(&ffp->msg_queue);
    ffp->af_mutex = SDL_CreateMutex();
    ffp->vf_mutex = SDL_CreateMutex();

    ffp_reset_internal(ffp);
    ffp->av_class = &ffp_context_class;
    ffp->meta = ijkmeta_create();

    av_opt_set_defaults(ffp);

    return ffp;
} 

从代码中我们可以看到,ffp_create()的主要作用是创建了FFPlayer对象并进行初始化。
好了,现在我们回过头来看看message_loop的使用。message_loop会在准备阶段被调用,也就是Java层调用prepareAsync()方法时,这个方法是在独立的线程中运行。我们来看下它的代码:

 static void message_loop_n(JNIEnv *env, IjkMediaPlayer *mp) {
    jobject weak_thiz = (jobject) ijkmp_get_weak_thiz(mp);
    JNI_CHECK_GOTO(weak_thiz, env, NULL, "mpjni: message_loop_n: null weak_thiz", LABEL_RETURN);

    while (1) {
        AVMessage msg;

        int retval = ijkmp_get_msg(mp, &msg, 1);
        if (retval < 0)
            break;

        // block-get should never return 0
        assert(retval > 0);

        switch (msg.what) {
        case FFP_MSG_FLUSH:
            MPTRACE("FFP_MSG_FLUSH:\n");
            post_event(env, weak_thiz, MEDIA_NOP, 0, 0);
            break;
        case FFP_MSG_ERROR:
            MPTRACE("FFP_MSG_ERROR: %d\n", msg.arg1);
            post_event(env, weak_thiz, MEDIA_ERROR, MEDIA_ERROR_IJK_PLAYER, msg.arg1);
            break;
        case FFP_MSG_PREPARED:
            MPTRACE("FFP_MSG_PREPARED:\n");
            post_event(env, weak_thiz, MEDIA_PREPARED, 0, 0);
            break;
        case FFP_MSG_COMPLETED:
            MPTRACE("FFP_MSG_COMPLETED:\n");
            post_event(env, weak_thiz, MEDIA_PLAYBACK_COMPLETE, 0, 0);
            break;
        case FFP_MSG_VIDEO_SIZE_CHANGED:
            MPTRACE("FFP_MSG_VIDEO_SIZE_CHANGED: %d, %d\n", msg.arg1, msg.arg2);
            post_event(env, weak_thiz, MEDIA_SET_VIDEO_SIZE, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_SAR_CHANGED:
            MPTRACE("FFP_MSG_SAR_CHANGED: %d, %d\n", msg.arg1, msg.arg2);
            post_event(env, weak_thiz, MEDIA_SET_VIDEO_SAR, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_VIDEO_RENDERING_START:
            MPTRACE("FFP_MSG_VIDEO_RENDERING_START:\n");
            post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_RENDERING_START, 0);
            break;
        case FFP_MSG_AUDIO_RENDERING_START:
            MPTRACE("FFP_MSG_AUDIO_RENDERING_START:\n");
            post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_AUDIO_RENDERING_START, 0);
            break;
        case FFP_MSG_VIDEO_ROTATION_CHANGED:
            MPTRACE("FFP_MSG_VIDEO_ROTATION_CHANGED: %d\n", msg.arg1);
            post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_ROTATION_CHANGED, msg.arg1);
            break;
        case FFP_MSG_BUFFERING_START:
            MPTRACE("FFP_MSG_BUFFERING_START:\n");
            post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_BUFFERING_START, 0);
            break;
        case FFP_MSG_BUFFERING_END:
            MPTRACE("FFP_MSG_BUFFERING_END:\n");
            post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_BUFFERING_END, 0);
            break;
        case FFP_MSG_BUFFERING_UPDATE:
            // MPTRACE("FFP_MSG_BUFFERING_UPDATE: %d, %d", msg.arg1, msg.arg2);
            post_event(env, weak_thiz, MEDIA_BUFFERING_UPDATE, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_BUFFERING_BYTES_UPDATE:
            break;
        case FFP_MSG_BUFFERING_TIME_UPDATE:
            break;
        case FFP_MSG_SEEK_COMPLETE:
            MPTRACE("FFP_MSG_SEEK_COMPLETE:\n");
            post_event(env, weak_thiz, MEDIA_SEEK_COMPLETE, 0, 0);
            break;
        case FFP_MSG_PLAYBACK_STATE_CHANGED:
            break;
        case FFP_MSG_TIMED_TEXT:
            if (msg.obj) {
                jstring text = (*env)->NewStringUTF(env, (char *)msg.obj);
                post_event2(env, weak_thiz, MEDIA_TIMED_TEXT, 0, 0, text);
                J4A_DeleteLocalRef__p(env, &text);
            }
            else {
                post_event2(env, weak_thiz, MEDIA_TIMED_TEXT, 0, 0, NULL);
            }
            break;
        default:
            ALOGE("unknown FFP_MSG_xxx(%d)\n", msg.what);
            break;
        }
        msg_free_res(&msg);
    }

LABEL_RETURN:
    ;
}

static int message_loop(void *arg) {
    MPTRACE("%s\n", __func__);

    JNIEnv *env = NULL;
    (*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL );

    IjkMediaPlayer *mp = (IjkMediaPlayer*) arg;
    JNI_CHECK_GOTO(mp, env, NULL, "mpjni: native_message_loop: null mp", LABEL_RETURN);

    message_loop_n(env, mp);

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);
    (*g_jvm)->DetachCurrentThread(g_jvm);

    MPTRACE("message_loop exit");
    return 0;
}

可以看出,message_loop将native线程和Java线程绑定了,目的是为了获取JNIEnv对象,以便逆向调用Java层方法。其内部调用了message_loop_n()方法,而message_loop_n()则是不断地调用ijkmp_get_msg()来获取当前的一些事件,并通过post_event()方法将事件传递到Java层。
ijkmp_get_msg()从队列中获取事件,没有事件时会阻塞(可以设置为不阻塞),返回-1表示队列已退出。
post_event()会调用到IjkMediaPlayer(Java类)对象的postEventFromNative()方法,最终调用到EventHandler的handleMessage()方法。

现在,我们来看下对象初始化工作做了哪些事情:

  1. 创建IjkMediaPlayer对象。
  2. 保存message_loop到IjkMediaPlayer->msg_loop中,这个是后续上层获取底层事件的核心途径。
  3. 创建并初始化FFPlayer对象(IjkMediaPlayer->ffplayer),初始化消息队列msg_queue,msg_loop会从msg_queue中循环获取消息。重点关注初始配置信息。
  4. 创建vout(video输出),使用opengl +ANativeWindow;创建pipeline,pipeline的核心任务是创建aout(audio输出)和video解码器,这些任务会在后续被执行,其中aout在prepare阶段被创建,video解码器在打开流之后创建。
  5. 保存Native层的IjkMediaPlayer对象到Java层中,保存Java层的IjkMediaPlayer对象的弱引用到Native的一些对象中。
  6. 设置选择硬编解码时触发的回调。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章