博客
关于我
ijkplayer播放器剖析(三)消息机制分析
阅读量:379 次
发布时间:2019-03-05

本文共 9462 字,大约阅读时间需要 31 分钟。

一、引言:

上一篇博客中分析了ijkplayer的整个流程,相信大家对其中的消息队列看的也是云里雾里的,所以这里单独会ijkplayer的消息机制做一个分析。

二、代码分析:

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

static voidIjkMediaPlayer_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的消息机制图解大致如下:
在这里插入图片描述

转载地址:http://idwwz.baihongyu.com/

你可能感兴趣的文章
Mysql_Postgresql中_geometry数据操作_st_astext_GeomFromEWKT函数_在java中转换geometry的16进制数据---PostgreSQL工作笔记007
查看>>
mysql_real_connect 参数注意
查看>>
mysql_secure_installation初始化数据库报Access denied
查看>>
MySQL_西安11月销售昨日未上架的产品_20161212
查看>>
Mysql——深入浅出InnoDB底层原理
查看>>
MySQL“被动”性能优化汇总
查看>>
MySQL、HBase 和 Elasticsearch:特点与区别详解
查看>>
MySQL、Redis高频面试题汇总
查看>>
MYSQL、SQL Server、Oracle数据库排序空值null问题及其解决办法
查看>>
mysql一个字段为空时使用另一个字段排序
查看>>
MySQL一个表A中多个字段关联了表B的ID,如何关联查询?
查看>>
MYSQL一直显示正在启动
查看>>
MySQL一站到底!华为首发MySQL进阶宝典,基础+优化+源码+架构+实战五飞
查看>>
MySQL万字总结!超详细!
查看>>
Mysql下载以及安装(新手入门,超详细)
查看>>
MySQL不会性能调优?看看这份清华架构师编写的MySQL性能优化手册吧
查看>>
MySQL不同字符集及排序规则详解:业务场景下的最佳选
查看>>
Mysql不同官方版本对比
查看>>
MySQL与Informix数据库中的同义表创建:深入解析与比较
查看>>
mysql与mem_细说 MySQL 之 MEM_ROOT
查看>>