1. RKMPP硬解码框架概述
在Rockchip平台上使用FFmpeg进行视频硬解码时,RKMPP(Rockchip Media Process Platform)是最核心的硬件加速接口。这套框架通过MPP(Media Process Platform)中间层,将FFmpeg的软解流程转换为调用SoC内部VPU(Video Process Unit)的硬件加速能力。
1.1 核心架构设计
RKMPP解码器的架构设计遵循了典型的硬件加速器集成模式:
- 接口抽象层:通过
rkmppdec.c实现FFmpeg的AVCodec接口 - 硬件适配层:MPP API封装了底层VPU的差异
- 内存管理层:DRM PRIME机制实现零拷贝
这种分层设计使得上层应用可以统一调用FFmpeg API,而底层自动适配不同型号的Rockchip芯片(如RK3588、RK3566等)。
提示:在RK3588等新一代芯片上,VPU已升级为RKVENC/RKVDEC专用IP核,但MPP接口保持兼容
2. 解码流程深度解析
2.1 初始化阶段关键操作
2.1.1 解码器创建流程
c复制// 创建MPP上下文
mpp_create(&r->mctx, &r->mapi);
// 初始化为解码模式
mpp_init(r->mctx, MPP_CTX_DEC, coding_type);
这个阶段需要特别注意:
- 必须正确设置
coding_type(如MPP_VIDEO_CodingAVC对应H.264) - 创建失败通常意味着:
- 内核驱动未加载(检查
/dev/mpp_service) - 其他进程占用了VPU资源
- 内核驱动未加载(检查
2.1.2 MJPEG特殊处理
MJPEG与其他视频编码有本质区别:
c复制if (avctx->codec_id == AV_CODEC_ID_MJPEG) {
r->buf_mode = RKMPP_DEC_HALF_INTERNAL;
mpp_buffer_group_get_internal(&r->buf_group_misc,
MPP_BUFFER_TYPE_DRM | MPP_BUFFER_FLAGS_DMA32);
}
差异点对比:
| 特性 | H.264/H.265 | MJPEG |
|---|---|---|
| 帧间依赖 | 强(IBP帧) | 无 |
| 数据特征 | 连续大块 | 碎片化小包 |
| 内存策略 | 预分配 | 动态申请 |
2.2 解码循环实现机制
2.2.1 数据流处理流程
mermaid复制graph TD
A[avcodec_send_packet] --> B[rkmpp_decode]
B --> C{解码成功?}
C -->|是| D[avcodec_receive_frame]
C -->|否| E[错误处理]
实际代码中的关键循环:
c复制while (1) {
ret = r->mapi->decode_get_frame(r->mctx, &mpp_frame);
if (ret == MPP_OK && mpp_frame) {
// 帧处理逻辑
break;
}
usleep(5000); // 避免CPU空转
}
2.2.2 跳帧策略优化
直播场景下的关键帧处理:
c复制if (!r->got_first_frame && !(mpp_pkt_flag & MPP_PKT_FLAG_INTRA)) {
av_log(avctx, AV_LOG_DEBUG, "Skip non-intra packet\n");
return 0;
}
这个策略解决了:
- 直播流初始阶段可能连续收到多个P帧
- 点播文件随机seek后的恢复问题
- 硬件解码器启动延迟导致的丢帧
3. 核心函数实现剖析
3.1 rkmpp_decode_receive_frame
3.1.1 信息变更处理
当检测到info_change时(如分辨率变化):
- 重新配置buffer group
- 更新AVCodecContext参数
- 处理隔行扫描模式
c复制if (r->info_change = mpp_frame_get_info_change(mpp_frame)) {
avctx->width = mpp_frame_get_width(mpp_frame);
avctx->height = mpp_frame_get_height(mpp_frame);
rkmpp_set_buffer_group(avctx, new_format, new_width, new_height);
}
3.1.2 帧导出逻辑
根据输出格式分三种处理路径:
- DRM PRIME直接输出:
c复制rkmpp_export_frame(avctx, frame, mpp_frame);
- 软件内存格式转换:
c复制AVFrame *tmp_frame = av_frame_alloc();
rkmpp_export_frame(avctx, tmp_frame, mpp_frame);
av_hwframe_transfer_data(frame, tmp_frame, 0);
- 错误处理:
c复制av_log(avctx, AV_LOG_ERROR, "Unsupported pixel format");
3.2 rkmpp_send_packet实现
3.2.1 数据包转换
非MJPEG流:
c复制mpp_packet_init(&mpp_pkt, pkt->data, pkt->size);
mpp_packet_set_pts(mpp_pkt, mpp_pkt_pts);
r->mapi->decode_put_packet(r->mctx, mpp_pkt);
MJPEG特殊处理:
c复制mpp_buffer_get(r->buf_group_misc, &mpp_buf, pkt->size);
mpp_buffer_write(mpp_buf, 0, pkt->data, pkt->size);
mpp_packet_init_with_buffer(&mpp_pkt, mpp_buf);
3.2.2 同步控制
关键同步点:
c复制mpp_buffer_sync_partial_end(mpp_buf, 0, pkt->size);
这个操作确保:
- CPU写入的数据对VPU可见
- 避免DMA传输未完成就开始解码
4. 实战经验与优化技巧
4.1 性能调优参数
| 参数 | 推荐值 | 作用说明 |
|---|---|---|
| MPP_TIMEOUT_MAX | 50ms | 帧获取超时时间 |
| buf_group大小 | 8-16帧 | 内存池容量 |
| fast_parse | 1 | 启用快速解析(非隔行视频) |
4.2 常见问题排查
-
解码不出帧:
- 检查首帧是否为I帧
- 确认
avcodec_send_packet返回成功 - 查看
dmesg是否有VPU错误
-
内存泄漏:
bash复制cat /proc/<pid>/maps | grep mpp检查是否有未释放的MPP buffer
-
花屏问题:
- 确认码流完整性(ffprobe检查)
- 检查色彩空间配置是否正确
4.3 高级技巧
- 低延迟模式:
c复制int timeout = 1; // 1ms超时
r->mapi->control(r->mctx, MPP_DEC_SET_PARSER_TIMEOUT, &timeout);
- 多实例管理:
c复制// 在RK3588上可同时运行4个1080p解码实例
for (i=0; i<4; i++) {
avcodec_open2(dec_ctx[i], codec, NULL);
}
- DMA-BUF复用:
c复制// 在渲染管线中直接使用DRM fd
int fd = mpp_buffer_get_fd(mpp_frame_get_buffer(mpp_frame));
5. 不同编码格式的差异处理
5.1 H.264/H.265处理
特点:
- 需要处理DPB(Decoded Picture Buffer)
- 支持B帧时需注意显示顺序
- 关键配置:
c复制
avctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
5.2 VP8/VP9注意事项
特殊处理:
c复制if (avctx->codec_id == AV_CODEC_ID_VP9) {
int vp9_superframe = 1;
mpi->control(ctx, MPP_DEC_SET_ENABLE_DEINTERLACE, &vp9_superframe);
}
5.3 MJPEG优化策略
性能提升技巧:
- 预分配缓冲区池
- 禁用去块滤波
c复制int disable_deblock = 1; mpp_dec_cfg_set_s32(cfg, "jpeg:disable_deblock", disable_deblock);
6. 内存管理深度解析
6.1 DRM PRIME机制
工作流程:
- MPP分配DRM buffer
- 导出为DMA-BUF fd
- FFmpeg通过
AV_PIX_FMT_DRM_PRIME访问
优势:
- 零拷贝显示输出
- 支持跨进程共享
6.2 Buffer Group策略
配置示例:
c复制MPP_RET ret = mpp_buffer_group_get_external(&group, MPP_BUFFER_TYPE_DRM);
mpp_buffer_group_limit_config(group, 8, 2); // 8帧缓存,2帧预留
监控方法:
bash复制cat /sys/kernel/debug/mpp/vpu/meminfo
7. 实际项目中的经验教训
-
分辨率切换问题:
- 遇到动态分辨率切换时,必须正确处理
info_change - 实测发现需要在收到变更后清空内部缓冲区
- 遇到动态分辨率切换时,必须正确处理
-
多线程陷阱:
c复制// 错误示例:跨线程调用MPP接口 // 正确做法:通过FFmpeg的线程队列机制 avcodec_send_packet() // 必须在同一线程 -
时间戳处理:
- Rockchip VPU对PTS有特殊要求
- 建议使用
mpp_packet_set_pts()而非依赖AVPacket的pts
-
低功耗场景:
c复制int low_power = 1; mpp_dec_cfg_set_s32(cfg, "base:low_power", low_power);这个配置在电池供电设备上可降低30%功耗