代码编织梦想

一、引言:

上一篇博客ijkplayer播放器剖析(一)从应用层分析至Jni层的流程分析中分析了ijkplayer的整个流程,相信大家对其中的消息队列看的也是云里雾里的,所以这里单独对ijkplayer的消息机制做一个分析。

二、代码分析:

先看下消息机制是怎么创建起来的,创建的发起是native_setup函数:

static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    MPTRACE("%s\n", __func__);
    IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
	...
}

需要注意的是ijkmp_android_create的入参是一个函数指针:

IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
    IjkMediaPlayer *mp = ijkmp_create(msg_loop);
    if (!mp)
        goto fail;
	...
}

这里传入了函数message_loop,函数里面的内容后面分析,可以看到,ijkmp_create的入参也是一个函数指针:

IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
    IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
    if (!mp)
        goto fail;
	/* 创建FFmpeg */
    mp->ffplayer = ffp_create();
    if (!mp->ffplayer)
        goto fail;
	/* 对mp->msg_loop进行赋值 */
    mp->msg_loop = msg_loop;
	...
}

这里面的流程我们也比较熟悉了,首先是去底层创建FFmpeg,之后会将上面传下来的msg_loop赋值给IjkMediaPlayer结构体维护的函数指针变量msg_loop。进入ffp_create函数看一下跟消息队列相关的内容:

FFPlayer *ffp_create()
{
    av_log(NULL, AV_LOG_INFO, "av_version_info: %s\n", av_version_info());
    av_log(NULL, AV_LOG_INFO, "ijk_version_info: %s\n", ijk_version_info());

	/* 1.申请FFPlayer结构体内存并初始化为0 */
    FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
    if (!ffp)
        return NULL;
	/* 2.初始化ffp的消息队列msg_queue */
    msg_queue_init(&ffp->msg_queue);
    ffp->af_mutex = SDL_CreateMutex();
    ffp->vf_mutex = SDL_CreateMutex();
	/* 3.对FFPlayer结构体成员进行reset操作 */
    ffp_reset_internal(ffp);
    ffp->av_class = &ffp_context_class;
    ffp->meta = ijkmeta_create();

    av_opt_set_defaults(ffp);

    las_stat_init(&ffp->las_player_statistic);
    return ffp;
}

进入函数,首先是对FFPlayer内存的初始化,接下来,会去调用msg_queue_init对消息队列进行一个初始化,看一下函数实现:

inline static void msg_queue_init(MessageQueue *q)
{
    memset(q, 0, sizeof(MessageQueue));
    /* 创建消息队列互斥锁 */
    q->mutex = SDL_CreateMutex();
    /* 创建消息队列信号量 */
    q->cond = SDL_CreateCond();
    /* abort_request变量用于记录队列是否处理消息 */
    q->abort_request = 1;
}

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

需要注意的是最后一行的abort_request变量,值为1表示消息队列不处理消息,值为0表示处理消息队列中的消息。再回到ffp_create看下ffp_reset_internal函数,其中有对消息处理的地方:

inline static void ffp_reset_internal(FFPlayer *ffp)
{
	...
	msg_queue_flush(&ffp->msg_queue);
	...
}

看下msg_queue_flush函数操作:

inline static void msg_queue_flush(MessageQueue *q)
{
    AVMessage *msg, *msg1;

    SDL_LockMutex(q->mutex);
    for (msg = q->first_msg; msg != NULL; msg = msg1) {
        msg1 = msg->next;
#ifdef FFP_MERGE
        av_freep(&msg);
#else
        msg->next = q->recycle_msg;
        q->recycle_msg = msg;
#endif
    }
    q->last_msg = NULL;
    q->first_msg = NULL;
    q->nb_messages = 0;
    SDL_UnlockMutex(q->mutex);
}

这个函数的主要作用是将消息队列中的相关变量清零。为后续的消息处理做好准备。

接下来看下消息队列是怎么转起来的,跟进到_prepareAsync这个native接口:

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:

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)
{
	...
	/* 1.开启消息队列 */
    msg_queue_start(&mp->ffplayer->msg_queue);

	/* 2.创建消息队列处理线程 */
    // released in msg_loop
    ijkmp_inc_ref(mp);
    mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
    // msg_thread is detached inside msg_loop
    // TODO: 9 release weak_thiz if pthread_create() failed;

	...

    return 0;
}

首先看msg_queue_start函数:

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);
}

回到上面消息处理线程,跟进到ijkmp_msg_loop:

static int ijkmp_msg_loop(void *arg)
{
    IjkMediaPlayer *mp = arg;
    /* 调用mp->msg_loop指向的函数来处理消息 */
    int ret = mp->msg_loop(arg);
    return ret;
}

我们前面已经分析了mp->msg_loop指向的函数是msg_loop@ijkplayer.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);
    JNI_CHECK_GOTO(weak_thiz, env, NULL, "mpjni: message_loop_n: null weak_thiz", LABEL_RETURN);

    while (1) {
        AVMessage msg;
		/* 1.从ijkplayer中获取一个message */
        int retval = ijkmp_get_msg(mp, &msg, 1);
        if (retval < 0)
            break;

        // block-get should never return 0
        assert(retval > 0);
		/* 2.通过msg.what进行消息处理 */
        switch (msg.what) {
            case FFP_MSG_FLUSH:
            	MPTRACE("FFP_MSG_FLUSH:\n");
            	post_event(env, weak_thiz, MEDIA_NOP, 0, 0);
            	break;
			...
        }
        msg_free_res(&msg);
    }

LABEL_RETURN:
    ;
}

先看下ijkmp_get_msg:

/* need to call msg_free_res for freeing the resouce obtained in msg */
int ijkmp_get_msg(IjkMediaPlayer *mp, AVMessage *msg, int block)
{
    assert(mp);
    while (1) {
        int continue_wait_next_msg = 0;
        /* 调用msg_queue_get获取一个消息 */
        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;
}

看下msg_queue_get是怎么拿到消息的:

/* return < 0 if aborted, 0 if no msg and > 0 if msg.  */
inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
{
    AVMessage *msg1;
    int ret;

    SDL_LockMutex(q->mutex);

    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);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

注释大致解释了消息的获取过程,回到上面的message_loop_n,ijkplayer发送的第一个消息是FFP_MSG_FLUSH,看下处理:

case FFP_MSG_FLUSH:
    MPTRACE("FFP_MSG_FLUSH:\n");
    post_event(env, weak_thiz, MEDIA_NOP, 0, 0);
    break;

跟进下post_event:

inline static void post_event(JNIEnv *env, jobject weak_this, int what, int arg1, int arg2)
{
    // MPTRACE("post_event(%p, %p, %d, %d, %d)", (void*)env, (void*) weak_this, what, arg1, arg2);
    J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL);
    // MPTRACE("post_event()=void");
}

J4AC_IjkMediaPlayer__postEventFromNative是一个宏定义,在ijkmedia\ijkj4a\j4a\class\tv\danmaku\ijk\media\player\IjkMediaPlayer.h中:

#define J4AC_IjkMediaPlayer__postEventFromNative J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__postEventFromNative

找到IjkMediaPlayer.c中:

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);
}

CallStaticVoidMethod是一个JNI回调到java层static方法的函数,第三个参数则是java层方法名,也就是class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative,找一下method_postEventFromNative的定义如下:

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

从name可以确认这个java层方法名。
找到java层中对应的函数:

postEventFromNative@android\ijkplayer\ijkplayer-java\src\main\java\tv\danmaku\ijk\media\player\IjkMediaPlayer.java:

    @CalledByNative
    private static void postEventFromNative(Object weakThiz, int what,
            int arg1, int arg2, Object obj) {
        if (weakThiz == null)
            return;

        @SuppressWarnings("rawtypes")
        IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
        if (mp == null) {
            return;
        }

        if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
            // this acquires the wakelock if needed, and sets the client side
            // state
            mp.start();
        }
        if (mp.mEventHandler != null) {
            Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
            mp.mEventHandler.sendMessage(m);
        }
    }

可以看到从jni层传上来的消息被重新投递进java层的消息机制中,找一下handleMessage的处理:

        public void handleMessage(Message msg) {
            IjkMediaPlayer player = mWeakPlayer.get();
            if (player == null || player.mNativeMediaPlayer == 0) {
                DebugLog.w(TAG,
                        "IjkMediaPlayer went away with unhandled events");
                return;
            }

			switch (msg.what) {
			case MEDIA_NOP: // interface test message - ignore
                break;
			...
			}
}

可以看到,java层对ijkplayer的第一个消息处理也是什么都没做。这里就基本分析 完了ijkplayer的消息机制。

三、总结:

ijkplayer的消息机制图解大致如下:

 如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_60259116/article/details/127266937

[ijkplayer]基于demo分析ijkplayer_俗科技的博客-爱代码爱编程

背景 博主主要是从事C语言开发,因此本文着重强调FFMPEG部分,关于JAVA应用和框架层只是一笔带过 1.目录结构 activities:包含了demo的所有activity;application:conten

ijkplayer直播播放器使用经验之谈——卡顿优化和秒开实现_cmsandly的博客-爱代码爱编程_ijkplayer秒开

    在我的博客移动平台播放器ijkplayer开源框架分析(以IOS源码为例),大致介绍了一下ijkplayer的基本函数调用顺序和主要线程作用,本博客想介绍一下在直播应用中,针对卡顿和秒开做的一些优化,本优化经验主要是用在Android系统上,ios上也可以借鉴,按本博客修改代码,网络带宽足够的情况下,音视频播放基本流畅不卡顿,首屏时间在500ms以

ijkplayer播放器剖析(二)从应用层分析至Jni层的流程分析-爱代码爱编程

一、引言: 在上一篇博客中,介绍了ijkplayer的编译及demo的使用,这篇博客将从应用层入手分析,看ijkplayer是如何调入到jni层的。 二、Java层代码分析: 选择码流进行播放时,将会跳转到VideoActivity,看一下onCreate: onCreate@ijkplayer\android\ijkplayer\ijkplayer-

ijkplayer播放器剖析(三)消息机制分析-爱代码爱编程

一、引言: 上一篇博客中分析了ijkplayer的整个流程,相信大家对其中的消息队列看的也是云里雾里的,所以这里单独会ijkplayer的消息机制做一个分析。 二、代码分析: 先看下消息机制是怎么创建起来的。创建的发起是native_setup函数: static void IjkMediaPlayer_native_setup(JNIEnv *env

ijkplayer播放器剖析(四)音频解码与音频输出机制分析-爱代码爱编程

一、引言: 在前面的博客中,我们对ijkplayer整个jni的流程及消息机制都详细的分析了一遍,分析流程机制有助于我们对整个架构有一个大致的了解,便于后续对音视频解码与输出渲染的分析,消息机制的分析有助于我们理解FFmpeg是如何处理输入输出buffer的。接下来,我们先梳理下read_thread这个线程,然后再分析音频是如何解码和输出的。 二、re

ijkplayer播放器剖析(六)视频同步与渲染机制分析-爱代码爱编程

一、引言: 在前面的博客中,将音频解码播放及视频解码都分析了,这篇博客将单独针对视频同步及渲染来分析,看下ijkplayer是如何做的。本博客分析的同步方式为以音频为主,视频去同步音频。 二、同步前提的确认: ijkplayer的同步前提跟其他的播放器略有不同,在ijkplayer中,会创建用于维护音频,视频的时钟及一个外部时钟,所有的同步操作都是基于这

ijkplayer 代码走读之 播放器启动过程详解-爱代码爱编程

上篇 ijkPlayer 代码走读之 Demoplayer 中,在 Android 代码层面是如何启动播放器, 我们已经说过,简单回顾一下,创建app应用时, protected void onCreate(Bundle savedInstanceState) { mVideoView.setVideoPath(); ///> 1

ijkplayer 源码分析(上)-爱代码爱编程

本文基于0.8.8版本的 ijkplayer ,对其源码进行剖析,涉及到不同平台下的封装接口或处理方式时,均以 Android 为例。 ijkplayer 是一款比较出众的开源 Android/IOS 跨平台播放器,基于 ffplay,API 易于集成,可定制编译控制体积。 ijkplayer 集成了三种播放器实现: An

ijkplayer 学习笔记-爱代码爱编程

ijk概述 mediacodec相关 OpenGL相关 filter相关 setOption配置相关 metadata相关 h264编码器特有的设置域 线程相关 消息机制 音频输出 声道切换 SDL_CreateCond 与 SDL_CreateThreadEx 如何暂停 笔记可能微乱,但大致清晰,可能会对他人有所帮助,故分享出来。

ffmpeg命令行录制一个具有非idr性质的i帧的视频_tusong86的博客-爱代码爱编程

之前在代码上写过几篇ffmpeg桌面录制的博客,用ffprobe查看里面的帧时,全部都是IDR这种I帧,没有普通的I帧,如下所示: <frame media_type="video" stream_index="0"

ffmpeg基础:视频流转图片_码农飞飞的博客-爱代码爱编程_视频逐帧提取图片

文章目录 1.定义图片RGB数据结构体2.定义分配和释放内存的C方法3.提取视频文件中的原始图像数据4.将YUV数据转换成图片RGB数据5.将RGB数据保存成对应的图片完整工程代码 在浏览视频的过程中,有时候

秋招android进阶面经,面试10余家经验分享,拿到offer真不难_android面经-爱代码爱编程

前言 我们都知道面试大厂主要就是考察程序员技术方向的专业技能,Java开发主要考察的就是Java方面的专业技能,而Android岗位的 专业技能 就是Android程序员面试的重要考察方向。 大厂的招聘条件是明牌的,但技