IjkPlayer系列之消息循环机制
共 9497字,需浏览 19分钟
·
2022-04-12 00:47
PS: 最近读到一句歌德的一句诗:无论你能做什么,或者梦想做什么,着手开始吧,大胆就是天赋、能量和魔力的代名词。
前面两篇文章中介绍了 JNI 基础知识以及 IjkPlayer 播放器的创建流程:
本文主要内容如下:
AVMessage和MessageQueue
消息队列初始化
消息循环的启动
消息循环线程
消息循环函数
小结
AVMessage和MessageQueue
先来看看 AVMessage
和 MessageQueue
两个结构体定义:
1// ff_ffmsg_queue.h
2typedef struct AVMessage {
3 int what;
4 int arg1;
5 int arg2;
6 void *obj;
7 void (*free_l)(void *obj);
8 struct AVMessage *next;
9} AVMessage;
10
11typedef struct MessageQueue {
12 AVMessage *first_msg, *last_msg;
13 int nb_messages;
14 int abort_request;
15 SDL_mutex *mutex;
16 SDL_cond *cond;
17
18 AVMessage *recycle_msg;
19 int recycle_count;
20 int alloc_count;
21} MessageQueue;
AVMessage
和 MessageQueue
的定义和实现都在 ff_ffmsg_queue.h 中,其相关操作函数主要是 msg_xxx
和 msg_quene_xxx
,如下:
1// AVMessage
2void msg_xxx(AVMessage *msg)
3// MessageQueue
4void msg_queue_xxx(MessageQueue *q)
5
MessageQueue
关键函数如下:
1// 初始化MessageQueue
2void msg_queue_init(MessageQueue *q)
3// 重置MessageQueue
4void msg_queue_flush(MessageQueue *q)
5// q->abort_request设置为0保证消息循环msg_loop能够进行
6void msg_queue_start(MessageQueue *q)
7// msg_quene_put_xxx系列函数都会调用msg_queue_put_private
8int msg_queue_put_private(MessageQueue *q, AVMessage *msg)
9// 获取MessageQueue中的第一条消息
10int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
11
消息队列初始化
消息队列初始化是在 IjkPlayer 播放器创建过程中初始化的,其关键函数调用如下:
1IjkMediaPlayer_native_setup->ijkmp_android_create->ijkmp_create
这里直接从 ijkmp_create
函数开始来看消息队列的初始化。
消息队列对应的是定义在 FFPlayer
中的 msg_queue
成员,在 IjkMediaPlayer
结构体创建过程中会调用函数 ffp_create
初始化 ffplayer
,如下:
1// ijkplayer.c
2IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*)){
3 IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
4 if (!mp)
5 goto fail;
6 // 创建FFPlayer并初始化mp->ffplayer
7 mp->ffplayer = ffp_create();
8 // mp->msg_loop
9 mp->msg_loop = msg_loop;
10 // ...
11}
继续查看函数 ffp_create
实现:
1// ff_ffplay.c
2FFPlayer *ffp_create(){
3 // ...
4 // 消息队列初始化
5 msg_queue_init(&ffp->msg_queue);
6 ffp->af_mutex = SDL_CreateMutex();
7 ffp->vf_mutex = SDL_CreateMutex();
8 // 内部调用msg_queue_flush
9 ffp_reset_internal(ffp);
10 // ...
11 return ffp;
12}
在函数 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
函数实现:
1// ijkplayer.c
2static int ijkmp_prepare_async_l(IjkMediaPlayer *mp){
3 // ...
4 ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);
5
6 // 开启消息循环
7 msg_queue_start(&mp->ffplayer->msg_queue);
8
9 // released in msg_loop
10 ijkmp_inc_ref(mp);
11 mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
12 // ...
13 return 0;
14}
显然调用了 msg_queue_start
开启消息循环,看下 msg_queue_start
的函数实现:
1// ff_ffmsg_queue.h
2inline static void msg_queue_start(MessageQueue *q){
3 SDL_LockMutex(q->mutex);
4 // 关键
5 q->abort_request = 0;
6 AVMessage msg;
7 msg_init_msg(&msg);
8 msg.what = FFP_MSG_FLUSH;
9 msg_queue_put_private(q, &msg);
10 SDL_UnlockMutex(q->mutex);
11}
这里将 abort_request
置为 0 表示允许消息 AVMessage
入队和出队,不调用该函数则无法完成消息循环,故初始化消息循环 msg_queue
之后还需调用 msg_queue_start
来启动消息循环。
继续看下消息循环获取函数 msg_queue_get
的实现,消息的获取就是通过该函数不断获取通知到应用层的,参数 block
为 1 表示阻塞,0 表示不阻塞,根据调用这里传入的是 1,也就是当消息队列 msg_quene
中没有消息时会等待添加消息后继续处理,如下:
1 // ff_ffmsg_queue.h
2inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block){
3 AVMessage *msg1;
4 int ret;
5 SDL_LockMutex(q->mutex);
6 for (;;) {
7 // abort
8 if (q->abort_request) {
9 ret = -1;
10 break;
11 }
12 // 获取队首消息
13 msg1 = q->first_msg;
14 if (msg1) {// 处理队列中的消息
15 q->first_msg = msg1->next;
16 if (!q->first_msg)
17 q->last_msg = NULL;
18 q->nb_messages--;
19 *msg = *msg1;
20 msg1->obj = NULL;
21#ifdef FFP_MERGE
22 av_free(msg1);
23#else
24 msg1->next = q->recycle_msg;
25 q->recycle_msg = msg1;
26#endif
27 ret = 1;
28 break;
29 } else if (!block) {// 直接退出
30 ret = 0;
31 break;
32 } else {// 阻塞等待
33 SDL_CondWait(q->cond, q->mutex);
34 }
35 }
36 SDL_UnlockMutex(q->mutex);
37 return ret;
38}
上述代码只有 q->abort_request
为 0 才会开始循环获取消息,这也就是为什么要使用 msg_queue_start
来开启消息循环的原因。
这里的开启消息循环只是保证消息能够正常出队入队,但是还是没真正运行消息循环函数 msg_loop
。
消息循环线程
记得上文中留一个问题,msg_loop
是什么时候被调用的呢,答案就是 msg_loop
是在消息循环线程中调用的,接着上文继续看下 ijkmp_prepare_async_l
,在该函数里面先调用了 msg_queue_start
,然后创建了消息循环线程,如下:
1// ijkplayer.c
2static int ijkmp_prepare_async_l(IjkMediaPlayer *mp){
3 // ...
4 ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);
5 // 开启消息循环
6 msg_queue_start(&mp->ffplayer->msg_queue);
7 // released in msg_loop
8 ijkmp_inc_ref(mp);
9 // 创建消息循环线程
10 mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
11 // ...
12 return 0;
13}
上述代码中 SDL_CreateThreadEx
创建了线程名为 ff_msg_loop
的线程,线程体运行函数为 ijkmp_msg_loop
,同时 IjkMediaPlayer
结构体的成员 msg_thread
被赋值,当线程创建完成后运行线程体函数 ijkmp_msg_loop
,如下:
1// ijkplayer.c
2static int ijkmp_msg_loop(void *arg){
3 IjkMediaPlayer *mp = arg;
4 // 调用消息循环函数
5 int ret = mp->msg_loop(arg);
6 return ret;
7}
这里完成了消息循环函数 msg_loop
的调用,到此 IjkPlayer 的消息循环真正启动,下面继续看下消息是如何发送到应用层的。
消息循环函数
消息循环函数是在播放器创建时,在 IjkMediaPlayer_native_setup
里面传入的,如下:
1// ijkplayer_jni.c
2static void IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this){
3 MPTRACE("%s\n", __func__);
4 // 创建C层对应的IjkMediaPlayer
5 IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
6 // ...
7}
message_loop
最终会被赋值给 IjkMediaPlayer
结构体的成员 msg_loop
,从前文知道消息循环线程 msg_thread
中调用了消息循环函数 msg_loop
,即这里的 message_loop
,其实现如下:
1// ijkplayer_jni.c
2static int message_loop(void *arg){
3 // ...
4 IjkMediaPlayer *mp = (IjkMediaPlayer*) arg;
5 // 关键函数message_loop_n
6 message_loop_n(env, mp);
7 // ...
8}
继续看下关键函数 message_loop_n
的实现:
1// ijkplayer_jni.c
2static void message_loop_n(JNIEnv *env, IjkMediaPlayer *mp){
3 jobject weak_thiz = (jobject) ijkmp_get_weak_thiz(mp);
4 JNI_CHECK_GOTO(weak_thiz, env, NULL, "mpjni: message_loop_n: null weak_thiz", LABEL_RETURN);
5 while (1) {
6 AVMessage msg;
7 // 从MessageQueue获取一个消息AVMessage
8 int retval = ijkmp_get_msg(mp, &msg, 1);
9 if (retval < 0)
10 break;
11
12 // block-get should never return 0
13 assert(retval > 0);
14 // 处理各种播放事件
15 switch (msg.what) {
16 case FFP_MSG_PREPARED:
17 MPTRACE("FFP_MSG_PREPARED:\n");
18 // 关键函数
19 post_event(env, weak_thiz, MEDIA_PREPARED, 0, 0);
20 break;
21 // ...
22 default:
23 ALOGE("unknown FFP_MSG_xxx(%d)\n", msg.what);
24 break;
25 }
26 // 内存资源回收
27 msg_free_res(&msg);
28 }
29LABEL_RETURN:
30 ;
31}
可见在消息循环函数中会以死循环的方式通过 ijkmp_get_msg
获取消息,然后通过 post_event
将消息发送给应用层:
1// ijkplayer_jni.c
2inline static void post_event(JNIEnv *env, jobject weak_this, int what, int arg1, int arg2){
3 // postEventFromNative
4 J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL);
5}
函数 post_event
调用 Java 层的 postEventFromNative
方法完成消息的回传,如下:
1@CalledByNative
2private static void postEventFromNative(Object weakThiz, int what,
3 int arg1, int arg2, Object obj) {
4 if (weakThiz == null)
5 return;
6
7 @SuppressWarnings("rawtypes")
8 IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
9 if (mp == null) {
10 return;
11 }
12
13 if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
14 // this acquires the wakelock if needed, and sets the client side
15 // state
16 mp.start();
17 }
18 if (mp.mEventHandler != null) {
19 Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
20 // 发送消息
21 mp.mEventHandler.sendMessage(m);
22 }
23}
postEventFromNative
收到底层 IjkPlayer 发送的消息将其转换成 Message
交给 EventHandler
进行处理,如下:
1private static class EventHandler extends Handler {
2 private final WeakReferencemWeakPlayer;
3 public EventHandler(IjkMediaPlayer mp, Looper looper) {
4 super(looper);
5 mWeakPlayer = new WeakReference(mp);
6 }
7 @Override
8 public void handleMessage(Message msg) {
9 IjkMediaPlayer player = mWeakPlayer.get();
10 if (player == null || player.mNativeMediaPlayer == 0) {
11 DebugLog.w(TAG,
12 "IjkMediaPlayer went away with unhandled events");
13 return;
14 }
15 switch (msg.what) {
16 case MEDIA_PREPARED:
17 player.notifyOnPrepared();
18 return;
19 // ...
20 default:
21 DebugLog.e(TAG, "Unknown message type " + msg.what);
22 }
23 }
24}
根据不同的消息类型进行处理,如上是 MEDIA_PREPARED
事件,最后回调给对应的回调接口,如这里的 mOnPreparedListener
,如下:
1protected final void notifyOnPrepared() {
2 if (mOnPreparedListener != null)
3 // 播放准备完成事件
4 mOnPreparedListener.onPrepared(this);
5}
到此消息循环函数执行完毕。
小结
msg_quene
,播放器从起播到结束产生的相关事件消息都会添加到该队列中,消息循环线程负责取出消息并通知出去,如果无消息可取,则会阻塞等待添加消息后继续执行消息循环流程。推荐阅读: