PS: 最近讀到一句歌德的一句詩:無論你能做什麼,或者夢想做什麼,著手開始吧,大膽就是天賦、能量和魔力的代名詞。
前面兩篇文章中介紹了 JNI 基礎知識以及 IjkPlayer 播放器的創建流程:
本文主要內容如下:
- AVMessage 和 MessageQueue
- 消息隊列初始化
- 消息循環的啟動
- 消息循環線程
- 消息循環函數
- 小結
AVMessage 和 MessageQueue#
先來看看 AVMessage 和 MessageQueue 兩個構造體定義:
// ff_ffmsg_queue.h
typedef struct AVMessage {
int what;
int arg1;
int arg2;
void *obj;
void (*free_l)(void *obj);
struct AVMessage *next;
} AVMessage;
typedef struct MessageQueue {
AVMessage *first_msg, *last_msg;
int nb_messages;
int abort_request;
SDL_mutex *mutex;
SDL_cond *cond;
AVMessage *recycle_msg;
int recycle_count;
int alloc_count;
} MessageQueue;
AVMessage 和 MessageQueue 的定義和實現都在 ff_ffmsg_queue.h 中,其相關操作函數主要是 msg_xxx 和 msg_quene_xxx,如下:
// AVMessage
void msg_xxx(AVMessage *msg)
// MessageQueue
void msg_queue_xxx(MessageQueue *q)
MessageQueue 關鍵函數如下:
// 初始化MessageQueue
void msg_queue_init(MessageQueue *q)
// 重置MessageQueue
void msg_queue_flush(MessageQueue *q)
// q->abort_request設置為0保證消息循環msg_loop能夠進行
void msg_queue_start(MessageQueue *q)
// msg_quene_put_xxx系列函數都會調用msg_queue_put_private
int msg_queue_put_private(MessageQueue *q, AVMessage *msg)
// 獲取MessageQueue中的第一條消息
int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
消息隊列初始化#
消息隊列初始化是在 IjkPlayer 播放器創建過程中初始化的,其關鍵函數調用如下:
IjkMediaPlayer_native_setup->ijkmp_android_create->ijkmp_create
這裡直接從 ijkmp_create 函數開始來看消息隊列的初始化。
消息隊列對應的是定義在 FFPlayer 中的 msg_queue 成員,在 IjkMediaPlayer 結構體創建過程中會調用函數 ffp_create 初始化 ffplayer,如下:
// ijkplayer.c
IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*)){
IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
if (!mp)
goto fail;
// 創建FFPlayer並初始化mp->ffplayer
mp->ffplayer = ffp_create();
// mp->msg_loop
mp->msg_loop = msg_loop;
// ...
}
繼續查看函數 ffp_create 實現:
// ff_ffplay.c
FFPlayer *ffp_create(){
// ...
// 消息隊列初始化
msg_queue_init(&ffp->msg_queue);
ffp->af_mutex = SDL_CreateMutex();
ffp->vf_mutex = SDL_CreateMutex();
// 內部調用msg_queue_flush
ffp_reset_internal(ffp);
// ...
return ffp;
}
在函數 ffp_create 中調用了 msg_queue_init 初始化了消息隊列 msg_queue,這裡會將 msg_loop 的 abort_request 置為 1,後續啟動消息循環線程的時候會將其置為 abort_request 置為 0。
ffp_reset_internal 中內部調用了 msg_queue_flush 重置了 msg_loop,到此消息隊列 msg_loop 完成了初始化。
繼續往下 mp->msg_loop = msg_loop 完成了消息循環函數的賦值,ijkmp_create 傳遞進來的消息循環函數是 message_loop,該函數將在後面小節中介紹,到此 message_loop 完成賦值。
函數指針 msg_loop 固然是一個函數,那麼 msg_loop 是什麼時候被調用 的呢?
消息循環的啟動#
消息循環的開始是 msg_queue_start 函數,其調用流程是從準備播放開始,即應用層調用 prepareAsync 開始準備播放時觸發,函數調用流程如下:
這裡看下 ijkmp_prepare_async_l 函數實現:
// ijkplayer.c
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp){
// ...
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");
// ...
return 0;
}
顯然調用了 msg_queue_start 開啟消息循環,看下 msg_queue_start 的函數實現:
// ff_ffmsg_queue.h
inline static void msg_queue_start(MessageQueue *q){
SDL_LockMutex(q->mutex);
// 關鍵
q->abort_request = 0;
AVMessage msg;
msg_init_msg(&msg);
msg.what = FFP_MSG_FLUSH;
msg_queue_put_private(q, &msg);
SDL_UnlockMutex(q->mutex);
}
這裡將 abort_request 置為 0 表示允許消息 AVMessage 入隊和出隊,不調用該函數則無法完成消息循環,故初始化消息循環 msg_queue 之後還需調用 msg_queue_start 來啟動消息循環。
繼續看下消息循環獲取函數 msg_queue_get 的實現,消息的獲取就是通過該函數不斷獲取通知到應用層的,參數 block 為 1 表示阻塞,0 表示不阻塞,根據調用這裡傳入的是 1,也就是當消息隊列 msg_quene 中沒有消息時會等待添加消息後繼續處理,如下:
// ff_ffmsg_queue.h
inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block){
AVMessage *msg1;
int ret;
SDL_LockMutex(q->mutex);
for (;;) {
// abort
if (q->abort_request) {
ret = -1;
break;
}
// 獲取隊首消息
msg1 = q->first_msg;
if (msg1) {// 處理隊列中的消息
q->first_msg = msg1->next;
if (!q->first_msg)
q->last_msg = NULL;
q->nb_messages--;
*msg = *msg1;
msg1->obj = NULL;
#ifdef FFP_MERGE
av_free(msg1);
#else
msg1->next = q->recycle_msg;
q->recycle_msg = msg1;
#endif
ret = 1;
break;
} else if (!block) {// 直接退出
ret = 0;
break;
} else {// 阻塞等待
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
上述代碼只有 q->abort_request 為 0 才會開始循環獲取消息,這也就是為什麼要使用 msg_queue_start 來開啟消息循環的原因。
這裡的開啟消息循環只是保證消息能夠正常出隊入隊,但是還是沒真正運行消息循環函數 msg_loop。
消息循環線程#
記得上文中留一個問題,msg_loop 是什麼時候被調用的呢,答案就是 msg_loop 是在消息循環線程中調用的,接著上文繼續看下 ijkmp_prepare_async_l,在該函數裡面先調用了 msg_queue_start,然後創建了消息循環線程,如下:
// ijkplayer.c
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp){
// ...
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");
// ...
return 0;
}
上述代碼中 SDL_CreateThreadEx 創建了線程名為 ff_msg_loop 的線程,線程體運行函數為 ijkmp_msg_loop, 同時 IjkMediaPlayer 結構體的成員 msg_thread 被賦值,當線程創建完成後運行線程體函數 ijkmp_msg_loop,如下:
// ijkplayer.c
static int ijkmp_msg_loop(void *arg){
IjkMediaPlayer *mp = arg;
// 調用消息循環函數
int ret = mp->msg_loop(arg);
return ret;
}
這裡完成了消息循環函數 msg_loop 的調用,到此 IjkPlayer 的消息循環真正啟動,下面繼續看下消息是如何發送到應用層的。
消息循環函數#
消息循環函數是在播放器創建時,在 IjkMediaPlayer_native_setup 裡面傳入的,如下:
// ijkplayer_jni.c
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);
// ...
}
message_loop 最終會被賦值給 IjkMediaPlayer 結構體的成員 msg_loop,從前文知道消息循環線程 msg_thread 中調用了消息循環函數 msg_loop,即這裡的 message_loop ,其實現如下:
// ijkplayer_jni.c
static int message_loop(void *arg){
// ...
IjkMediaPlayer *mp = (IjkMediaPlayer*) arg;
// 關鍵函數message_loop_n
message_loop_n(env, mp);
// ...
}
繼續看下關鍵函數 message_loop_n 的實現:
// ijkplayer_jni.c
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;
// 從MessageQueue獲取一個消息AVMessage
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_PREPARED:
MPTRACE("FFP_MSG_PREPARED:\n");
// 關鍵函數
post_event(env, weak_thiz, MEDIA_PREPARED, 0, 0);
break;
// ...
default:
ALOGE("unknown FFP_MSG_xxx(%d)\n", msg.what);
break;
}
// 內存資源回收
msg_free_res(&msg);
}
LABEL_RETURN:
;
}
可見在消息循環函數中會以死循環的方式通過 ijkmp_get_msg 獲取消息,然後通過 post_event 將消息發送給應用層:
// ijkplayer_jni.c
inline static void post_event(JNIEnv *env, jobject weak_this, int what, int arg1, int arg2){
// postEventFromNative
J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL);
}
函數 post_event 調用 Java 層的 postEventFromNative 方法完成消息的回傳,如下:
@CalledByNative
private static void postEventFromNative(Object weakThiz, int what,
int arg1, int arg2, Object obj) {
if (weakThiz == null)
return;
@SuppressWarnings("rawtypes")
IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
if (mp == null) {
return;
}
if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
// this acquires the wakelock if needed, and sets the client side
// state
mp.start();
}
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
// 發送消息
mp.mEventHandler.sendMessage(m);
}
}
postEventFromNative 收到底層 IjkPlayer 發送的消息將其轉換成 Message 交給 EventHandler 進行處理,如下:
private static class EventHandler extends Handler {
private final WeakReference<IjkMediaPlayer> mWeakPlayer;
public EventHandler(IjkMediaPlayer mp, Looper looper) {
super(looper);
mWeakPlayer = new WeakReference<IjkMediaPlayer>(mp);
}
@Override
public void handleMessage(Message msg) {
IjkMediaPlayer player = mWeakPlayer.get();
if (player == null || player.mNativeMediaPlayer == 0) {
DebugLog.w(TAG,
"IjkMediaPlayer went away with unhandled events");
return;
}
switch (msg.what) {
case MEDIA_PREPARED:
player.notifyOnPrepared();
return;
// ...
default:
DebugLog.e(TAG, "Unknown message type " + msg.what);
}
}
}
根據不同的消息類型進行處理,如上是 MEDIA_PREPARED 事件,最後回調給對應的回調接口,如這裡的 mOnPreparedListener,如下:
protected final void notifyOnPrepared() {
if (mOnPreparedListener != null)
// 播放準備完成事件
mOnPreparedListener.onPrepared(this);
}
到此消息循環函數執行完畢。
小結#
本文行文是從 Native 層開始,一直到 Java 層收到消息,IjkPlayer 的消息循環中最重要的就是消息隊列 msg_quene,播放器從起播到結束產生的相關事件消息都會添加到該隊列中,消息循環線程負責取出消息並通知出去,如果無消息可取,則會阻塞等待添加消息後繼續執行消息循環流程。