Skip to content

MPP_EncoderCore

SweerItTer edited this page Feb 1, 2026 · 3 revisions

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 是否为空

相关文档


参考资料

主页

API 文档

DMA 模块

DRM 模块

NET 模块

V4L2 模块

V4L2Param 模块

RGA 模块

MPP 模块

Sys 模块

Mouse 模块

Utils 模块

Clone this wiki locally