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. 設置選擇硬編解碼時觸發的回調。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章