消息队列
在进行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层的
setup
将msg_loop
函数指针传递到ijkplayer实例,注意这个时候并没有调用msg_loop
这个方法。最后,初始化了MessageQueue - 在prepare的流程中的
ijkmp_prepare_async_l
方法时通过SDL_CreateThreadEx
去执行msg_loop
方法(注意,msg_loop
方法未执行不代表消息队列为null) - 消息为
FF_MSG_XXX
,代表着ffplay的消息 - 当消息从队列中取出时,共有两个loop去处理。分别为:
message_loop_n
和ijkmp_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模型。