ijkplayer 源码分析(2):消息分发处理机制

1、引言

上篇博客 ijkplayer 源码分析(1):初始化流程4.1.1 ijkmp_create() 的部分简要说明了下 ijkplayer 的消息处理机制,本文再根据源码进行详细分析,搞清楚其消息机制及处理流程。

播放器是一个较为复杂的多线程工程,如数据读取线程、音频解码线程、视频解码线程、视频播放线程等。各个线程都需要产生事件,如状态改变、出错等,需要传递给控制层和业务层去处理。因此需要一个消息处理机制来完成这些工作,把各个线程产生的事件转化成消息来处理。当然也可以通过接口回调的方式来处理,但样式繁多的事件会需要极其庞大复杂的接口,会很难维护和扩展。因此消息机制是一个很不错的方案。

通过分析 ijkplayer 源码会发现,ijkplayer 实现的消息处理机制和 Android 端的 Handler、Looper、Message、MessageQueue 的消息分发和处理机制很类似。下面我们就详细分析下 ijkplayer 的消息分发处理机制。

本文是基于 A4ijkplayer 项目进行 ijkplayer 源码分析,该项目是将 ijkplayer 改成基于 CMake 编译,可导入 Android Studio 编译运行,方便代码查找、函数跳转、单步调试、调用栈跟踪等。

2、消息分发处理机制

2.1 Android 的消息分发处理机制

我们先来回顾下 Android 端 Handler、Looper、Message、MessageQueue 的消息分发和处理机制,这有助于我们理解 ijkplayer 的消息处理机制。

Android 的消息分发处理机制如下:

  • 通过 Handler 的 sendMessage() 方法发送一个消息

  • Looper 将该消息加入到消息队列 MessageQueue 中

  • Looper 所在线程循环遍历 MessageQueue 从中取出消息分发,如果没有消息则挂起等待

  • 分发到 Handler 执行 handleMessage() 处理消息 以上就完成了消息的发送和处理流程

2.2 ijkplayer 的消息分发处理机制

如上图,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;

2.2.1 Message 提供的消息相关的处理函数:

// 初始化消息体,实际就是调用 memeset 重置内存
inline static void msg_init_msg(AVMessage *msg)
​
// 释放 msg 的参数 obj 内存,并将其置空,注:不是释放 msg 内存
inline static void msg_free_res(AVMessage *msg)

2.2.2 MessageQueue 提供的消息队列相关的处理函数:

// 初始化 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)

2.2.3 创建和销毁 MessageQueue 的使用方法

 // 创建、初始化和启用 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;

3、ijkplayer 消息机制与流程详解

跟进并分析 ijkplayer 源码,我们来梳理下 ijkplayer 消息机制的创建流程。如下图:

3.1 IjkMediaPlayer_native_setup()

根据上篇博客 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 中。那消息队列和消息处理线程是什么时候启用和流转起来的呢?

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

3.2 IjkMediaPlayer_prepareAsync()

分析 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() 后会清空消息队列)。

  • 创建和启动消息处理线程 线程名为: ff_msg_loop,线程执行的函数体为: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);
}

3.3 ijkplayer 消息的消费与分发

我们看下 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 的整个流程。

原文链接:ijkplayer 源码分析(2):消息分发处理机制 - 掘金

猜你喜欢

转载自blog.csdn.net/irainsa/article/details/130172955