上篇博客 ijkplayer 源码分析(1):初始化流程 的 4.1.1 ijkmp_create() 的部分简要说明了下 ijkplayer 的消息处理机制,本文再根据源码进行详细分析,搞清楚其消息机制及处理流程。
播放器是一个较为复杂的多线程工程,如数据读取线程、音频解码线程、视频解码线程、视频播放线程等。各个线程都需要产生事件,如状态改变、出错等,需要传递给控制层和业务层去处理。因此需要一个消息处理机制来完成这些工作,把各个线程产生的事件转化成消息来处理。当然也可以通过接口回调的方式来处理,但样式繁多的事件会需要极其庞大复杂的接口,会很难维护和扩展。因此消息机制是一个很不错的方案。
通过分析 ijkplayer 源码会发现,ijkplayer 实现的消息处理机制和 Android 端的 Handler、Looper、Message、MessageQueue 的消息分发和处理机制很类似。下面我们就详细分析下 ijkplayer 的消息分发处理机制。
本文是基于 A4ijkplayer 项目进行 ijkplayer 源码分析,该项目是将 ijkplayer 改成基于 CMake 编译,可导入 Android Studio 编译运行,方便代码查找、函数跳转、单步调试、调用栈跟踪等。
我们先来回顾下 Android 端 Handler、Looper、Message、MessageQueue 的消息分发和处理机制,这有助于我们理解 ijkplayer 的消息处理机制。
Android 的消息分发处理机制如下:
如上图,ijkplayer 的消息分发处理机制和 Android 类似,采用的生产者—消费者模式。生产者(可以是多个)从任意线程生产消息,将其加入到消息队列中,消费者(只有一个)在一个独立的线程循环从消息队列取出消息进行分发处理,如果队列为空则等待。
ijkplayer 中的消息结构体为 AVMessage,消息队列结构体为 MessageQueue,均位于 ijkmedia/ijkplayer/ff_ffmsg_queue.h
文件中,结构如下,可以看到跟 Android 的 Message 很像。
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;
// 初始化消息体,实际就是调用 memeset 重置内存
inline static void msg_init_msg(AVMessage *msg)
// 释放 msg 的参数 obj 内存,并将其置空,注:不是释放 msg 内存
inline static void msg_free_res(AVMessage *msg)
// 初始化 MessageQueue
inline static void msg_queue_init(MessageQueue *q)
// 释放 MessageQueue 内所有 Message 内存及内部的 Mutex、Condition 内存,并不释放 MessageQueue 本身内存,需外部 free 或 delete
inline static void msg_queue_destroy(MessageQueue *q)
// 启用 MessageQueue 并填入一个 FFP_MSG_FLUSH 消息
inline static void msg_queue_start(MessageQueue *q)
// 终止使用 MessageQueue,调用后可通过 msg_queue_start 重新启用
inline static void msg_queue_abort(MessageQueue *q)
// 以下 put 系列是向 MessageQueue 添加消息,只是参数不同
inline static int msg_queue_put(MessageQueue *q, AVMessage *msg)
inline static void msg_queue_put_simple1(MessageQueue *q, int what)
inline static void msg_queue_put_simple2(MessageQueue *q, int what, int arg1)
inline static void msg_queue_put_simple3(MessageQueue *q, int what, int arg1, int arg2)
inline static void msg_queue_put_simple4(MessageQueue *q, int what, int arg1, int arg2, void *obj, int obj_len)
// 从队列队首取出消息
inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
// 移除指定 msg.what 的消息
inline static void msg_queue_remove(MessageQueue *q, int what)
// 清空 MessageQueue
inline static void msg_queue_flush(MessageQueue *q)
// 创建、初始化和启用 MessageQueue
MessageQueue* q = (MessageQueue*)malloc(sizeof(MessageQueue);
msg_queue_init(q);
msg_queue_start(q);
// put 消息
// get 消息
// 终止、销毁和释放 MessageQueue,创建时的逆过程
msg_queue_abort(q);
msg_queue_destroy(q);
free(q);
q = NULL;
跟进并分析 ijkplayer 源码,我们来梳理下 ijkplayer 消息机制的创建流程。如下图:
根据上篇博客 ijkplayer 源码分析(1):初始化流程 可知,消息机制最开始是在 Java 层构造函数中调用 native_setup()
然后调用到 native 层的 IjkMediaPlayer_native_setup()
时开始创建的。
static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
// ...
}
调用 ijkmp_create()
传入函数指针 message_loop,message_loop 函数内容会在后面分析。
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
IjkMediaPlayer *mp = ijkmp_create(msg_loop);
// ...
}
接着看 ijkmp_create()
函数,这里调用 ffp_create()
,并保存 msg_loop 函数指针,后面再 prepareAsync 的时候通过 IjkMediaPlayer 区到 msg_loop 函数,放入消息处理线程中执行。
IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
// ...
mp->ffplayer = ffp_create(); // 创建 FFPlayer
// ...
mp->msg_loop = msg_loop; // 保存 msg_loop 函数指针
}
接着看 ffp_create()
函数,和消息相关的是初始化消息队列,并情况队列。
FFPlayer *ffp_create()
{
// ...
FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
// ...
msg_queue_init(&ffp->msg_queue); // 初始化消息队列
// ...
ffp_reset_internal(ffp); // 内部会执行 msg_queue_flush() 清空消息队列
return ffp;
}
以上在 native_setup()
函数中就创建好并初始化了 MessageQueue,并将 message_loop 函数指针保存到了 IjkMediaPlayer 中。那消息队列和消息处理线程是什么时候启用和流转起来的呢?
分析 ijkplayer 源码,发现其是在 prepareAsync()
中启用的。
static void
IjkMediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)
{
// ...
retval = ijkmp_prepare_async(mp);
// ...
}
int ijkmp_prepare_async(IjkMediaPlayer *mp)
{
...
int retval = ijkmp_prepare_async_l(mp);
...
}
在 ijkmp_prepare_async_l()
中做了两件事:
msg_queue_start()
启用消息队列,并添加一个 FFP_MSG_FLUSH 消息(在切换资源后调用 prepareAsync() 后会清空消息队列)。ijkmp_msg_loop()
,其内部实际是之前在 native_setup()
函数中传入并保存的 msg_loop 函数指针,也就是 message_loop()
函数,如下:static int ijkmp_msg_loop(void *arg)
{
IjkMediaPlayer *mp = arg;
int ret = mp->msg_loop(arg);
return ret;
}
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
// ...
// 启用消息队列
msg_queue_start(&mp->ffplayer->msg_queue);
// ...
// 创建消息队列处理线程
mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
//...
return 0;
}
inline static void msg_queue_start(MessageQueue *q)
{
SDL_LockMutex(q->mutex);
// 启用消息队列
q->abort_request = 0;
// 添加一个 FFP_MSG_FLUSH 消息
AVMessage msg;
msg_init_msg(&msg);
msg.what = FFP_MSG_FLUSH;
msg_queue_put_private(q, &msg);
SDL_UnlockMutex(q->mutex);
}
我们看下 message_loop()
函数,在 ijkmedia/ijkplayer/android/ijkplayer_jni.c
文件中,如下:
static int message_loop(void *arg)
{
// ...
message_loop_n(env, mp);
// ...
}
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);
if (retval < 0)
break;
switch (msg.what) {
// ...
case FFP_MSG_FLUSH:
// 发送消息到上层
post_event(env, weak_thiz, MEDIA_NOP, 0, 0);
break;
case FFP_MSG_PREPARED:
post_event(env, weak_thiz, MEDIA_PREPARED, 0, 0);
break;
// ...
}
// 释放消息的参数内存
msg_free_res(&msg);
}
}
这里主要有两个函数:
ijkmp_get_msg()
post_event()
。我们先看 ijkmp_get_msg()
从消息队列取消息:
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;
switch (msg->what) {...}
if (continue_wait_next_msg) {
msg_free_res(msg);
continue;
}
return retval;
}
return -1;
}
我们再看 post_event()
函数:
在上面的 message_loop_n()
函数中,取到在 msg_queue_start()
时添加的 FFP_MSG_FLUSH 消息(消息队列启用后的第一个消息)后会调用 post_event()
函数发送 msg.what = MEDIA_NOP 的事件:
case FFP_MSG_FLUSH:
// 发送消息到上层
post_event(env, weak_thiz, MEDIA_NOP, 0, 0);
break;
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);
}
J4AC_IjkMediaPlayer__postEventFromNative
是在 ijkmedia/ijkj4a/j4a/class/tv/danmaku/ijk/media/player/IjkMediaPlayer.h
文件中定义的宏,实际调用的是下面函数:
void J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__postEventFromNative(JNIEnv *env, jobject weakThiz, jint what, jint arg1, jint arg2, jobject obj)
{
(*env)->CallStaticVoidMethod(env, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.id, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative, weakThiz, what, arg1, arg2, obj);
}
method_postEventFromNative
的相关定义是在 ijkmedia/ijkj4a/j4a/class/tv/danmaku/ijk/media/player/IjkMediaPlayer.c
文件中,如下:
class_id = class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.id;
name = "postEventFromNative";
sign = "(Ljava/lang/Object;IIILjava/lang/Object;)V";
class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative = J4A_GetStaticMethodID__catchAll(env, class_id, name, sign);
其对应的是 Java 层的静态函数 postEventFromNative()
,也就是底层调用 post_envent()
最终会通过 JNI 回调到 Java 层 postEventFromNative()
函数(在 IjkMediaPlayer.java 中)。
@CalledByNative
private static void postEventFromNative(Object weakThiz, int what,
int arg1, int arg2, Object obj) {
// ...
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
然后会被发送到 EventHandler 中由其 handleMessage()
处理,这里会接受到 native 启用消息队列后的第一个 msg.what = MEDIA_NOP 的消息,Java 忽略了该消息:
private static class EventHandler extends Handler {
// ...
@Override
public void handleMessage(Message msg) {
// ...
switch (msg.what) {
case MEDIA_PREPARED:
player.notifyOnPrepared();
return;
case MEDIA_NOP: // interface test message - ignore
break;
// ...
}
}
以上就串起了 ijkplayer 的整个消息机制和启用流程,从 Java 到 native 再到 Java 的整个流程。