上一篇文章我們簡單地了解了ijkplayer的使用方法,本文準備分析一下ijkplayer初始化的流程。
在這里我還是建議大家clone下來源碼同步分析,因為ijkplayer文件太多,我開始看的時候是越看越亂,到最后重新看一點,然后用筆記記一點,才有了頭緒。
我們上一篇文章有一個示例代碼,為了方便,我再貼一下:
// init player
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
videoView.setVideoURI(Uri.parse("http://106.36.45.36/live.aishang.ctlcdn.com/00000110240001_1/encoder/1/playlist.m3u8"));
videoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer mp) {
videoView.start();
}
});
OK,現在我們就從初始化的第一句分析起,在loadLibrariesOnce(null)
中加載了我們編譯出來的幾個so文件,如下:
libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("ijksdl");
libLoader.loadLibrary("ijkplayer");
這里直接把libLoader看成System.loadLibrary(libname)
就行,雖然看起來沒什么,挺正常的,但是分析到后面發現,java層調用的函數在c層找不到0.0。之后google了下發現:
JNI在加載時,會調用JNI_OnLoad()
,而卸載時會調用JNI_UnLoad()
,我們可以利用這兩個方法來動態方式實現JNI。
然后趕快找找C代碼中有沒有JNI_OnLoad()
方法。
果然,在ijkplayer_jni.c
中找到了JNI_OnLoad()
方法:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
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 );
// FindClass returns LocalReference
IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
(*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
ijkmp_global_init();
ijkmp_global_set_inject_callback(inject_callback);
FFmpegApi_global_init(env);
return JNI_VERSION_1_4;
}
這個方法里面,初始化了很多內容,首先RegisterNatives
注冊g_methods
中的native方法,其中包括:
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 },
//...
{ "_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 },
{ "getAudioSessionId", "()I", (void *) IjkMediaPlayer_getAudioSessionId },
{ "native_init", "()V", (void *) IjkMediaPlayer_native_init },
{ "native_setup", "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
//......
{ "native_profileBegin", "(Ljava/lang/String;)V", (void *) IjkMediaPlayer_native_profileBegin },
{ "native_profileEnd", "()V", (void *) IjkMediaPlayer_native_profileEnd },
};
注冊后,相當于java層聲明為native方法的函數與c層有一個映射關系;舉個栗子,當我們java里面調用_start()
方法的時候,其實調用的是c層中的IjkMediaPlayer_start()
方法;對應地,作者在java層聲明了一個修飾java方法的annotation,這些函數也會在這里注冊,實現后面在C層也能調用java層的函數。是不是很神奇??
接著還調用了幾個init方法:
**ffp_global_init() **-->主要是ffmpeg的初始化工作, 前面說過ijkplayer是基于ffmpeg的。
FFmpegApi_global_init(env)-->初始化FFmpegApi#av_base64_encode函數。
后面調用的IjkMediaPlayer.native_profileBegin("libijkplayer.so");
發現并沒有什么作用的樣子,至少我注釋了還是能成功運行??磥磉@里要留坑了。
然后我們再看到videoView.setVideoURI();
,這里的url支持本地視頻和在線視頻,接著我們繼續看到它的實現:
private void openVideo() {
if (mUri == null || mSurfaceHolder == null) {
// not ready for playback just yet, will try again later
return;
}
// we shouldn't clear the target state, because somebody might have
// called start() previously
release(false);
AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
try {
mMediaPlayer = createPlayer(mSettings.getPlayer());
mMediaPlayer.setOnPreparedListener(mPreparedListener);
//這里省略一大波setListener......
mCurrentBufferPercentage = 0;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
} else {
mMediaPlayer.setDataSource(mUri.toString());
}
bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setScreenOnWhilePlaying(true);
mMediaPlayer.prepareAsync();
if (mHudViewHolder != null)
mHudViewHolder.setMediaPlayer(mMediaPlayer);
// REMOVED: mPendingSubtitleTracks
// we don't set the target state here either, but preserve the
// target state that was there before.
mCurrentState = STATE_PREPARING;
attachMediaController();
} catch (IOException ex) {
...
} catch (IllegalArgumentException ex) {
...
} finally {
}
}
在這里, 做了下面幾件事:
- 獲得AudioManager
- 通過setting獲取player的類型
- 初始化IMediaPlayer (createPlayer()函數)
- 然后調用setDataSource(String)
- 屏幕常亮等
- 設置MediaController
- mMediaPlayer.prepareAsync()
初始化IMediaPlayer
這里只講一下IjkMediaPlayer
,其實我們除了使用IjkMediaPlayer
,還可以使用IjkExoMediaPlayer
或者AndroidMediaPlayer
。只需要修改一下createPlayer(int playerType)
的參數就行了。
在IjkMediaPlayer
初始化過程中,做了好多好多事,我們來一一看一下:
public IjkMediaPlayer(IjkLibLoader libLoader) {
initPlayer(libLoader);
}
private void initPlayer(IjkLibLoader libLoader) {
loadLibrariesOnce(libLoader);
initNativeOnce();
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_setup(new WeakReference<IjkMediaPlayer>(this));
}
由于之前調用了一次loadLibrariesOnce()
,所以這里并沒有什么用。
initNativeOnce()
經過幾次跳轉調用了native方法的native_init
函數,這里跳轉到C代碼里面,對應調用了IjkMediaPlayer_native_init()
,不過看起來并沒有什么操作。接著初始化了一個handler,后面在c層通過調用java的方法來post message。
這里最后調用了native方法native_setup(new WeakReference<IjkMediaPlayer>(this));
對應C語言的IjkMediaPlayer_native_setup()
方法,也在ijkplayer_jni.c文件里面:
static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
MPTRACE("%s\n", __func__);
IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
jni_set_media_player(env, thiz, mp);
ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, (*env)->NewGlobalRef(env, weak_this));
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}
先看ijkmp_android_create(message_loop);/ijkplayer_android.c
:
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;
}
首先創建一個IJKMediaPlayer類型的player吧,先貼出IJKPlayerMediaPlayer
的結構:
struct IjkMediaPlayer {
volatile int ref_count;
pthread_mutex_t mutex; //這是一個互斥鎖,因為后面有很多多線程操作,同步少不了
FFPlayer *ffplayer; //ffplayer
int (*msg_loop)(void*); //msg的一個處理函數
SDL_Thread *msg_thread;
SDL_Thread _msg_thread;
int mp_state;
char *data_source; //數據源
void *weak_thiz;
int restart;
int restart_from_beginning;
int seek_req;
long seek_msec;
};
在create_ijkplayer(ijkmp_create(msg_loop)函數)
的同時,也給它指定了msg的處理函數和ffplayer。
接下來就是對ijkplayer結構體中的ffplayer進行設置,剛剛通過SDL_VoutAndroid_CreateForAndroidSurface()
創建了輸出設備vout,后面在ffpipeline_set_vout()
里面:mp->ffplayer->opaque->weak_vout = vout
。
然后再jni_set_media_player()
,這里基本就結束了。
其實細細分析還有的地方沒有分析到,比如在SDL_VoutAndroid_CreateForAndroidSurface()
創建視頻輸出設備的時候,跟蹤函數跳轉發現,硬解用的mediacidec,軟解用的ffmpeg:
static SDL_VoutOverlay *func_create_overlay_l(int width, int height, int frame_format, SDL_Vout *vout)
{
switch (frame_format) {
case IJK_AV_PIX_FMT__ANDROID_MEDIACODEC:
return SDL_VoutAMediaCodec_CreateOverlay(width, height, vout);
default:
return SDL_VoutFFmpeg_CreateOverlay(width, height, frame_format, vout);
}
}
還有渲染視頻設備用的ANativeWindow,對接surface等。還有一些地方是設置了函數指針,現在分析的話,后面可能會懵,這些放到后面再來分析。
OK,現在返回java層,剛剛分析到了IJKMediaPlayer的構造函數,作者在構造函數里面做了很多事情的樣子。
setDataSource(String)
接著我們設置了視頻源,在JNI層對應IjkMediaPlayer_setDataSourceAndHeaders()/ijkplayer_jni.c
。同樣代碼跟蹤到了ijkmp_set_data_source_l()
:
//...
freep((void**)&mp->data_source);
mp->data_source = strdup(url);
//...
ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);
//...
在ijkmp_change_state_l
里面封裝了一個what為MP_STATE_INITIALIZED
的AVMessage
,最后把其放入ijkplayer->ffplayer->msg_queue中,然后調用:
int SDL_CondSignal(SDL_cond *cond)
{
assert(cond);
if (!cond)
return -1;
return pthread_cond_signal(&cond->id);
}
cond->id其實是ijkplayer->ffplayer->msg_queue->cond->id,類型為pthread_cond_t
。
那么pthread_cond_signal
這個函數是干嘛的呢?
其實在liunx里面,pthread_cond_signal
函數的作用是發送一個信號給另外一個正在處于阻塞等待狀態的線程,使其脫離阻塞狀態,繼續執行,當某個線程繼續執行的時候發現msg_queue中有msg的時候會有相應操作。到底這里把是發給誰呢?請繼續往下面讀。
prepareAsync() 函數
其中調用了IjkMediaPlayer_prepareAsync/ijkplayer_jni.c
,我們繼續看它做了哪些操作。
同樣,這個函數最終調用了ijkmp_prepare_async_l()
:
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
assert(mp);
//...
assert(mp->data_source);
ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);
msg_queue_start(&mp->ffplayer->msg_queue);
// released in msg_loop
ijkmp_inc_ref(mp);
mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
// msg_thread is detached inside msg_loop
// TODO: 9 release weak_thiz if pthread_create() failed;
int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source);
if (retval < 0) {
ijkmp_change_state_l(mp, MP_STATE_ERROR);
return retval;
}
return 0;
}
和上面一樣,ijkmp_change_state_l()
向msg_queue
發送一個FFP_MSG_PLAYBACK_STATE_CHANGED
狀態
咦?這里又發送了一個狀態FFP_MSG_FLUSH
,然而函數名是msg_queue_start()
,這是什么意思呢?
然后接下來就創建了一個msg_loop的消息循環線程,線程入口為ijkmp_msg_loop
。這里其實就是上面放進msg_queue
的message的處理線程。后面再具體分析各個線程做了什么。
在最后,這里繼續調用ffp_prepare_async_l()
.然而在這個函數里面:
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
//....
av_log(NULL, AV_LOG_INFO, "===== versions =====\n");
ffp_show_version_str(ffp, "FFmpeg", av_version_info());
ffp_show_version_int(ffp, "libavutil", avutil_version());
ffp_show_version_int(ffp, "libavcodec", avcodec_version());
ffp_show_version_int(ffp, "libavformat", avformat_version());
ffp_show_version_int(ffp, "libswscale", swscale_version());
ffp_show_version_int(ffp, "libswresample", swresample_version());
av_log(NULL, AV_LOG_INFO, "===== options =====\n");
ffp_show_dict(ffp, "player-opts", ffp->player_opts);
ffp_show_dict(ffp, "format-opts", ffp->format_opts);
ffp_show_dict(ffp, "codec-opts ", ffp->codec_opts);
ffp_show_dict(ffp, "sws-opts ", ffp->sws_dict);
ffp_show_dict(ffp, "swr-opts ", ffp->swr_opts);
av_log(NULL, AV_LOG_INFO, "===================\n");
//...
VideoState *is = stream_open(ffp, file_name, NULL);
//...
ffp->is = is;
ffp->input_filename = av_strdup(file_name);
return 0;
}
看到這一大波log信息沒有,要是大家運行了上一篇的demo,會發現這不就是打印的log么?
然后繼續跟蹤代碼:
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
//...
/* start video display */
if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
goto fail;
//...
is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
//...
is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
//...
}
上面函數里面只留下了重要的代碼,這里創建了兩個線程,一個是以video_refresh_thread
為入口的視頻顯示線程。還有一個是以read_thread
為入口的網絡數據或者本地文件的數據讀取線程。
其實在read_thread
里面還向msg_queue發送了一個消息,那就是ffp_notify_msg1(ffp, FFP_MSG_PREPARED);
,消息循環線程收到這個消息后,會調用java層的函數,向handler發送個MEDIA_PREPARED
消息,然后在handler的handleMessage
函數里面會處理這個消息:
case MEDIA_PREPARED:
player.notifyOnPrepared();
return;
最終會調用我們上一文設置的:
videoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer mp) {
videoView.start();
}
});
的onPrepared()函數,然后初始化好了,接下來就可以播放了。
** 如果大家還想了解ijkplayer的工作流程的話,可以關注下android下的ijkplayer。**