-
Notifications
You must be signed in to change notification settings - Fork 1
MPP_EncoderCore
SweerItTer edited this page Feb 1, 2026
·
3 revisions
MppEncoderCore 是 utilsCore MPP 模块的核心类,提供基于 Slot 的视频编码核心功能,使用 DMA-BUF 实现零拷贝编码。
- Slot 管理和生命周期控制
- DMA-BUF 缓冲区池管理
- MPP 编码任务调度
- 编码结果包封装
- 多线程异步编码
- 视频录制
- 实时流编码
- 图像压缩
- 多路并发编码
- 依赖: Rockchip MPP 库, DmaBuffer, MppEncoderContext
- 被依赖: EncoderContext, StreamWriter 等上层模块
MppEncoderCore 是编码核心的封装类,提供:
- 15 个 Slot 的缓冲区池(RK356X 实测最优值)
- 异步编码线程
- Slot 状态机管理
- DMA-BUF 零拷贝支持
- 外部 DMA-BUF 导入支持
- 生产者-消费者模式: Slot 队列管理
- 状态机模式: Slot 状态转换
- RAII: SlotGuard 自动释放 Slot
- 工厂模式: 通过 MppEncoderContext 创建
static constexpr size_t SLOT_COUNT = 15; // Slot 数量,RK356X 实测值编码结果包封装,负责管理 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());
}编码元信息,包含编码结果包和 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: 编码后的数据包
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 数据结构,持有 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 状态(原子操作)
explicit MppEncoderCore(const MppEncoderContext::Config& cfg, int core_id);
~MppEncoderCore();参数说明:
-
cfg(输入): 编码配置 -
core_id(输入): 核心编号
返回值: 无
所有权归属:
- MppEncoderCore 拥有所有 Slot 和 MppEncoderContext 的所有权
注意事项:
- 构造时会初始化编码上下文和 Slot 池
- 启动编码线程
- 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);void resetConfig(const MppEncoderContext::Config& cfg);参数说明:
-
cfg(输入): 新的编码配置
返回值: 无
所有权归属:
- 无所有权转移
注意事项:
- 线程安全操作
- 会重置编码上下文
- 不影响当前正在编码的 Slot
void endOfthisEncode();参数说明: 无
返回值: 无
所有权归属:
- 无所有权转移
注意事项:
- 设置结束标志
- 编码线程会在处理完所有待编码 Slot 后退出
- 必须在销毁 MppEncoderCore 前调用
std::pair<DmaBufferPtr, int> acquireWritableSlot();参数说明: 无
返回值:
- 成功: 返回
{DMA-BUF 指针, Slot 索引} - 失败: 返回
{nullptr, -1}
所有权归属:
- 返回的 DMA-BUF 由调用者持有(但所有权仍在 Slot 中)
- Slot 状态自动转换为
Writing
注意事项:
- 如果没有可用的 Slot,返回
{nullptr, -1} - 获取后 Slot 状态变为
Writing - 必须调用
submitFilledSlot()提交或releaseSlot()释放 - 返回的 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");
}EncodedMeta submitFilledSlot(int slot_id);参数说明:
-
slot_id(输入): 填充完成的 Slot 索引
返回值: EncodedMeta(用于后续获取编码结果)
所有权归属:
- 返回的 EncodedMeta 包含 Slot 索引
- Slot 状态自动转换为
Filled
注意事项:
- Slot 状态从
Writing转换为Filled - Slot 会被加入到待编码队列
- 返回的 meta 必须保存,用于获取编码结果
- 必须使用
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());
}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 释放
注意事项:
- 用于导入外部 DMA-BUF(如 CameraController 的帧)
- lifetime_holder 用于保持外部资源生命周期
- 实现零拷贝编码
- 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());
}
}
}
});bool tryGetEncodedPacket(EncodedMeta& meta);参数说明:
-
meta(输入/输出): 编码元信息
返回值:
-
true: 获取成功,meta.packet 包含编码结果 -
false: 获取失败,Slot 还在编码中
所有权归属:
- meta.packet 的所有权由调用者持有
注意事项:
- 轮询检查编码是否完成
- 编码完成后 meta.packet 会被填充
- 获取后 Slot 状态仍为
Encoded - 必须调用
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);void releaseSlot(int slot_id);参数说明:
-
slot_id(输入): 要释放的 Slot 索引
返回值: 无
所有权归属:
- Slot 所有权回归到池中
注意事项:
- Slot 状态从
Encoded转换为Writable - Slot 可以被再次使用
- 必须在获取编码结果后调用
- 线程安全操作
使用例程:
// 使用 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);
}int coreId() const noexcept;参数说明: 无
返回值: 核心编号
所有权归属:
- 只读访问
size_t load() const noexcept;参数说明: 无
返回值: 当前负载(正在使用或编码中的 Slot 数量)
所有权归属:
- 只读访问
注意事项:
- 负载 = SLOT_COUNT - free_slots_.size()
- 负载越小,可用空间越大
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 | 析构函数 | 原子操作 |
-
Slot 状态:
std::atomic<SlotState>保护 -
Free Slots:
std::mutex free_mtx_保护 -
Pending Slots:
std::mutex pending_mtx_保护 -
编码线程:
std::condition_variable pending_cv_同步 -
参数切换:
std::mutex switch_mtx_保护
-
获取 Slot:
acquireWritableSlot()是线程安全的 -
提交 Slot:
submitFilledSlot()是线程安全的 -
获取结果:
tryGetEncodedPacket()是线程安全的 -
释放 Slot:
releaseSlot()是线程安全的 -
重置配置:
resetConfig()是线程安全的
// 创建编码器
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();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);
}
});// 创建两个编码器实例
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 ...- Slot 数量: 固定为 15 个(RK356X 实测最优值)
- 状态转换: 必须按照正确的状态转换流程
- Slot 释放: 获取编码结果后必须释放 Slot
- 零拷贝: 使用 submitFilledSlotWithExternal() 实现零拷贝
- 线程安全: 所有公共方法都是线程安全的
- 结束编码: 销毁前必须调用 endOfthisEncode()
- 负载监控: 使用 load() 方法监控负载
- 错误处理: 检查返回值和 meta.packet 是否为空
- EncoderContext - 编码器上下文
- DmaBuffer - DMA 缓冲区
- StreamWriter - 流写入器
- MPP 模块总览
主页
API 文档
DMA 模块
DRM 模块
- DRM 模块总览
- DeviceController - DRM 设备控制器
- DrmLayer - DRM 图层管理
- PlanesCompositor - DRM 平面合成器
- DrmBpp - DRM 格式定义
NET 模块
- NET 模块总览
- TcpServer - TCP 服务器
- SocketConnection - Socket 连接管理
- CommandHandler - 命令处理器
- DataPacket - 数据包
V4L2 模块
- V4L2 模块总览
- CameraController - V4L2 摄像头控制器
- Frame - V4L2 帧数据结构
- FormatTool - V4L2 格式工具
- Exception - V4L2 异常类
V4L2Param 模块
- V4L2Param 模块总览
- ParamControl - 参数控制
- ParamLogger - 参数日志
- ParamProcessor - 参数处理器
RGA 模块
- RGA 模块总览
- RgaConverter - RGA 转换器
- RgaProcessor - RGA 处理器
- FormatTool - RGA 格式工具
MPP 模块
- MPP 模块总览
- EncoderContext - 编码器上下文
- EncoderCore - 编码器核心
- JpegEncoder - JPEG 编码器
- StreamWriter - 流写入器
- MppResourceGuard - MPP 资源守护
- FileTools - 文件工具
- FormatTool - 格式工具
Sys 模块
- Sys 模块总览
- CpuMonitor - CPU 监控器
- MemoryMonitor - 内存监控器
- Base - 基础类
Mouse 模块
- Mouse 模块总览
- Watcher - 鼠标监视器
Utils 模块
- Utils 模块总览
- AsyncThreadPool - 异步线程池
- ConcurrentQueue - 并发队列
- FdWrapper - 文件描述符包装器
- FenceWatcher - 围栏监视器
- FixedSizePool - 固定大小对象池
- Logger - 日志记录器
- ObjectsPool - 对象池
- OrderedQueue - 有序队列
- ProgressBar - 进度条
- SafeQueue - 安全队列
- SharedBufferState - 共享缓冲区状态
- SimpleVariant - 简单变体类型
- ThreadPauser - 线程暂停器
- ThreadUtils - 线程工具
- Types - 类型定义
- UdevMonitor - Udev 监视器