MPP_EncoderCore - SweerItTer/utilsCore GitHub Wiki

MppEncoderCore API 文档

概述

MppEncoderCore 是 utilsCore MPP 模块的核心类,提供基于 Slot 的视频编码核心功能,使用 DMA-BUF 实现零拷贝编码。

职责

  • Slot 管理和生命周期控制
  • DMA-BUF 缓冲区池管理
  • MPP 编码任务调度
  • 编码结果包封装
  • 多线程异步编码

适用场景

  • 视频录制
  • 实时流编码
  • 图像压缩
  • 多路并发编码

依赖关系

  • 依赖: Rockchip MPP 库, DmaBuffer, MppEncoderContext
  • 被依赖: EncoderContext, StreamWriter 等上层模块

类分析

MppEncoderCore 类

职责与用途

MppEncoderCore 是编码核心的封装类,提供:

  • 15 个 Slot 的缓冲区池(RK356X 实测最优值)
  • 异步编码线程
  • Slot 状态机管理
  • DMA-BUF 零拷贝支持
  • 外部 DMA-BUF 导入支持

设计模式

  • 生产者-消费者模式: Slot 队列管理
  • 状态机模式: Slot 状态转换
  • RAII: SlotGuard 自动释放 Slot
  • 工厂模式: 通过 MppEncoderContext 创建

常量定义

static constexpr size_t SLOT_COUNT = 15;  // Slot 数量,RK356X 实测值

内部类定义

EncodedPacket 类

编码结果包封装,负责管理 MppPacket 生命周期。

方法定义

class EncodedPacket {
public:
    explicit EncodedPacket(MppPacket pkt, size_t len, bool keyframe);
    ~EncodedPacket();
    MppPacket& rawPacket();                    // 获取原始 MppPacket
    void* data() const;                        // 获取数据指针
    size_t length() const;                     // 获取数据长度
    bool isKeyframe() const;                   // 是否关键帧
    uint64_t getPts() const;                   // 获取时间戳
    void setPts(const std::chrono::steady_clock::time_point& tp);
    void setDataLen(size_t len);               // 设置数据长度
    void setKeyframe(bool keyframe);           // 设置关键帧标志
};

所有权归属:

  • EncodedPacket 持有 MppPacket 的所有权
  • 析构时自动释放 MppPacket

使用例程:

// 从 meta 获取编码包
auto packet = meta.packet;
if (packet && packet->isKeyframe()) {
    printf("Keyframe: %zu bytes\n", packet->length());
    write_packet(packet->data(), packet->length());
}

EncodedMeta 结构体

编码元信息,包含编码结果包和 Slot 信息。

结构体定义

struct EncodedMeta {
    int core_id = -1;              // EncoderCore 实例 id
    int slot_id = -1;              // Slot 索引
    MppEncoderCore* core = nullptr; // 所属 EncoderCore 指针
    EncodedPacketPtr packet = nullptr; // 编码结果包
};

字段说明:

  • core_id: 所属 EncoderCore 的 ID
  • slot_id: 使用的 Slot 索引
  • core: 指向 EncoderCore 的指针
  • packet: 编码后的数据包

SlotState 枚举

Slot 状态机,管理 Slot 的生命周期。

枚举定义

enum class SlotState : uint8_t {
    Writable = 0,  // 可写状态,空闲
    Writing,       // 写入中,已获取但未提交
    Filled,        // 已填充,等待编码
    Encoding,      // 正在编码
    Encoded,       // 编码完成,等待获取结果
    invalid        // 无效状态
};

状态转换:

Writable → acquireWritableSlot() → Writing
Writing → submitFilledSlot() → Filled
Filled → workerThread() → Encoding
Encoding → 编码完成 → Encoded
Encoded → releaseSlot() → Writable

Slot 结构体

内部 Slot 数据结构,持有 DMA-BUF 和编码状态。

结构体定义

struct Slot {
    DmaBufferPtr dmabuf;                      // DMA-BUF 引用,保持生命周期
    std::shared_ptr<MppBufferGuard> enc_buf;  // 缓存导入后的 Buffer
    DmaBufferPtr external_dmabuf;             // 外部 DMA-BUF
    std::atomic_bool using_external{false};   // 使用外部 DMA-BUF 标志
    std::shared_ptr<void> lifetime_holder;    // 保留外部资源生命周期
    EncodedPacketPtr packet = nullptr;        // 编码结果
    std::atomic<SlotState> state{SlotState::invalid}; // 当前状态
};

字段说明:

  • dmabuf: 内部分配的 DMA-BUF
  • enc_buf: 导入 MPP 后的 Buffer
  • external_dmabuf: 外部导入的 DMA-BUF
  • using_external: 是否使用外部 DMA-BUF
  • lifetime_holder: 外部资源生命周期持有者
  • packet: 编码结果包
  • state: Slot 状态(原子操作)

公共 API 方法

构造函数

explicit MppEncoderCore(const MppEncoderContext::Config& cfg, int core_id);
~MppEncoderCore();

参数说明:

  • cfg (输入): 编码配置
  • core_id (输入): 核心编号

返回值: 无

所有权归属:

  • MppEncoderCore 拥有所有 Slot 和 MppEncoderContext 的所有权

注意事项:

  1. 构造时会初始化编码上下文和 Slot 池
  2. 启动编码线程
  3. core_id 用于标识不同的编码器实例

使用例程:

MppEncoderContext::Config cfg;
cfg.width = 1920;
cfg.height = 1080;
cfg.format = MPP_FMT_YUV420SP;
cfg.type = MPP_VIDEO_CodingAVC;

auto encoder = std::make_shared<MppEncoderCore>(cfg, 0);

resetConfig() - 重置配置

void resetConfig(const MppEncoderContext::Config& cfg);

参数说明:

  • cfg (输入): 新的编码配置

返回值: 无

所有权归属:

  • 无所有权转移

注意事项:

  1. 线程安全操作
  2. 会重置编码上下文
  3. 不影响当前正在编码的 Slot

endOfthisEncode() - 结束编码

void endOfthisEncode();

参数说明: 无

返回值: 无

所有权归属:

  • 无所有权转移

注意事项:

  1. 设置结束标志
  2. 编码线程会在处理完所有待编码 Slot 后退出
  3. 必须在销毁 MppEncoderCore 前调用

acquireWritableSlot() - 获取可写 Slot

std::pair<DmaBufferPtr, int> acquireWritableSlot();

参数说明: 无

返回值:

  • 成功: 返回 {DMA-BUF 指针, Slot 索引}
  • 失败: 返回 {nullptr, -1}

所有权归属:

  • 返回的 DMA-BUF 由调用者持有(但所有权仍在 Slot 中)
  • Slot 状态自动转换为 Writing

注意事项:

  1. 如果没有可用的 Slot,返回 {nullptr, -1}
  2. 获取后 Slot 状态变为 Writing
  3. 必须调用 submitFilledSlot() 提交或 releaseSlot() 释放
  4. 返回的 DMA-BUF 可以直接写入数据

使用例程:

auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
if (dmabuf && slot_id >= 0) {
    // 填充数据到 DMA-BUF
    void* data = dmabuf->map();
    memcpy(data, frame_data, frame_size);
    dmabuf->unmap();
    
    // 提交编码
    auto meta = encoder->submitFilledSlot(slot_id);
} else {
    printf("No available slot\n");
}

submitFilledSlot() - 提交填充完成的 Slot

EncodedMeta submitFilledSlot(int slot_id);

参数说明:

  • slot_id (输入): 填充完成的 Slot 索引

返回值: EncodedMeta(用于后续获取编码结果)

所有权归属:

  • 返回的 EncodedMeta 包含 Slot 索引
  • Slot 状态自动转换为 Filled

注意事项:

  1. Slot 状态从 Writing 转换为 Filled
  2. Slot 会被加入到待编码队列
  3. 返回的 meta 必须保存,用于获取编码结果
  4. 必须使用 tryGetEncodedPacket() 获取编码结果

使用例程:

auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
// ... 填充数据 ...
auto meta = encoder->submitFilledSlot(slot_id);

// 等待编码完成
EncodedPacketPtr packet;
while (!encoder->tryGetEncodedPacket(meta)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

// 使用编码结果
if (meta.packet) {
    write_packet(meta.packet->data(), meta.packet->length());
}

submitFilledSlotWithExternal() - 提交外部 DMA-BUF

EncodedMeta submitFilledSlotWithExternal(int slot_id, DmaBufferPtr external_dmabuf, std::shared_ptr<void> lifetime_holder);

参数说明:

  • slot_id (输入): Slot 索引
  • external_dmabuf (输入): 外部 DMA-BUF
  • lifetime_holder (输入): 外部资源生命周期持有者

返回值: EncodedMeta

所有权归属:

  • external_dmabuf 的所有权由 lifetime_holder 管理
  • lifetime_holder 会保持到 Slot 释放

注意事项:

  1. 用于导入外部 DMA-BUF(如 CameraController 的帧)
  2. lifetime_holder 用于保持外部资源生命周期
  3. 实现零拷贝编码
  4. Slot 标记为使用外部 DMA-BUF

使用例程:

camera.setFrameCallback([&](FramePtr frame) {
    if (frame->type() == Frame::MemoryType::DMABUF) {
        auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
        if (dmabuf && slot_id >= 0) {
            // 获取外部 DMA-BUF fd
            int fd = frame->dmabuf_fd();
            auto external_dmabuf = DmaBuffer::import(fd);
            
            // 提交外部 DMA-BUF
            auto meta = encoder->submitFilledSlotWithExternal(
                slot_id, external_dmabuf, frame);
            
            // 等待编码完成
            while (!encoder->tryGetEncodedPacket(meta)) {
                std::this_thread::sleep_for(std::chrono::milliseconds(1));
            }
            
            // 使用编码结果
            if (meta.packet) {
                write_packet(meta.packet->data(), meta.packet->length());
            }
        }
    }
});

tryGetEncodedPacket() - 获取编码结果

bool tryGetEncodedPacket(EncodedMeta& meta);

参数说明:

  • meta (输入/输出): 编码元信息

返回值:

  • true: 获取成功,meta.packet 包含编码结果
  • false: 获取失败,Slot 还在编码中

所有权归属:

  • meta.packet 的所有权由调用者持有

注意事项:

  1. 轮询检查编码是否完成
  2. 编码完成后 meta.packet 会被填充
  3. 获取后 Slot 状态仍为 Encoded
  4. 必须调用 releaseSlot() 释放 Slot

使用例程:

auto meta = encoder->submitFilledSlot(slot_id);

// 等待编码完成
while (!encoder->tryGetEncodedPacket(meta)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

// 使用编码结果
if (meta.packet && meta.packet->isKeyframe()) {
    printf("Keyframe: %zu bytes\n", meta.packet->length());
}

// 释放 Slot
encoder->releaseSlot(slot_id);

releaseSlot() - 释放 Slot

void releaseSlot(int slot_id);

参数说明:

  • slot_id (输入): 要释放的 Slot 索引

返回值: 无

所有权归属:

  • Slot 所有权回归到池中

注意事项:

  1. Slot 状态从 Encoded 转换为 Writable
  2. Slot 可以被再次使用
  3. 必须在获取编码结果后调用
  4. 线程安全操作

使用例程:

// 使用 SlotGuard 自动释放
{
    auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
    // ... 填充数据 ...
    auto meta = encoder->submitFilledSlot(slot_id);
    
    // 等待编码完成
    while (!encoder->tryGetEncodedPacket(meta)) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    
    // 使用编码结果
    if (meta.packet) {
        write_packet(meta.packet->data(), meta.packet->length());
    }
    
    encoder->releaseSlot(slot_id);
}

coreId() - 获取核心 ID

int coreId() const noexcept;

参数说明: 无

返回值: 核心编号

所有权归属:

  • 只读访问

load() - 获取负载

size_t load() const noexcept;

参数说明: 无

返回值: 当前负载(正在使用或编码中的 Slot 数量)

所有权归属:

  • 只读访问

注意事项:

  • 负载 = SLOT_COUNT - free_slots_.size()
  • 负载越小,可用空间越大

辅助类定义

SlotGuard 类

RAII 风格的 Slot 守护类,自动释放 Slot。

方法定义

class SlotGuard {
public:
    SlotGuard(MppEncoderCore* c, int s);
    ~SlotGuard();
    void release();  // 手动释放,禁用自动释放
};

使用例程:

// 自动释放
{
    auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
    SlotGuard guard(encoder.get(), slot_id);
    
    // ... 填充数据和编码 ...
    
    // guard 析构时自动调用 releaseSlot()
}

// 手动释放
{
    auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
    SlotGuard guard(encoder.get(), slot_id);
    
    // ... 编码 ...
    
    guard.release();  // 禁用自动释放
    // ... 继续使用 slot_id ...
    encoder->releaseSlot(slot_id);
}

所有权规则总结

资源 创建者 拥有者 释放方式 线程保护
MppEncoderContext 构造函数 MppEncoderCore 析构函数 内部同步
Slot[] initSlots() MppEncoderCore cleanupSlots() free_mtx_
DMA-BUF acquireWritableSlot() Slot cleanupSlots() 原子操作
EncodedPacket workerThread EncodedMeta 析构函数 原子操作
External DMA-BUF submitFilledSlotWithExternal() lifetime_holder 析构函数 原子操作

线程安全说明

同步机制

  1. Slot 状态: std::atomic<SlotState> 保护
  2. Free Slots: std::mutex free_mtx_ 保护
  3. Pending Slots: std::mutex pending_mtx_ 保护
  4. 编码线程: std::condition_variable pending_cv_ 同步
  5. 参数切换: std::mutex switch_mtx_ 保护

线程安全建议

  • 获取 Slot: acquireWritableSlot() 是线程安全的
  • 提交 Slot: submitFilledSlot() 是线程安全的
  • 获取结果: tryGetEncodedPacket() 是线程安全的
  • 释放 Slot: releaseSlot() 是线程安全的
  • 重置配置: resetConfig() 是线程安全的

典型使用场景

场景 1: 基本编码流程

// 创建编码器
MppEncoderContext::Config cfg;
cfg.width = 1920;
cfg.height = 1080;
cfg.format = MPP_FMT_YUV420SP;
cfg.type = MPP_VIDEO_CodingAVC;

auto encoder = std::make_shared<MppEncoderCore>(cfg, 0);

// 编码流程
for (int i = 0; i < 100; ++i) {
    // 1. 获取可写 Slot
    auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
    if (!dmabuf || slot_id < 0) {
        printf("No available slot\n");
        continue;
    }
    
    // 2. 填充数据
    void* data = dmabuf->map();
    memcpy(data, frame_data, frame_size);
    dmabuf->unmap();
    
    // 3. 提交编码
    auto meta = encoder->submitFilledSlot(slot_id);
    
    // 4. 等待编码完成
    while (!encoder->tryGetEncodedPacket(meta)) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    
    // 5. 使用编码结果
    if (meta.packet) {
        write_file(meta.packet->data(), meta.packet->length());
    }
    
    // 6. 释放 Slot
    encoder->releaseSlot(slot_id);
}

// 结束编码
encoder->endOfthisEncode();

场景 2: 零拷贝编码(与 CameraController 配合)

camera.setFrameCallback([&](FramePtr frame) {
    if (frame->type() == Frame::MemoryType::DMABUF) {
        auto [dmabuf, slot_id] = encoder->acquireWritableSlot();
        if (!dmabuf || slot_id < 0) {
            return;  // 无可用 Slot
        }
        
        // 导入外部 DMA-BUF
        int fd = frame->dmabuf_fd();
        auto external_dmabuf = DmaBuffer::import(fd);
        
        // 提交外部 DMA-BUF(零拷贝)
        auto meta = encoder->submitFilledSlotWithExternal(
            slot_id, external_dmabuf, frame);
        
        // 等待编码完成
        while (!encoder->tryGetEncodedPacket(meta)) {
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }
        
        // 写入文件
        if (meta.packet) {
            write_file(meta.packet->data(), meta.packet->length());
        }
        
        // 释放 Slot
        encoder->releaseSlot(slot_id);
    }
});

场景 3: 多编码器实例

// 创建两个编码器实例
MppEncoderContext::Config cfg1;
cfg1.width = 1920; cfg1.height = 1080;
cfg1.format = MPP_FMT_YUV420SP;
cfg1.type = MPP_VIDEO_CodingAVC;

MppEncoderContext::Config cfg2;
cfg2.width = 1280; cfg2.height = 720;
cfg2.format = MPP_FMT_YUV420SP;
cfg2.type = MPP_VIDEO_CodingHEVC;

auto encoder1 = std::make_shared<MppEncoderCore>(cfg1, 0);
auto encoder2 = std::make_shared<MppEncoderCore>(cfg2, 1);

// 使用不同的编码器
auto [dmabuf1, slot_id1] = encoder1->acquireWritableSlot();
// ... 编码到 1080p ...

auto [dmabuf2, slot_id2] = encoder2->acquireWritableSlot();
// ... 编码到 720p ...

注意事项

  1. Slot 数量: 固定为 15 个(RK356X 实测最优值)
  2. 状态转换: 必须按照正确的状态转换流程
  3. Slot 释放: 获取编码结果后必须释放 Slot
  4. 零拷贝: 使用 submitFilledSlotWithExternal() 实现零拷贝
  5. 线程安全: 所有公共方法都是线程安全的
  6. 结束编码: 销毁前必须调用 endOfthisEncode()
  7. 负载监控: 使用 load() 方法监控负载
  8. 错误处理: 检查返回值和 meta.packet 是否为空

相关文档


参考资料

⚠️ **GitHub.com Fallback** ⚠️