IJKPlayer阅读笔记(三)消息队列机制

1,209 阅读4分钟

消息队列

在进行prepare流程之前,先了解下ijkplayer内部的消息队列机制。 先来看下IjkMediaPlayer_native_setup方法

  • IjkMediaPlayer_native_setup
    该方法位于ijkplayer_jni.c
static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) {
    ...
    //初始化,该方法中初始化了ijkplayer
    IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
    ...
}

这里将message_loop方法传递到ijkmp_android_create中。注意,这个时候并没有去执行message_loop函数。我们跟一下ijkmp_android_create方法

  • ijkmp_android_create
    该方法位于ijkplayer_android.c
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
    ...
    //这里继续初始化,ijkmp_create初始完成之后会返回IjkMediaPlayer指针
    IjkMediaPlayer *mp = ijkmp_create(msg_loop);
    ...
    return mp;
}

这个函数将message_loop函数指针传递到了ijkmp_create

  • ijkmp_create
    该方法位于ijkplayer.c
IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void *)) {
    ...
    mp->msg_loop = msg_loop;
    ...
}

这个方法把message_loop函数指针赋值给了ijkplayer中的msg_loop。 到这里(ijkplayer)初始化流程就结束了。初始化时并未调用message_loop函数,只是将函数指针传递到了ijkplayer中的msg_loop属性

调用

那么这个方法在哪里调用了呢?答案是prepare流程的ijkmp_prepare_async_l方法

  • ijkmp_prepare_async_l
    该方法位于ijkplayer.c
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp) {
    ...
    mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
    ...
}

这里创建了一个线程ijkmp_msg_loop

  • ijkmp_msg_loop
    该方法位于ijkplayer.c
static int ijkmp_msg_loop(void *arg) {
    IjkMediaPlayer *mp = arg;
    int ret = mp->msg_loop(arg);
    return ret;
}

在这里我们看到了他执行了message_loop方法,参数为IJKMediaPlayer对象 好了到了这里,我们可以看message_loop的代码了

具体代码

  • message_loop
    该方法位于ijkplayer_jni.c
static int message_loop(void *arg) {
    ...
    IjkMediaPlayer *mp = (IjkMediaPlayer *) arg;
    message_loop_n(env, mp);
    ...
}

这里调用了message_loop_n函数

  • message_loop_n 该方法位于ijkplayer_jni.c
static void message_loop_n(JNIEnv *env, IjkMediaPlayer *mp) {
    jobject weak_thiz = (jobject) ijkmp_get_weak_thiz(mp);

    while (1) {
        AVMessage msg;
        int retval = ijkmp_get_msg(mp, &msg, 1);
        //省略掉msg的具体操作
        }
        msg_free_res(&msg);
    }
}

先忽略掉msg的具体操作,先来看一下ijkmp_get_msg方法

获取消息

  • ijkmp_get_msg方法
    该方法位于ijkplayer.c
int ijkmp_get_msg(IjkMediaPlayer *mp, AVMessage *msg, int block) {
    while (1) {
        int continue_wait_next_msg = 0;
        int retval = msg_queue_get(&mp->ffplayer->msg_queue, msg, block);
        if (retval <= 0)
            return retval;
        //忽略msg部分
        return retval;
    }
    return -1;
}

我们来看一下msg_queue_get方法

  • msg_queue_get 该方法位于ijkplayer/ff_ffmsg_queue.h
inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
{
    AVMessage *msg1;
    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);
        }
    }
    return ret;
}

该方法是内联方法,终止请求(abort_request)返回-1。这段代码并不复杂,就是从MessageQueue中取数据,取到的数据赋值给第二个参数。那么MessageQueue中的数据是什么时候放入的呢?

MessageQueue数据的放入

根据get我们能想到放入的方法应该为set(结果是put,差不多),那么msg_queue_put方法在什么时候调用呢? 寻找全文发现对外暴露的是ffp_notify_msgX(X为参数+what)这几个方法,这些方法在read_thread或播放器更改状态时会调用,传递的msg.what为相对应的状态。

MessageQueue总结

ijkplayer更改状态或执行I/O流(网络流)读取时,会将对应的状态放入到MessageQueue中。和handler相似,loop操作会不间断的从MessageQueue中去取数据。
到了这里我们理清了消息队列的机制。接下来我们从头阅读下ijk的消息机制

初次总结

组成部分

Java

java层很简单,一个用于从Native层接受传递过来的msg.what;一个原生的handler用于发送接收到的msg.what;一个用于处理消息的handler回调接口(android的handler机制)

Native

Native部分相对于复杂一点,首先由MessageQueue组成消息队列,接着由for循环充当loop

再次阅读

  • 首先在Native层的setupmsg_loop函数指针传递到ijkplayer实例,注意这个时候并没有调用msg_loop这个方法。最后,初始化了MessageQueue
  • prepare的流程中的ijkmp_prepare_async_l方法时通过SDL_CreateThreadEx去执行msg_loop方法(注意,msg_loop方法未执行不代表消息队列为null
  • 消息为FF_MSG_XXX,代表着ffplay的消息
  • 当消息从队列中取出时,共有两个loop去处理。分别为:message_loop_nijkmp_get_msg
msg.what ijkmp_get_msg message_loop_n 是否在ijkmp_get_msg中继续等待下一条消息
FFP_MSG_PREPARED 处理 -
FFP_MSG_COMPLETED 处理 -
FFP_MSG_SEEK_COMPLETE 处理 -
FFP_REQ_START 处理 -
FFP_REQ_PAUSE 处理 -
FFP_REQ_SEEK 处理 -
... -

我们发现在ijkmp_get_msg中处理并且需要等待下一条消息的三种状态都是FFP_REQ开头的,这代表着他们是请求。
既然是请求,是不是意味着它们是从java层进行对应的操作时传递过来呢?
我们留着这个疑问,等待下次阅读这几个对应操作时再来解答这个问题。

总结

ijk的消息队列模型就是简化的handler对应的模型,这个模型很常见在ffplay、MediaCodec(Native)硬解的官方demo中都应用到这个模型,如果理解困难的话,不防先阅读handler模型。