今天介紹下 IjkPlayer 的播放器創建流程,本文開始將正式開始 IjkPlayer 的源碼閱讀之旅,主要內容如下:
- 初始化 so
- Java 層播放器創建
- IjkMediaPlayer 結構體
- Native 層播放器創建
- 調用流程圖
閱讀之前可以先看前面幾篇文章:
初始化 so#
從 IjkPlayer 源碼中VideoActivity
查看 IjkPlayer 的初始化,關鍵代碼如下:
// 加載so庫
IjkMediaPlayer.loadLibrariesOnce(null);
// android-ndk-profiler性能分析
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
查看loadLibrariesOnce
源碼如下:
private static volatile boolean mIsLibLoaded = false;
public static void loadLibrariesOnce(IjkLibLoader libLoader) {
synchronized (IjkMediaPlayer.class) {
// 保證只加載一次
if (!mIsLibLoaded) {
if (libLoader == null)
// sLocalLibLoader為默認的IjkLibLoader
libLoader = sLocalLibLoader;
libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("ijksdl");
libLoader.loadLibrary("ijkplayer");
mIsLibLoaded = true;
}
}
}
loadLibrariesOnce
加載相應的 so 庫,native_profileBegin
則是 android-ndk-profiler 工具進行性能分析的函數,對應的還有native_profileEnd
,其定義如下:
public static native void native_profileBegin(String libName);
public static native void native_profileEnd();
上述方法分別調用了函數 monstartup
和 moncleanup
,分別在程序運行開始和結束調用,關於 android-ndk-profiler 的這裡不做過多介紹。
在 IjkPlayer 系列之 JNI 基礎及源碼目錄介紹 這篇文章中介紹了 JNI 的註冊方式,其中 IjkPlayer 使用的就是其動態註冊方式,即當調用 System.loadLibrary
加載庫的時候會去查找 JNI_OnLoad
這個函數,然後在該函數回調中進行註冊,下面看下 ijkplayer 中的 JNI_OnLoad
實現,如下:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
// 保存全局的JavaVM
g_jvm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
// 互斥鎖的初始化
pthread_mutex_init(&g_clazz.mutex, NULL );
// 將IjkMediaPlayer轉換為一個全局引用
IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
// 註冊Native方法與Java方法相對應
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
// 初始化:註冊編解碼器、解復用器、協議
ijkmp_global_init();
// 設置回調,對應Java層的onNativeInvoke回調函數
ijkmp_global_set_inject_callback(inject_callback);
// 註冊av_base64_encode與FFmpegApi_av_base64_encode的對應關係
FFmpegApi_global_init(env);
return JNI_VERSION_1_4;
}
其中數組 g_methods
定義了 Native 函數與 Java 方法的一一對應關係,如下:
static JNINativeMethod g_methods[] = {
{
"_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
(void *) IjkMediaPlayer_setDataSourceAndHeaders
},
{ "_setDataSourceFd", "(I)V", (void *) IjkMediaPlayer_setDataSourceFd },
{ "_setDataSource", "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
{ "_setAndroidIOCallback", "(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },
{ "_setVideoSurface", "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
{ "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync },
{ "_start", "()V", (void *) IjkMediaPlayer_start },
{ "_stop", "()V", (void *) IjkMediaPlayer_stop },
{ "seekTo", "(J)V", (void *) IjkMediaPlayer_seekTo },
{ "_pause", "()V", (void *) IjkMediaPlayer_pause },
{ "isPlaying", "()Z", (void *) IjkMediaPlayer_isPlaying },
{ "getCurrentPosition", "()J", (void *) IjkMediaPlayer_getCurrentPosition },
{ "getDuration", "()J", (void *) IjkMediaPlayer_getDuration },
{ "_release", "()V", (void *) IjkMediaPlayer_release },
{ "_reset", "()V", (void *) IjkMediaPlayer_reset },
{ "setVolume", "(FF)V", (void *) IjkMediaPlayer_setVolume },
{ "getAudioSessionId", "()I", (void *) IjkMediaPlayer_getAudioSessionId },
{ "native_init", "()V", (void *) IjkMediaPlayer_native_init },
{ "native_setup", "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
{ "native_finalize", "()V", (void *) IjkMediaPlayer_native_finalize },
{ "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
{ "_setOption", "(ILjava/lang/String;J)V", (void *) IjkMediaPlayer_setOptionLong },
{ "_getColorFormatName", "(I)Ljava/lang/String;", (void *) IjkMediaPlayer_getColorFormatName },
{ "_getVideoCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getVideoCodecInfo },
{ "_getAudioCodecInfo", "()Ljava/lang/String;", (void *) IjkMediaPlayer_getAudioCodecInfo },
{ "_getMediaMeta", "()Landroid/os/Bundle;", (void *) IjkMediaPlayer_getMediaMeta },
{ "_setLoopCount", "(I)V", (void *) IjkMediaPlayer_setLoopCount },
{ "_getLoopCount", "()I", (void *) IjkMediaPlayer_getLoopCount },
{ "_getPropertyFloat", "(IF)F", (void *) ijkMediaPlayer_getPropertyFloat },
{ "_setPropertyFloat", "(IF)V", (void *) ijkMediaPlayer_setPropertyFloat },
{ "_getPropertyLong", "(IJ)J", (void *) ijkMediaPlayer_getPropertyLong },
{ "_setPropertyLong", "(IJ)V", (void *) ijkMediaPlayer_setPropertyLong },
{ "_setStreamSelected", "(IZ)V", (void *) ijkMediaPlayer_setStreamSelected },
{ "native_profileBegin", "(Ljava/lang/String;)V", (void *) IjkMediaPlayer_native_profileBegin },
{ "native_profileEnd", "()V", (void *) IjkMediaPlayer_native_profileEnd },
{ "native_setLogLevel", "(I)V", (void *) IjkMediaPlayer_native_setLogLevel },
{ "_setFrameAtTime", "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
};
上述代碼主要工作如下:
- 註冊 Native 方法與 Java 方法之間的對應關係。
- 註冊編解碼器、解復用器、協議等。
- 設置
onNativeInvoke
回調供 Native 層調用,主要關心其返回值,比如斷網重連等。
Java 層播放器創建#
看下 Java 層 IjkMediaPlayer
的創建
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
查看構造方法如下:
public IjkMediaPlayer() {
// sLocalLibLoader為默認的IjkLibLoader
this(sLocalLibLoader);
}
public IjkMediaPlayer(IjkLibLoader libLoader) {
initPlayer(libLoader);
}
private void initPlayer(IjkLibLoader libLoader) {
// 嘗試加載庫,避免之前從未加載過so
loadLibrariesOnce(libLoader);
// 對應c層IjkMediaPlayer_native_init方法,暫時為空實現
initNativeOnce();
// 用來處理Iik底層傳遞上來的消息
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;
}
// IjkMediaPlayer包裝成弱引用傳遞到native層
native_setup(new WeakReference<IjkMediaPlayer>(this));
}
可見初始化的時候加載了 ijkffmpeg、ijksdl、ijkplayer 庫,創建了 mEventHandler
處理 Native 拋上來的播放事件,比如播放開始、播放完成、播放出錯等事件,具體由 Native 層調用 Java 層的函數 postEventFromNative
來發送對應的事件,在閱讀 C 層代碼之前,先了解一下 IjkMediaPlayer
結構體。
IjkMediaPlayer 結構體#
IjkPlayer 對應 IjkMediaPlayer
結構體,其初始化主要是對該結構體進行初始化,其定義如下:
struct IjkMediaPlayer {
/* IjkMediaPlayer創建一次則ref_count計數加一次 */
volatile int ref_count;
/* 保護接口調用的鎖*/
pthread_mutex_t mutex;
/* FFPlayer是原ffplayer裡的結構體,有被ijk擴展 */
FFPlayer *ffplayer;
/* 用於ijkPlayer回調給應用層的一個消息循環函數 */
int (*msg_loop)(void*);
/* 消息線程 */
SDL_Thread *msg_thread;
SDL_Thread _msg_thread;
/* 播放器狀態 */
int mp_state;
/* 播放地址 */
char *data_source;
/* Java層IjkMediaPlayer對應弱引用對象 */
void *weak_thiz;
/* 是否重新播放 */
int restart;
/* restart是否從頭開始 */
int restart_from_beginning;
/* 標識用户是不是seek了進度條 */
int seek_req;
/* seek的毫秒值 */
long seek_msec;
};
整個播放流程中涉及到的參數基本都是 IjkMediaPlayer
結構體中的成員變量,後面 Native 層 IjkPlayer 的創建都是在初始化上述結構體中的成員變量。
Native 層播放器創建#
Native 層播放器的創建主要是結構體 IjkMediaPlayer
的創建及初始化,這裡接著上文繼續查看 native_setup
方法的具體實現如下:
static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
MPTRACE("%s\n", __func__);
// 創建C層對應的IjkMediaPlayer
IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
// Java層mNativeMediaPlayer初始化
jni_set_media_player(env, thiz, mp);
// 初始化mp->weak_thiz
ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
// 初始化ffp->inject_opaque等
ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
// 初始化ffp->ijkio_inject_opaque等
ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
// 設置解碼器選擇回調
ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
上述代碼主要是開始創建 C 層的 IjkMediaPlayer
、其對應結構體部分屬性的初始化及解碼器回調,mediacodec_select_callback
會通過調用 Java 層的函數 onSelectCodec
獲取合適的解碼器信息,可在 IjkMediaCodecInfo
中進行解碼器的適配。
繼續查看 ijkmp_android_create
函數如下:
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
// 填充IjkMediaPlayer結構體
IjkMediaPlayer *mp = ijkmp_create(msg_loop);
if (!mp)
goto fail;
// 初始化SDL_Vout,表示IJK中的顯示上下文
mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
if (!mp->ffplayer->vout)
goto fail;
// 初始化IJKFF_Pipeline,解碼器、音頻輸出
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
if (!mp->ffplayer->pipeline)
goto fail;
// 綁定SDL_Vout到IJKFF_Pipeline_Opaque
ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
return mp;
fail:
ijkmp_dec_ref_p(&mp);
return NULL;
}
其中 ijkmp_create
主要是創建了 FFPlayer
,並將 msg_loop
進行賦值,msg_loop
事件循環相關調用將在後續文章中介紹,繼續看下後面幾個函數:
SDL_VoutAndroid_CreateForAndroidSurface
:初始化IjkPlayer
的 顯示上下文SDL_Vout
。ffpipeline_create_from_android
: 初始化IJKFF_Pipeline
,解碼器、音頻輸出。ffpipeline_set_vout
:綁定SDL_Vout
到IJKFF_Pipeline_Opaque
。
調用流程圖#
IjkPlayer 創建主要函數調用流程如下:
其他細節會在後續的文章中介紹,下一篇將介紹 IjkPlayer 中消息循環機制。