FFmpeg: 自实现ijkplayer播放器--04消息队列设计

本文详细介绍了基于FFmpeg的ijkplayer播放器中消息队列的设计,包括播放器状态转换图,不同状态对应的消息,以及消息对象的创建、插入、获取、初始化和销毁等关键API。消息队列采用链表存储,通过recycle_msg实现消息回收,优化内存操作。此外,还涵盖了消息队列的启动、终止和管理等核心功能。


消息队列,用于发送,设置播放器的状态,实现ui界面,jikpalyer以及ffplay之间的通信

播放器状态转换图

实线箭头连接的状态变化通过 API 调⽤完成
虚线箭头连接的状态变化是通过 播放器内部执⾏完特定任务或者发⽣错误 ⽽⾃动发⽣的状态
变化
请添加图片描述

播放器状态对应的消息:
  • idle: MP_STATE_IDLE 闲置状态,刚完成构造的 FijkPlaye
  • initialized: MP_STATE_INITIALIZED 初始化完成状态,和 idle 状态相⽐,仅是多了输⼊媒体
    数据源的信息
  • async_preparing:MP_STATE_ASYNC_PREPARING 异步准备状态,进行打开媒体⽂件,打开解码器以及新建解码线程,新建数据 read 线程,打开⾳频输出设备,新建视频输出线程等
  • prepared:MP_STATE_PREPARED,完成指定任务后⾃动转化为此状态。此状态下已经缓冲并解码了⼀部分⾳视频数据,可以随时进⾏播放
  • started:MP_STATE_STARTED 媒体(视频、⾳频)正在播放中
  • paused:MP_STATE_PAUSED 媒体(视频、⾳频)播放暂停
  • completed:MP_STATE_COMPLETED 媒体(视频、⾳频)播放完成。 可重新从头开始播
    放。
  • stop: MP_STATE_STOPPED 播放器各种线程占⽤资源都已经释放。 ⾳频设备关闭
  • error: MP_STATE_ERROR 播放器出现错误

消息对象

typedef struct AVMessage {
    int what;           // 消息类型
    int arg1;           // 参数1
    int arg2;           // 参数2
    void *obj;          // 如果arg1 arg2还不够存储消息则使⽤该参数
    void (*free_l)(void *obj);  // 释放obj指向的函数
    struct AVMessage *next; // 下⼀个消息
} 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;    // 分配的次数
} MessageQueue;

recycle_msg:
用于回收消息,消息使用链表进行存储,当消息取出时,通过recycle_msg链接该消息,重新用做新消息使用
作用:节省了对新消息申请空间,以及对取出的消息释放内存操作

消息队列api

插入消息
// 消息队列内部重新去构建 AVMessage(重新申请AVMessage,或者来自于recycle_msg)
// 新的消息插入到尾部
int msg_queue_put_private(MessageQueue *q, AVMessage *msg)
{
    AVMessage *msg1;

    if(q->abort_request)
        return -1;

    //1. 消息体使用回收的资源还是重新malloc
    msg1 = q->recycle_msg;
    if(msg1) {
        q->recycle_msg = msg1->next;
        q->recycle_count++;
    } else {
        q->alloc_count++;
        msg1 = (AVMessage *)av_malloc(sizeof(AVMessage));
    }

    *msg1 = *msg;
    msg1->next = NULL;

    if(!q->first_msg) {
        q->first_msg = msg1;
    } else {
        q->last_msg->next = msg1;
    }

    q->last_msg = msg1;
    q->nb_messages++;
    SDL_CondSignal(q->cond);
    return 0;
}
获取消息
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;
            msg1->next = q->recycle_msg;
            q->recycle_msg = msg1;
            ret =1;
            break;      // 记得这里有个break的
        } else if (!block) {
            ret = 0;
            break;
        } else {
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}
初始化消息
// 消息队列初始化
void msg_queue_init(MessageQueue *q)
{
    memset(q, 0, sizeof(MessageQueue));
    q->mutex = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
    q->abort_request = 1;
}
插入消息加锁
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;
}

初始化消息
void msg_init_msg(AVMessage *msg)
{
    memset(msg, 0, sizeof(AVMessage));
}
设置消息参数
void msg_queue_put_simple1(MessageQueue *q, int what)
{
    AVMessage msg;
    msg_init_msg(&msg);
    msg.what = what;
    msg_queue_put(q, &msg);
}

// 释放msg的obj资源
void msg_obj_free_l(void *obj)
{
    av_free(obj);
}
//插入消息,带消息类型,带2个参数,带obj
void msg_queue_put_simple4(MessageQueue *q, int what, int arg1, int arg2, void *obj, int obj_len)
{
    AVMessage msg;
    msg_init_msg(&msg);
    msg.what = what;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    msg.obj = av_malloc(obj_len);
    memcpy(msg.obj, obj, obj_len);
    msg.free_l = msg_obj_free_l;
    msg_queue_put(q, &msg);
}
消息队列初始化
void msg_queue_init(MessageQueue *q)
{
    memset(q, 0, sizeof(MessageQueue));
    q->mutex = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
    q->abort_request = 1;
}
清空消息
 // 消息队列flush,清空所有的消息
void msg_queue_flush(MessageQueue *q)
{
    AVMessage *msg, *msg1;

    SDL_LockMutex(q->mutex);
    for (msg = q->first_msg; msg != NULL; msg = msg1) { // 这个时候的obj没有清空?那会导致泄漏,实际是把消息对象暂存到了recycle_msg
        msg1 = msg->next;
        msg->next = q->recycle_msg;
        q->recycle_msg = msg;
    }
    q->last_msg = NULL;
    q->first_msg = NULL;
    q->nb_messages = 0;
    SDL_UnlockMutex(q->mutex);

}
销毁消息
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);
}
启动消息队列
void msg_queue_start(MessageQueue *q)
{
    SDL_LockMutex(q->mutex);
    q->abort_request = 0;
    // 插入一个消息
    AVMessage msg;
    msg_init_msg(&msg);
    msg.what = FFP_MSG_FLUSH;
    msg_queue_put_private(q, &msg);
    SDL_UnlockMutex(q->mutex);
}
终止消息队列
void msg_queue_abort(MessageQueue *q)
{
    SDL_LockMutex(q->mutex);
    q->abort_request = 1;
    SDL_CondSignal(q->cond);
    SDL_UnlockMutex(q->mutex);
}
删除消息
// 消息删除 把队列里同一消息类型的消息全删除掉
void msg_queue_remove(MessageQueue *q, int what)
{
    AVMessage **p_msg, *msg, *last_msg;
        SDL_LockMutex(q->mutex);

        last_msg = q->first_msg;

        if (!q->abort_request && q->first_msg) {
            p_msg = &q->first_msg;
            while (*p_msg) {
                msg = *p_msg;
                if (msg->what == what) {        // 同类型的消息全部删除
                    *p_msg = msg->next;
                    msg_free_res(msg);
                    msg->next = q->recycle_msg;     // 消息体回收
                    q->recycle_msg = msg;
                    q->nb_messages--;
                } else {
                    last_msg = msg;
                    p_msg = &msg->next;
                }
            }

            if (q->first_msg) {
                q->last_msg = last_msg;
            } else {
                q->last_msg = NULL;
            }
        }

        SDL_UnlockMutex(q->mutex);
}
<think>好的,我现在需要分析用户关于IJKPlayer源码中VideoToolbox硬解的问题。用户之前已经询问过相关的代码片段,现在希望得到源码分析的详细介绍。首先,我得回想一下IJKPlayer的整体结构,它基于FFmpeg,所以硬解部分应该和FFmpeg的硬件加速机制相关。 用户之前给出的代码涉及设置硬件加速选项,遍历编解码器的硬件配置,初始化VideoToolbox。我需要确定这些步骤在IJKPlayer中的具体实现位置,可能是在解码器初始化的部分。比如,在FFmpeg的avcodec_open2之前,IJKPlayer可能调用了相关的硬件加速设置。 接下来,用户可能需要了解VideoToolbox在IJKPlayer中的集成方式。可能需要解释如何通过FFmpeg的hwcontext机制来创建硬件设备上下文,以及如何在解码时使用这个上下文。此外,iOS/macOS平台上的VideoToolbox支持哪些编码格式,比如H.264和HEVC,这也是需要注意的点。 还要考虑用户可能的疑问点,比如硬解和软解的区别,为什么使用VideoToolbox能提升性能,或者遇到不支持的编码格式时如何处理。同时,错误处理机制,比如当初始化失败时如何回退到软解,这些在源码中是否有体现。 另外,用户可能对IJKPlayer中特定的封装函数感兴趣,比如hw_decoder_init的具体实现,里面是否涉及创建硬件设备、配置解码器参数等步骤。需要回忆相关代码结构,或者参考FFmpeg的官方文档来确认这些函数的用法。 还需要注意代码中的日志输出,比如ALOGI和ALOGE,这些日志在调试时的重要性,帮助开发者确认硬解是否成功启用,或者在失败时快速定位问题。 最后,可能需要补充一些性能优化的建议,比如多线程解码、内存管理,或者不同iOS版本之间的兼容性问题。这些都是在实际使用VideoToolbox硬解时需要考虑的因素。 总结下来,回答的结构应该包括:IJKPlayerFFmpeg的关系,VideoToolbox硬解的初始化流程,关键函数的作用,支持格式与平台要求,错误处理机制,以及性能优化点。需要确保用中文清晰解释,并保持技术细节的准确性。</think>### IJKPlayer源码分析 - VideoToolbox硬解实现 IJKPlayer是基于FFmpeg的跨平台播放器,其VideoToolbox硬件解码实现主要通过与FFmpeg硬件加速框架的深度集成完成。以下是关键实现分析: #### 一、架构设计原理 $$ \text{IJKPlayer架构} = \text{FFmpeg核心} + \text{平台硬件抽象层} $$ VideoToolbox硬解通过FFmpeg的`hwcontext`机制实现,核心类交互关系: ```mermaid graph TD A[IJKFF_Pipeline] --> B[VideoToolbox初始化] B --> C[av_hwdevice_ctx_create] C --> D[AVHWDeviceContext] D --> E[AVCodecContext] ``` #### 二、核心实现流程 1. **硬件加速使能检测** ```c // 检查编译时支持 #if CONFIG_VIDEOTOOLBOX // 检查运行时可用性 if (av_hwdevice_find_type_by_name("videotoolbox") != AV_HWDEVICE_TYPE_NONE) { enable_hwaccel = 1; } ``` 2. **硬件上下文创建** ```objc AVBufferRef *hw_device_ctx = NULL; av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VIDEOTOOLBOX, NULL, NULL, 0); ``` 3. **解码器配置(关键代码段)** ```c // 设置硬件像素格式 avctx->get_format = get_hw_format; static enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { while (*pix_fmts != AV_PIX_FMT_NONE) { if (*pix_fmts == AV_PIX_FMT_VIDEOTOOLBOX) return AV_PIX_FMT_VIDEOTOOLBOX; pix_fmts++; } return AV_PIX_FMT_NONE; } ``` #### 三、关键技术点 1. **内存管理机制** - 使用`CVPixelBufferRef`直接访问GPU内存 - 通过`AVBufferRef`实现引用计数管理: $$ \text{显存生命周期} = \max(\text{解码器引用计数}, \text{渲染器持有计数}) $$ 2. **格式转换流水线** ```c Videotoolbox硬解输出 --> 转换YUV420P --> OpenGL ES渲染 ``` 转换过程通过`vtDecompressionSessionDecodeFrame`回调实现 3. **多线程同步** - 独立的硬件解码线程:`videotoolbox_async_thread` - 使用pthread条件变量实现帧缓存同步 ```c pthread_mutex_lock(&q->mutex); while (q->count == 0) pthread_cond_wait(&q->cond, &q->mutex); frame = q->frames[q->rindex]; pthread_mutex_unlock(&q->mutex); ``` #### 四、性能优化策略 1. **零拷贝渲染** ```objc CVOpenGLESTextureCacheCreateTextureFromImage( kCFAllocatorDefault, _textureCache, pixelBuffer, NULL, GL_TEXTURE_2D, GL_RGBA, width, height, GL_BGRA, GL_UNSIGNED_BYTE, 0, &_texture); ``` 直接绑定CVPixelBuffer到OpenGL纹理 2. **动态降级策略** | 触发条件 | 处理方式 | |---|---| | 解码超时 > 200ms | 切换软解 | | 显存不足 | 清空缓存队列 | | 格式不支持 | 触发格式转换器 | 3. **参数调优建议 ```objc [options setPlayerOptionIntValue:0 forKey:@"videotoolbox-max-frame-width"]; // 4K限制 [options setPlayerOptionIntValue:1 forKey:@"videotoolbox-async"]; // 异步模式 ``` #### 五、平台适配问题 1. **iOS版本差异** - ≥ iOS8: 完整HEVC支持 - iOS11+: 改进的色彩空间处理 2. **Mac特殊处理** ```c #if TARGET_OS_MAC av_dict_set(&opts, "output_cvpixelbufferpool", "true", 0); #endif ``` 该实现通过深度整合FFmpeg硬件加速框架与苹果原生VideoToolbox API,在保证跨平台兼容性的同时实现了高性能视频解码。实际开发中需特别注意显存管理和线程同步问题。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八月的雨季997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值