IjkPlayer系列之消息循环机制

前面两篇文章中介绍了 JNI 基础知识以及 IjkPlayer 播放器的创建流程。本文主要内容如下:

  1. AVMessage和MessageQueue
  2. 消息队列初始化
  3. 消息循环的启动
  4. 消息循环线程
  5. 消息循环函数
  6. 小结

AVMessage和MessageQueue

先来看看 AVMessage 和 MessageQueue 两个结构体定义:

 1 // ff_ffmsg_queue.h
 2 typedef 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
11 typedef 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
2 void msg_xxx(AVMessage *msg)
3 // MessageQueue
4 void msg_queue_xxx(MessageQueue *q)

MessageQueue 关键函数如下:

 1 // 初始化MessageQueue
 2 void msg_queue_init(MessageQueue *q)
 3 // 重置MessageQueue
 4 void msg_queue_flush(MessageQueue *q)
 5 // q->abort_request设置为0保证消息循环msg_loop能够进行
 6 void msg_queue_start(MessageQueue *q)
 7 // msg_quene_put_xxx系列函数都会调用msg_queue_put_private
 8 int msg_queue_put_private(MessageQueue *q, AVMessage *msg)
 9 // 获取MessageQueue中的第一条消息
10 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,如下:

 1 // ijkplayer.c
 2 IjkMediaPlayer *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
 2 FFPlayer *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
 2 static 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
 2 inline 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
 2 inline 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
 2 static 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
2 static 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
2 static void IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this){
3    MPTRACE("%sn", __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
2 static 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
 2 static 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    }
29 LABEL_RETURN:
30    ;
31 }

可见在消息循环函数中会以死循环的方式通过 ijkmp_get_msg 获取消息,然后通过 post_event 将消息发送给应用层:

1 // ijkplayer_jni.c
2 inline 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
 2 private 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 进行处理,如下:

 1 private static class EventHandler extends Handler {
 2    private final WeakReference<IjkMediaPlayer> mWeakPlayer;
 3    public EventHandler(IjkMediaPlayer mp, Looper looper) {
 4        super(looper);
 5        mWeakPlayer = new WeakReference<IjkMediaPlayer>(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,如下:

1 protected final void notifyOnPrepared() {
2    if (mOnPreparedListener != null)
3        // 播放准备完成事件
4        mOnPreparedListener.onPrepared(this);
5 }

到此消息循环函数执行完毕。

小结

本文行文是从 Native 层开始,一直到Java 层收到消息,IjkPlayer 的消息循环中最重要的就是消息队列 msg_quene,播放器从起播到结束产生的相关事件消息都会添加到该队列中,消息循环线程负责取出消息并通知出去,如果无消息可取,则会阻塞等待添加消息后继续执行消息循环流程。

作者: jzman,个人微信公众号:躬行之 ,可以关注一起交流学习.

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论