一、引言:
上一篇博客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的消息机制图解大致如下:
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!