PS: 最近读到一句歌德的一句诗:无论你能做什么,或者梦想做什么,着手开始吧,大胆就是天赋、能量和魔力的代名词。
前面两篇文章中介绍了 JNI 基础知识以及 IjkPlayer 播放器的创建流程:
本文主要内容如下:
AVMessage和MessageQueue
消息队列初始化
消息循环的启动
消息循环线程
消息循环函数
小结
AVMessage和MessageQueue 先来看看 AVMessage 和 MessageQueue 两个结构体定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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,如下:
1 2 3 4 void msg_xxx (AVMessage *msg) void msg_queue_xxx (MessageQueue *q)
MessageQueue 关键函数如下:
1 2 3 4 5 6 7 8 9 10 void msg_queue_init (MessageQueue *q) void msg_queue_flush (MessageQueue *q) void msg_queue_start (MessageQueue *q) int msg_queue_put_private (MessageQueue *q, AVMessage *msg) int msg_queue_get (MessageQueue *q, AVMessage *msg, int block)
消息队列初始化 消息队列初始化是在 IjkPlayer 播放器创建过程中初始化的,其关键函数调用如下:
1 IjkMediaPlayer_native_setup->ijkmp_android_create->ijkmp_create
这里直接从 ijkmp_create 函数开始来看消息队列的初始化。
消息队列对应的是定义在 FFPlayer 中的 msg_queue 成员,在 IjkMediaPlayer 结构体创建过程中会调用函数 ffp_create 初始化 ffplayer,如下:
1 2 3 4 5 6 7 8 9 10 11 IjkMediaPlayer *ijkmp_create (int (*msg_loop)(void *)) { IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof (IjkMediaPlayer)); if (!mp) goto fail; mp->ffplayer = ffp_create(); mp->msg_loop = msg_loop; }
继续查看函数 ffp_create 实现:
1 2 3 4 5 6 7 8 9 10 11 12 FFPlayer *ffp_create () { msg_queue_init(&ffp->msg_queue); ffp->af_mutex = SDL_CreateMutex(); ffp->vf_mutex = SDL_CreateMutex(); 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 开始准备播放时触发,函数调用流程如下:
sequenceDiagram
Activity->>IjkMediaPlayer.java: prepareAsync
IjkMediaPlayer.java->>ijkplayer_jni.c: _prepareAsync
ijkplayer_jni.c->>ijkplayer.c: ijkmp_prepare_async
ijkplayer.c->>ijkplayer.c: ijkmp_prepare_async_l
ijkplayer.c->>ff_ffmsg_queue.h: msg_queue_start
这里看下 ijkmp_prepare_async_l 函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int ijkmp_prepare_async_l (IjkMediaPlayer *mp) { ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING); msg_queue_start(&mp->ffplayer->msg_queue); 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 的函数实现:
1 2 3 4 5 6 7 8 9 10 11 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 中没有消息时会等待添加消息后继续处理,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 inline static int msg_queue_get (MessageQueue *q, AVMessage *msg, int block) { AVMessage *msg1; int ret; SDL_LockMutex(q->mutex); for (;;) { 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,然后创建了消息循环线程,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 static int ijkmp_prepare_async_l (IjkMediaPlayer *mp) { ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING); msg_queue_start(&mp->ffplayer->msg_queue); 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,如下:
1 2 3 4 5 6 7 static int ijkmp_msg_loop (void *arg) { IjkMediaPlayer *mp = arg; int ret = mp->msg_loop(arg); return ret; }
这里完成了消息循环函数 msg_loop 的调用,到此 IjkPlayer 的消息循环真正启动,下面继续看下消息是如何发送到应用层的。
消息循环函数 消息循环函数是在播放器创建时,在 IjkMediaPlayer_native_setup 里面传入的,如下:
1 2 3 4 5 6 7 static void IjkMediaPlayer_native_setup (JNIEnv *env, jobject thiz, jobject weak_this) { MPTRACE("%s\n" , __func__); IjkMediaPlayer *mp = ijkmp_android_create(message_loop); }
message_loop 最终会被赋值给 IjkMediaPlayer 结构体的成员 msg_loop,从前文知道消息循环线程 msg_thread 中调用了消息循环函数 msg_loop,即这里的 message_loop ,其实现如下:
1 2 3 4 5 6 7 8 static int message_loop (void *arg) { IjkMediaPlayer *mp = (IjkMediaPlayer*) arg; message_loop_n(env, mp); }
继续看下关键函数 message_loop_n 的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 ; 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 将消息发送给应用层:
1 2 3 4 5 inline static void post_event (JNIEnv *env, jobject weak_this, int what, int arg1, int arg2) { J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL ); }
函数 post_event 调用 Java 层的 postEventFromNative 方法完成消息的回传,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @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) { mp.start(); } if (mp.mEventHandler != null ) { Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); mp.mEventHandler.sendMessage(m); } }
postEventFromNative 收到底层 IjkPlayer 发送的消息将其转换成 Message 交给 EventHandler 进行处理,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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,如下:
1 2 3 4 5 protected final void notifyOnPrepared () { if (mOnPreparedListener != null ) mOnPreparedListener.onPrepared(this ); }
到此消息循环函数执行完毕。
小结 本文行文是从 Native 层开始,一直到Java 层收到消息,IjkPlayer 的消息循环中最重要的就是消息队列 msg_quene,播放器从起播到结束产生的相关事件消息都会添加到该队列中,消息循环线程负责取出消息并通知出去,如果无消息可取,则会阻塞等待添加消息后继续执行消息循环流程。