ijkplayer源码分析 —— 事件消息设计

1. 播放器消息设计

播放器是一个典型多线程工程,核心线程如读数据、视频解码、音频解码、视频渲染、音频输出等,此外还需要提供给内部或上层应用层接受各种事件的消息模块。该消息模块需要支持任意线程生产消息数据,同时也要支持任何时间终止等。下面我们来看下 ijkplayer 中消息的一个基本流程图

2. 事件消息队列设计

2.1 结构体对象定义

ff_ffmsg_queue.h 消息、队列结构体定义和功能函数

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

// 消息结构体(节点)
typedef struct AVMessage {
    int what;	//消息类型
    int arg1;	//整型可选参数
    int arg2;	//整型可选参数
    void *obj;	//无类型可选动态内存参数
    void (*free_l)(void *obj); //用于释放obj指针的内存
} 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;		//alloc次数
} MessageQueue;

2.2 队列管理函数

// 消息初始化
inline static void msg_init_msg(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_put(MessageQueue *q, AVMessage *msg);

// 队列初始化,abort_request=1,初始化锁、信号量
inline static void msg_queue_init(MessageQueue *q);
// 清空消息队列,释放相关内存
inline static void msg_queue_flush(MessageQueue *q);
// 销毁队列,abort_request=1
inline static void msg_queue_destroy(MessageQueue *q);
// 终止队列,abort_request=1,发送信号量通知其他线程
inline static void msg_queue_abort(MessageQueue *q);
// 启动队列,abort_request=0,队列中添加一个what为0的消息,发送信号量
inline static void msg_queue_start(MessageQueue *q);
// 获取队头一条消息,block:无消息时是否阻塞
inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
// 移除队列中what类型的消息
inline static void msg_queue_remove(MessageQueue *q, int what);

2.3 核心函数分析 

// 队列中添加一条消息
inline static int msg_queue_put(MessageQueue *q, AVMessage *msg)
{
    int ret;

    SDL_LockMutex(q->mutex);	// 线程加锁
    ret = msg_queue_put_private(q, msg);
    SDL_UnlockMutex(q->mutex);	// 线程解锁

    return ret;
}

inline static int msg_queue_put_private(MessageQueue *q, AVMessage *msg)
{
    AVMessage *msg1;

    if (q->abort_request)
        return -1;	// 队列中止,直接返回

#ifdef FFP_MERGE
    msg1 = av_malloc(sizeof(AVMessage)); // 无重用下直接分配内存
#else
    msg1 = q->recycle_msg;
    if (msg1) {	// 存在重用对象:将重用对象的next赋值给队列重用对象,重用+1
        q->recycle_msg = msg1->next;
        q->recycle_count++;
    } else {	// 不存在重用对象:分配一个内存空间,alloc+1
        q->alloc_count++;
        msg1 = av_malloc(sizeof(AVMessage));
    }
#ifdef FFP_SHOW_MSG_RECYCLE
    int total_count = q->recycle_count + q->alloc_count;
    if (!(total_count % 10)) {  // alloc及重用消息数量日志输出
        av_log(NULL, AV_LOG_DEBUG, "msg-recycle \t%d + \t%d = \t%d\n", q->recycle_count, q->alloc_count, total_count);
    }
#endif
#endif
    if (!msg1)
        return -1;

    *msg1 = *msg;		// 拷贝msg(浅拷贝,obj内存不会拷贝)
    msg1->next = NULL;	// 下一个NULL,最为最后一个节点

    if (!q->last_msg)	// 无队首,放队首
        q->first_msg = msg1;
    else	// 当前队尾的下一个指向新消息(即最后)
        q->last_msg->next = msg1;
    q->last_msg = msg1;	// 改变对尾指针
    q->nb_messages++;	// 队列数量加1
    SDL_CondSignal(q->cond);	// 发送信号量
    return 0;
}

// 获取队头一条消息,block:无消息时是否阻塞
/* 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--;	// 队列数量减1
            *msg = *msg1;	// 浅拷贝
            msg1->obj = NULL;	// 原队首obj变量置空(内存不能释放,否则拷贝的msg对象参数受影响)
#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;
}

// 队列清空,释放内存或全部放入重用池
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;  // 将msg设置q->recycle_msg,q->recycle_msg设置重用池中下一个
        q->recycle_msg = msg;
#endif
    }
    q->last_msg = NULL;
    q->first_msg = NULL;
    q->nb_messages = 0;
    SDL_UnlockMutex(q->mutex);
}

// 队列销毁
inline static void msg_queue_destroy(MessageQueue *q)
{
    msg_queue_flush(q);

    SDL_LockMutex(q->mutex);
    while(q->recycle_msg) { // 清空重用池对象
        AVMessage *msg = q->recycle_msg;
        if (msg)
            q->recycle_msg = msg->next; // 下一个重用对象
        msg_free_res(msg); // 释放消息引用内存
        av_freep(&msg); // 释放重用池消息对象
    }
    SDL_UnlockMutex(q->mutex);

    SDL_DestroyMutex(q->mutex);
    SDL_DestroyCond(q->cond);
}

inline static void msg_free_res(AVMessage *msg)
{
    if (!msg || !msg->obj)
        return;
    assert(msg->free_l);
    msg->free_l(msg->obj);
    msg->obj = NULL;
}

3 应用层设计(iOS)

3.1 初始化

OC层 IJKFFMoviePlayerController

- (id)initWithContentURLString:(NSString *)aUrlString
                   withOptions:(IJKFFOptions *)options
{
    self = [super init];
    if (self) {
		......
        // init player
        _mediaPlayer = ijkmp_ios_create(media_player_msg_loop);  //播放器初始化,传入消息函数指针
        _msgPool = [[IJKFFMoviePlayerMessagePool alloc] init];	//消息重用池
        ......
    }
    return self;
}

OC-C层 ijkplayer_ios

IjkMediaPlayer *ijkmp_ios_create(int (*msg_loop)(void*))
{
	//创建IjkMediaPlayer对象(包括FFPlayer初始化),消息函数继续向下传递
    IjkMediaPlayer *mp = ijkmp_create(msg_loop);  
			......
    return mp;
}

c层 ijkplayer

IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
    IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
			......

    mp->ffplayer = ffp_create(); // 初始化FFPlayer,初始化消息队列
			......

    mp->msg_loop = msg_loop;  // 函数指针赋值给IjkMediaPlayer的msg_loop

    return mp;
}

c层 ff_ffplay

FFPlayer *ffp_create()
{
		......
    FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
		......

    msg_queue_init(&ffp->msg_queue); // 消息队列初始化
		......
    return ffp;
}

3.2 线程启动

ijkplayer

在ijkmp_prepare_async_l中我们看到在ff_msg_loop线程中调用了 ijkmp_msg_loop函数,这个函数中调用了msg_loop 函数,并传递了自身对象作为参数,由初始化那里可知,msg_loop指向IJKFFMoviePlayerController中的media_player_msg_loop函数。

// 播放器准备播放
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
   ......
    ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING); // 添加一条消息,msg_queue_start未调用过之前无效

    msg_queue_start(&mp->ffplayer->msg_queue);	// 消息队列启动

    // 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

    return 0;
}

static int ijkmp_msg_loop(void *arg)
{
    IjkMediaPlayer *mp = arg;
    int ret = mp->msg_loop(arg);	// 调用msg_loop函数
    return ret;
}

void ijkmp_change_state_l(IjkMediaPlayer *mp, int new_state)
{
    mp->mp_state = new_state;
    ffp_notify_msg1(mp->ffplayer, FFP_MSG_PLAYBACK_STATE_CHANGED);
}

3.3 消息回调

IJKFFMoviePlayerController

// 应用层播放器注册回调函数
int media_player_msg_loop(void* arg)
{
    @autoreleasepool {
        IjkMediaPlayer *mp = (IjkMediaPlayer*)arg;
        __weak IJKFFMoviePlayerController *ffpController = ffplayerRetain(ijkmp_set_weak_thiz(mp, NULL));
        while (ffpController) {  //ff_msg_loop线程中保持循环,循环条件:当前控制器存在
            @autoreleasepool {
                IJKFFMoviePlayerMessage *msg = [ffpController obtainMessage]; // OC对象便于消息分发,obtainMessage获取oc重用池中一个对象,不存在时创建
                if (!msg)
                    break;

                int retval = ijkmp_get_msg(mp, &msg->_msg, 1); //读取消息,无时阻塞等待
                if (retval < 0) // 消息队列终止
                    break;

                // block-get should never return 0
                assert(retval > 0);
                [ffpController performSelectorOnMainThread:@selector(postEvent:) withObject:msg waitUntilDone:NO];	// 分发至主线程
            }
        }

        // retained in prepare_async, before SDL_CreateThreadEx
        ijkmp_dec_ref_p(&mp);
        return 0;
    }
}

// 应用层播放器实现
- (void)postEvent: (IJKFFMoviePlayerMessage *)msg
{
    if (!msg)
        return;

    AVMessage *avmsg = &msg->_msg;	// 取出消息原始数据
    switch (avmsg->what) {				// OC基础播放器业务处理或外抛
        case FFP_MSG_FLUSH:
            break;
        case FFP_MSG_ERROR: {
            NSLog(@"FFP_MSG_ERROR: %d\n", avmsg->arg1);

            [self setScreenOn:NO];

            [[NSNotificationCenter defaultCenter]
             postNotificationName:IJKMPMoviePlayerPlaybackStateDidChangeNotification
             object:self];

            [[NSNotificationCenter defaultCenter]
                postNotificationName:IJKMPMoviePlayerPlaybackDidFinishNotification
                object:self
                userInfo:@{
                    IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey: @(IJKMPMovieFinishReasonPlaybackError),
                    @"error": @(avmsg->arg1)}];
            break;
        }
 				......
        default:
            // NSLog(@"unknown FFP_MSG_xxx(%d)\n", avmsg->what);
            break;
    }

    [_msgPool recycle:msg];	// 使用完毕,放入重用池
}

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

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/126662998