一、引言:

上一篇博客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;
}


需要注意的是最后一行的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;
}


注释大致解释了消息的获取过程,回到上面的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的消息机制图解大致如下:

iOS 开发 IJKPlayer 缓存设置 ijkplayer原理_音视频开发

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