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

这篇具有很好参考价值的文章主要介绍了ijkplayer 源码分析(2):消息分发处理机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

一、引言

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

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

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

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

二、消息分发处理机制

1、Android 的消息分发处理机制

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

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

  • 通过 Handler 的 sendMessage() 方法发送一个消息
  • Looper 将该消息加入到消息队列 MessageQueue 中
  • Looper 所在线程循环遍历 MessageQueue 从中取出消息分发,如果没有消息则挂起等待
  • 分发到 Handler 执行 handleMessage() 处理消息
    以上就完成了消息的发送和处理流程
    ijkplayer 源码分析(2):消息分发处理机制

2、ijkplayer 的消息分发处理机制

ijkplayer 源码分析(2):消息分发处理机制
如上图,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.1 Message 提供的消息相关的处理函数:

// 初始化消息体,实际就是调用 memeset 重置内存
inline static void msg_init_msg(AVMessage *msg)

// 释放 msg 的参数 obj 内存,并将其置空,注:不是释放 msg 内存
inline static void msg_free_res(AVMessage *msg)

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.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;

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

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

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

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、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 的整个流程。文章来源地址https://www.toymoban.com/news/detail-405087.html

到了这里,关于ijkplayer 源码分析(2):消息分发处理机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android源码解析--享元设计模式,handler消息传递机制(基于Android API 33 SDK分析)

    使用共享对象可有效地支持大量的细粒度的对象 核心:对象复用。 1.1 享元模式Demo 火车票购票Demo 缓存对象在一个Map中。下面我们还会分析 用法 跟进去 这就是最明显的一个享元设计模式。 Android 开发一个知识点:UI 不能够在子线程中更新。 我们跟进post函数 Handler 传递了一个

    2024年02月11日
    浏览(31)
  • 【linux 多线程并发】多线程模型下的信号通信处理,与多进程处理的比较,属于相同进程的线程信号分发机制

    ​ 专栏内容 : 参天引擎内核架构 本专栏一起来聊聊参天引擎内核架构,以及如何实现多机的数据库节点的多读多写,与传统主备,MPP的区别,技术难点的分析,数据元数据同步,多主节点的情况下对故障容灾的支持。 手写数据库toadb 本专栏主要介绍如何从零开发,开发的

    2024年01月17日
    浏览(43)
  • Android OkHttp源码分析--分发器

    OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中 的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。 OKHttp优点: 1、支持Http1、Http2、Quic以及WebSocket; 2、连接池复用底层TCP(Socket),减少请求

    2024年02月13日
    浏览(37)
  • 安卓之异步消息处理机制

    简介 为什么叫异步消息处理机制? 先来看同步消息机制,它是指发送方必须等待接收方处理完消息后才能继续执行,也就是顺序执行。在 Android 中,如果在主线程中执行耗时操作,就会导致主线程阻塞,应用无法响应用户交互,这就是同步阻塞的例子。因此,需要在子线程

    2024年02月12日
    浏览(33)
  • 高效处理消息:使用Spring Boot实现消息重试机制

    当涉及到消息发送和接收的可靠性,Spring Boot提供了一些机制来确保消息的可靠传递。其中包括消息确认机制和重试机制。下面是一个示例代码,演示如何在Spring Boot中实现可靠的消息发送和接收。 首先,我们需要配置RabbitMQ的连接信息和相关属性。在 application.properties 文件中

    2024年02月11日
    浏览(43)
  • Android之handler消息处理机制详解

    handler是什么? ​ Handler是一个在消息处理机制中负责发送和处理消息的类,是消息处理的关键。 Handler:负责 发送消息 和处理消息 Looper:内置一个死循环,可以不断的 取出消息 并通知handler处理消息,是handler起作用的基础 Message:消息实体类 MessageQueue:消息队列,负责存储消

    2024年02月04日
    浏览(44)
  • Rust 基础入门 ——所有权 引言 :垃圾自动回收机制的缺陷。

    在以往,内存安全几乎都是通过 GC 的方式实现,但是 GC 会引来性能、内存占用以及 Stop the world 等问题,在高性能场景和系统编程上是不可接受的, 我们先介绍一下这些概念都是什么: 内存安全 是指程序在运行过程中不会访问未分配的内存或者已释放的内存,从而避免了内

    2024年02月11日
    浏览(31)
  • 源码分析——ArrayList源码+扩容机制分析

    ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。 ArrayList 继承于 AbstractList ,实现了 List , RandomAccess , Cloneable , ja

    2024年02月14日
    浏览(40)
  • 【Spring云原生系列】Spring RabbitMQ:异步处理机制的基础--消息队列 原理讲解+使用教程

    🎉🎉 欢迎光临,终于等到你啦 🎉🎉 🏅我是 苏泽 ,一位对技术充满热情的探索者和分享者。🚀🚀 🌟持续更新的专栏 《Spring 狂野之旅:从入门到入魔》 🚀 本专栏带你从Spring入门到入魔   这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://suzee.blog.

    2024年03月15日
    浏览(48)
  • 需求分析引言:架构漫谈(一)

    本文主要对架构的概念做一些介绍,并引申出需求分析的重要性。 后续准备做一个系列,定期介绍我工作以来的一些需求实现的案例。 注:因为架构的内容比较庞大,里面的每个点,都可以扩展成一系列的文章, 因此,本文只是漫谈,多数内容仅做介绍,后续有时间,我再

    2024年02月10日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包