1. 播放器消息设计
播放器是一个典型多线程工程,核心线程如读数据、视频解码、音频解码、视频渲染、音频输出等,此外还需要提供给内部或上层应用层接受各种事件的消息模块。该消息模块需要支持任意线程生产消息数据,同时也要支持任何时间终止等。下面我们来看下 ijkplayer 中消息的一个基本流程图
2. 事件消息队列设计
2.1 结构体对象定义
ff_ffmsg_queue.h 消息、队列结构体定义和功能函数
// 消息结构体(节点)
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
// 播放器准备播放
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);
}
在ijkmp_prepare_async_l中我们看到在ff_msg_loop线程中调用了 ijkmp_msg_loop函数,这个函数中调用了msg_loop 函数,并传递了自身对象作为参数,由初始化那里可知,msg_loop指向IJKFFMoviePlayerController中的media_player_msg_loop函数。
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]; // 使用完毕,放入重用池
}