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


    1. 播放器消息设计

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

    2. 事件消息队列设计

    2.1 结构体对象定义

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

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

    1. // 消息结构体(节点)
    2. typedef struct AVMessage {
    3. int what; //消息类型
    4. int arg1; //整型可选参数
    5. int arg2; //整型可选参数
    6. void *obj; //无类型可选动态内存参数
    7. void (*free_l)(void *obj); //用于释放obj指针的内存
    8. } AVMessage;
    9. // 消息队列
    10. typedef struct MessageQueue {
    11. AVMessage *first_msg, *last_msg; //队首指针、对尾指针
    12. int nb_messages; //总消息数
    13. int abort_request; //中止队列标记
    14. SDL_mutex *mutex; //线程锁
    15. SDL_cond *cond; //条件变量(信号量)
    16. AVMessage *recycle_msg; //重用池首个对象指针,减少动态分配内存次数
    17. int recycle_count; //重用次数
    18. int alloc_count; //alloc次数
    19. } MessageQueue;

    2.2 队列管理函数

    1. // 消息初始化
    2. inline static void msg_init_msg(AVMessage *msg);
    3. // 便利生成消息并添加进队列函数
    4. inline static void msg_queue_put_simple1(MessageQueue *q, int what);
    5. inline static void msg_queue_put_simple2(MessageQueue *q, int what, int arg1);
    6. inline static void msg_queue_put_simple3(MessageQueue *q, int what, int arg1, int arg2);
    7. inline static void msg_queue_put_simple4(MessageQueue *q, int what, int arg1, int arg2, void *obj, int obj_len);
    8. // 队列中添加一条消息,发送信号量
    9. inline static int msg_queue_put(MessageQueue *q, AVMessage *msg);
    10. // 队列初始化,abort_request=1,初始化锁、信号量
    11. inline static void msg_queue_init(MessageQueue *q);
    12. // 清空消息队列,释放相关内存
    13. inline static void msg_queue_flush(MessageQueue *q);
    14. // 销毁队列,abort_request=1
    15. inline static void msg_queue_destroy(MessageQueue *q);
    16. // 终止队列,abort_request=1,发送信号量通知其他线程
    17. inline static void msg_queue_abort(MessageQueue *q);
    18. // 启动队列,abort_request=0,队列中添加一个what为0的消息,发送信号量
    19. inline static void msg_queue_start(MessageQueue *q);
    20. // 获取队头一条消息,block:无消息时是否阻塞
    21. inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
    22. // 移除队列中what类型的消息
    23. inline static void msg_queue_remove(MessageQueue *q, int what);

    2.3 核心函数分析 

    1. // 队列中添加一条消息
    2. inline static int msg_queue_put(MessageQueue *q, AVMessage *msg)
    3. {
    4. int ret;
    5. SDL_LockMutex(q->mutex); // 线程加锁
    6. ret = msg_queue_put_private(q, msg);
    7. SDL_UnlockMutex(q->mutex); // 线程解锁
    8. return ret;
    9. }
    10. inline static int msg_queue_put_private(MessageQueue *q, AVMessage *msg)
    11. {
    12. AVMessage *msg1;
    13. if (q->abort_request)
    14. return -1; // 队列中止,直接返回
    15. #ifdef FFP_MERGE
    16. msg1 = av_malloc(sizeof(AVMessage)); // 无重用下直接分配内存
    17. #else
    18. msg1 = q->recycle_msg;
    19. if (msg1) { // 存在重用对象:将重用对象的next赋值给队列重用对象,重用+1
    20. q->recycle_msg = msg1->next;
    21. q->recycle_count++;
    22. } else { // 不存在重用对象:分配一个内存空间,alloc+1
    23. q->alloc_count++;
    24. msg1 = av_malloc(sizeof(AVMessage));
    25. }
    26. #ifdef FFP_SHOW_MSG_RECYCLE
    27. int total_count = q->recycle_count + q->alloc_count;
    28. if (!(total_count % 10)) { // alloc及重用消息数量日志输出
    29. av_log(NULL, AV_LOG_DEBUG, "msg-recycle \t%d + \t%d = \t%d\n", q->recycle_count, q->alloc_count, total_count);
    30. }
    31. #endif
    32. #endif
    33. if (!msg1)
    34. return -1;
    35. *msg1 = *msg; // 拷贝msg(浅拷贝,obj内存不会拷贝)
    36. msg1->next = NULL; // 下一个NULL,最为最后一个节点
    37. if (!q->last_msg) // 无队首,放队首
    38. q->first_msg = msg1;
    39. else // 当前队尾的下一个指向新消息(即最后)
    40. q->last_msg->next = msg1;
    41. q->last_msg = msg1; // 改变对尾指针
    42. q->nb_messages++; // 队列数量加1
    43. SDL_CondSignal(q->cond); // 发送信号量
    44. return 0;
    45. }
    46. // 获取队头一条消息,block:无消息时是否阻塞
    47. /* return < 0 if aborted, 0 if no msg and > 0 if msg. */
    48. inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
    49. {
    50. AVMessage *msg1;
    51. int ret;
    52. SDL_LockMutex(q->mutex);
    53. for (;;) { // 无限循环,注意出循环条件
    54. if (q->abort_request) {
    55. ret = -1; // 队列中止,返回
    56. break;
    57. }
    58. msg1 = q->first_msg;
    59. if (msg1) { // 取队首存在
    60. q->first_msg = msg1->next; // 第二条设置为队首
    61. if (!q->first_msg) // 队首不存在,对尾也清空
    62. q->last_msg = NULL;
    63. q->nb_messages--; // 队列数量减1
    64. *msg = *msg1; // 浅拷贝
    65. msg1->obj = NULL; // 原队首obj变量置空(内存不能释放,否则拷贝的msg对象参数受影响)
    66. #ifdef FFP_MERGE
    67. av_free(msg1); // 释放临时变量内存
    68. #else
    69. msg1->next = q->recycle_msg; // 将已取出的这个消息标记为重用对象,之前的设置为此对象的下一个
    70. q->recycle_msg = msg1;
    71. #endif
    72. ret = 1;
    73. break;
    74. } else if (!block) { // 队首不存在,不等待返回
    75. ret = 0;
    76. break;
    77. } else {
    78. // 该函数会先释放锁,等待信号量条件后,重新加锁
    79. SDL_CondWait(q->cond, q->mutex); // 队首不存在,等待信号量后继续执行(循环)
    80. }
    81. }
    82. SDL_UnlockMutex(q->mutex);
    83. return ret;
    84. }
    85. // 队列清空,释放内存或全部放入重用池
    86. inline static void msg_queue_flush(MessageQueue *q)
    87. {
    88. AVMessage *msg, *msg1;
    89. SDL_LockMutex(q->mutex);
    90. for (msg = q->first_msg; msg != NULL; msg = msg1) {
    91. msg1 = msg->next; //取出下一条消息
    92. #ifdef FFP_MERGE
    93. av_freep(&msg); //直接释放消息内存
    94. #else
    95. msg->next = q->recycle_msg; // 将msg设置q->recycle_msg,q->recycle_msg设置重用池中下一个
    96. q->recycle_msg = msg;
    97. #endif
    98. }
    99. q->last_msg = NULL;
    100. q->first_msg = NULL;
    101. q->nb_messages = 0;
    102. SDL_UnlockMutex(q->mutex);
    103. }
    104. // 队列销毁
    105. inline static void msg_queue_destroy(MessageQueue *q)
    106. {
    107. msg_queue_flush(q);
    108. SDL_LockMutex(q->mutex);
    109. while(q->recycle_msg) { // 清空重用池对象
    110. AVMessage *msg = q->recycle_msg;
    111. if (msg)
    112. q->recycle_msg = msg->next; // 下一个重用对象
    113. msg_free_res(msg); // 释放消息引用内存
    114. av_freep(&msg); // 释放重用池消息对象
    115. }
    116. SDL_UnlockMutex(q->mutex);
    117. SDL_DestroyMutex(q->mutex);
    118. SDL_DestroyCond(q->cond);
    119. }
    120. inline static void msg_free_res(AVMessage *msg)
    121. {
    122. if (!msg || !msg->obj)
    123. return;
    124. assert(msg->free_l);
    125. msg->free_l(msg->obj);
    126. msg->obj = NULL;
    127. }

    3 应用层设计(iOS)

    3.1 初始化

    OC层 IJKFFMoviePlayerController

    1. - (id)initWithContentURLString:(NSString *)aUrlString
    2. withOptions:(IJKFFOptions *)options
    3. {
    4. self = [super init];
    5. if (self) {
    6. ......
    7. // init player
    8. _mediaPlayer = ijkmp_ios_create(media_player_msg_loop); //播放器初始化,传入消息函数指针
    9. _msgPool = [[IJKFFMoviePlayerMessagePool alloc] init]; //消息重用池
    10. ......
    11. }
    12. return self;
    13. }

    OC-C层 ijkplayer_ios

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

    c层 ijkplayer

    1. IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
    2. {
    3. IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
    4. ......
    5. mp->ffplayer = ffp_create(); // 初始化FFPlayer,初始化消息队列
    6. ......
    7. mp->msg_loop = msg_loop; // 函数指针赋值给IjkMediaPlayer的msg_loop
    8. return mp;
    9. }

    c层 ff_ffplay

    1. FFPlayer *ffp_create()
    2. {
    3. ......
    4. FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
    5. ......
    6. msg_queue_init(&ffp->msg_queue); // 消息队列初始化
    7. ......
    8. return ffp;
    9. }

    3.2 线程启动

    ijkplayer

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

    1. // 播放器准备播放
    2. static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
    3. {
    4. ......
    5. ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING); // 添加一条消息,msg_queue_start未调用过之前无效
    6. msg_queue_start(&mp->ffplayer->msg_queue); // 消息队列启动
    7. // released in msg_loop
    8. ijkmp_inc_ref(mp);
    9. mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
    10. // msg_thread is detached inside msg_loop
    11. return 0;
    12. }
    13. static int ijkmp_msg_loop(void *arg)
    14. {
    15. IjkMediaPlayer *mp = arg;
    16. int ret = mp->msg_loop(arg); // 调用msg_loop函数
    17. return ret;
    18. }
    19. void ijkmp_change_state_l(IjkMediaPlayer *mp, int new_state)
    20. {
    21. mp->mp_state = new_state;
    22. ffp_notify_msg1(mp->ffplayer, FFP_MSG_PLAYBACK_STATE_CHANGED);
    23. }

    3.3 消息回调

    IJKFFMoviePlayerController

    1. // 应用层播放器注册回调函数
    2. int media_player_msg_loop(void* arg)
    3. {
    4. @autoreleasepool {
    5. IjkMediaPlayer *mp = (IjkMediaPlayer*)arg;
    6. __weak IJKFFMoviePlayerController *ffpController = ffplayerRetain(ijkmp_set_weak_thiz(mp, NULL));
    7. while (ffpController) { //ff_msg_loop线程中保持循环,循环条件:当前控制器存在
    8. @autoreleasepool {
    9. IJKFFMoviePlayerMessage *msg = [ffpController obtainMessage]; // OC对象便于消息分发,obtainMessage获取oc重用池中一个对象,不存在时创建
    10. if (!msg)
    11. break;
    12. int retval = ijkmp_get_msg(mp, &msg->_msg, 1); //读取消息,无时阻塞等待
    13. if (retval < 0) // 消息队列终止
    14. break;
    15. // block-get should never return 0
    16. assert(retval > 0);
    17. [ffpController performSelectorOnMainThread:@selector(postEvent:) withObject:msg waitUntilDone:NO]; // 分发至主线程
    18. }
    19. }
    20. // retained in prepare_async, before SDL_CreateThreadEx
    21. ijkmp_dec_ref_p(&mp);
    22. return 0;
    23. }
    24. }
    25. // 应用层播放器实现
    26. - (void)postEvent: (IJKFFMoviePlayerMessage *)msg
    27. {
    28. if (!msg)
    29. return;
    30. AVMessage *avmsg = &msg->_msg; // 取出消息原始数据
    31. switch (avmsg->what) { // OC基础播放器业务处理或外抛
    32. case FFP_MSG_FLUSH:
    33. break;
    34. case FFP_MSG_ERROR: {
    35. NSLog(@"FFP_MSG_ERROR: %d\n", avmsg->arg1);
    36. [self setScreenOn:NO];
    37. [[NSNotificationCenter defaultCenter]
    38. postNotificationName:IJKMPMoviePlayerPlaybackStateDidChangeNotification
    39. object:self];
    40. [[NSNotificationCenter defaultCenter]
    41. postNotificationName:IJKMPMoviePlayerPlaybackDidFinishNotification
    42. object:self
    43. userInfo:@{
    44. IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey: @(IJKMPMovieFinishReasonPlaybackError),
    45. @"error": @(avmsg->arg1)}];
    46. break;
    47. }
    48. ......
    49. default:
    50. // NSLog(@"unknown FFP_MSG_xxx(%d)\n", avmsg->what);
    51. break;
    52. }
    53. [_msgPool recycle:msg]; // 使用完毕,放入重用池
    54. }

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

  • 相关阅读:
    JVM篇---第八篇
    Flex 布局,学会瞬间让你不再掉头发,让布局变的更简单
    jQuery中遍历元素,创建元素,添加元素,删除元素汇总
    yolov5数据集
    redisinsight--基础--2.1--安装--window
    设计模式-03-建造者模式
    Linux用户管理
    (OS 5)拒绝访问。 : AH10014: Failed to open the ‘Apache2.4‘ service
    Docker镜像与容器的亲密对话:深度剖析两者内在关联与实战演绎
    STL中set的基本概念与使用
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/126662998