初始化對象流程如下圖
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()方法。
現在,我們來看下對象初始化工作做了哪些事情:
- 創建IjkMediaPlayer對象。
- 保存message_loop到IjkMediaPlayer->msg_loop中,這個是後續上層獲取底層事件的核心途徑。
- 創建並初始化FFPlayer對象(IjkMediaPlayer->ffplayer),初始化消息隊列msg_queue,msg_loop會從msg_queue中循環獲取消息。重點關注初始配置信息。
- 創建vout(video輸出),使用opengl +ANativeWindow;創建pipeline,pipeline的核心任務是創建aout(audio輸出)和video解碼器,這些任務會在後續被執行,其中aout在prepare階段被創建,video解碼器在打開流之後創建。
- 保存Native層的IjkMediaPlayer對象到Java層中,保存Java層的IjkMediaPlayer對象的弱引用到Native的一些對象中。
- 設置選擇硬編解碼時觸發的回調。