视频编码
本章讲用 MPP 框架在 RK182X 上做 H.264 / H.265 硬件视频编码。
整体框图
摄像头 / YUV 源
↓
预处理(RGA:缩放 / 裁剪 / 格式转换)
↓ NV12 帧
MPP Encoder(VPU 硬编码)
↓
码流输出(H.264 / H.265 NALU)
↓
文件存储 / RTSP 推流 / 网络传输RK182X VPU 支持 H.264 / H.265 硬件编码,最高 4K@30fps,适用于摄像头录制、RTSP 推流、视频会议等场景。
1. 编码能力
| 格式 | Profile | 最大分辨率 | 最大帧率 | 最大码率 |
|---|---|---|---|---|
| H.265 / HEVC | Main / Main10 | 4096×2304 | 30 fps | 40 Mbps |
| H.264 / AVC | High Profile | 4096×2304 | 30 fps | 40 Mbps |
2. 完整编码 C 代码示例
六步流程:mpp_create → mpp_enc_cfg_init(设分辨率/帧率/码率/GOP)→ mpp_init(ENC, codec) → MPP_ENC_SET_CFG → 循环(encode_put_frame + encode_get_packet 写文件)→ flush → 清理。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rockchip/mpp_buffer.h>
#include <rockchip/mpp_frame.h>
#include <rockchip/mpp_packet.h>
#include <rockchip/rk_mpi.h>
#include <rockchip/rk_mpi_cmd.h> /* MPP_ENC_SET_CFG */
#include <rockchip/rk_venc_cfg.h> /* mpp_enc_cfg_* */
#include <rockchip/rk_venc_rc.h> /* MPP_ENC_RC_MODE_* */
#include <rockchip/rk_venc_cmd.h> /* MppEncROICfg / MppEncH264Cfg */
int main(void) {
const RK_U32 width = 1920;
const RK_U32 height = 1080;
const RK_U32 fps = 30;
const RK_U32 bitrate = 4000000;
MppCodingType codec = MPP_VIDEO_CodingAVC;
MPP_RET ret;
MppCtx ctx = NULL;
MppApi *mpi = NULL;
ret = mpp_create(&ctx, &mpi);
if (ret) return ret;
MppEncCfg cfg = NULL;
mpp_enc_cfg_init(&cfg);
mpp_enc_cfg_set_s32(cfg, "prep:width", width);
mpp_enc_cfg_set_s32(cfg, "prep:height", height);
mpp_enc_cfg_set_s32(cfg, "prep:format", MPP_FMT_YUV420SP);
mpp_enc_cfg_set_s32(cfg, "rc:mode", MPP_ENC_RC_MODE_CBR);
mpp_enc_cfg_set_s32(cfg, "rc:bps_target", bitrate);
mpp_enc_cfg_set_s32(cfg, "rc:fps_in", fps);
mpp_enc_cfg_set_s32(cfg, "rc:fps_out", fps);
mpp_enc_cfg_set_s32(cfg, "rc:gop", fps * 2);
/* 编码类型由顶层 mpp_init 第三个参数传入;
* 没有 MPP_SET_ENC_CODING_TYPE 控制命令
* 也没有 mpi->init(MppApi 没有 init 成员)
*
* 必须先 mpp_init 再 MPP_ENC_SET_CFG(顺序反了直接 assertion abort,见下方警告)
*/
ret = mpp_init(ctx, MPP_CTX_ENC, codec);
if (ret) return ret;
ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
if (ret) return ret;
FILE *fp_out = fopen("output.h264", "wb");
if (!fp_out) { perror("fopen"); return -1; }
MppBufferGroup group = NULL;
mpp_buffer_group_get_internal(&group, MPP_BUFFER_TYPE_DRM);
const RK_U32 total_frames = fps * 10;
for (RK_U32 i = 0; i < total_frames; i++) {
MppFrame frame = NULL;
MppBuffer buf = NULL;
mpp_frame_init(&frame);
mpp_frame_set_width (frame, width);
mpp_frame_set_height(frame, height);
mpp_frame_set_fmt (frame, MPP_FMT_YUV420SP);
/* mpp_buffer_get_with_tag 5 个参数 (group, &buf, size, tag, caller) */
mpp_buffer_get_with_tag(group, &buf,
width * height * 3 / 2,
"enc_input", __FUNCTION__);
memset(mpp_buffer_get_ptr(buf), 128,
width * height * 3 / 2);
mpp_frame_set_buffer(frame, buf);
mpp_frame_set_pts(frame, (RK_S64)i * 1000 / fps);
mpi->encode_put_frame(ctx, frame);
for (;;) {
MppPacket packet = NULL;
ret = mpi->encode_get_packet(ctx, &packet);
if (ret != MPP_OK || !packet) break;
fwrite(mpp_packet_get_data(packet), 1,
mpp_packet_get_length(packet), fp_out);
mpp_packet_deinit(&packet);
}
mpp_frame_deinit(&frame);
}
/* flush */
mpi->encode_put_frame(ctx, NULL);
for (;;) {
MppPacket packet = NULL;
ret = mpi->encode_get_packet(ctx, &packet);
if (ret != MPP_OK || !packet) break;
fwrite(mpp_packet_get_data(packet), 1,
mpp_packet_get_length(packet), fp_out);
mpp_packet_deinit(&packet);
}
fclose(fp_out);
mpp_buffer_group_put(group);
mpp_enc_cfg_deinit(cfg);
mpp_destroy(ctx);
printf("Encode complete: output.h264\n");
return 0;
}调用顺序硬约束:
MPP_ENC_SET_CFG必须在mpp_init(ctx, MPP_CTX_ENC, codec)之后——mpi->control需要 context 已初始化为 ENC 模式才能识别编码命令。顺序反了会直接 assertion abort:mpp[7484]: mpp: Assertion mpp->mType == MPP_CTX_ENC failed at mpp_control:1190 mpp[7484]: mpp: Assertion mpp->mEnc failed at mpp_control_enc:1525 mpp[7484]: mpp_enc: mpp_enc_control_v2 found NULL enc mpp[7484]: mpp: command 320001 param 0x55a9c1b0e8 ret -3 MPP_ENC_SET_CFG failed: -3编码器最小参数限制(实测 VEPU580/581):
- 分辨率下限 1280×720:设 320×240 会被打回并 fallback 到 1280×720
- 码率范围 1K ~ 100M:设 400K 超范围会 fallback 到 2M
- fps 默认 30:设 15 会 fallback 到 30
实测 fallback 日志:
mpp_enc: invalid set w:h [320:240] stride [0:0] rotation 0 mpp_enc: restore cfg w:h [1280:720] stride [1280:720] rotation 0 mpp_enc: set prep cfg w:h [1280:720] stride [1280:720] fmt 0 rotate 0 mirror 0 mpp_enc: invalid bit per second (bps) 400000 [0:0] out of range 1K~100M mpp_enc: restore bps to 2000000 [1500000:2500000] mpp_enc: set rc cbr bps [2000000:2500000:1500000] fps [30:1:fix] - [30:1:fix] gop 30 hal_h264e_vepu580: hal_h264e_vepu580_status_check wdg timeout
3. 编译编码程序
VPU 编码能力已全部封进 librockchip_mpp,链接时只需要 -lrockchip_mpp。
aarch64-linux-gnu-gcc encode_test.c \
-I/usr/include/rockchip \
-L/usr/lib/aarch64-linux-gnu \
-lrockchip_mpp \
-o encode_test4. API 验证
MPP 编码控制命令:
grep -E "MPP_ENC_SET_ROI_CFG|MPP_ENC_SET_H264_CFG" /usr/include/rockchip/rk_mpi_cmd.h输出:
MPP_ENC_SET_ROI_CFG, /* set MppEncROICfg structure */ROI 和 H264 配置结构:
grep -E "MppEncROICfg|MppEncH264Cfg" /usr/include/rockchip/rk_venc_cmd.h输出:
typedef enum MppEncH264CfgChange_e {
}
MppEncH264CfgChange;
typedef struct MppEncH264Cfg_t {
}
MppEncH264Cfg;
* structure such as MppEncH264Cfg and MppEncJpegCfg. This data
MppEncH264Cfg h264;
typedef struct MppEncROICfg_t {
}
MppEncROICfg;
typedef struct MppEncROICfg0_t {
} MppEncROICfgLegacy;
typedef struct MppEncROICfg2_t {
} MppEncROICfg2;ROI Region 和 quality 字段:
grep -E "MppEncROIRegion|quality" /usr/include/rockchip/rk_venc_cmd.h | head -10输出:
* This is quality and bitrate request from user.
* - paramter quality and qp will not take effect
* - paramter 'quality' define 5 quality levels
* quality - quality parameter, only takes effect in VBR mode
* Mpp provide total 5 quality level:
MppEncRcQuality quality;
* quality config
/* quality config */
* quality factor config
typedef struct MppEncROIRegion_t {buffer with tag 函数:
grep "mpp_buffer_get_with_tag" /usr/include/rockchip/mpp_buffer.h输出:
mpp_buffer_get_with_tag(group, buffer, size, MODULE_TAG, __FUNCTION__)
MPP_RET mpp_buffer_get_with_tag(MppBufferGroup group, MppBuffer *buffer, size_t size,编码速率控制模式:
grep -E "MPP_ENC_RC_MODE_(CBR|VBR|CQP|FIXQP|AVBR)" /usr/include/rockchip/rk_venc_rc.h输出:
MPP_ENC_RC_MODE_VBR,
MPP_ENC_RC_MODE_CBR,
MPP_ENC_RC_MODE_FIXQP,
MPP_ENC_RC_MODE_AVBR,MppApi 编码函数定义:
grep -E "encode_put_frame|encode_get_packet" /usr/include/rockchip/rk_mpi.h输出:
* encode_put_frame : send video frame to encoder only, async interface
* encode_get_packet: get encoded video packet from encoder only, async interface
MPP_RET (*encode_put_frame)(MppCtx ctx, MppFrame frame);
MPP_RET (*encode_get_packet)(MppCtx ctx, MppPacket *packet);5. 码率控制模式
四种模式:通过 mpp_enc_cfg_set_s32(cfg, "rc:mode", ...) 设置。
| 模式 | 常量 | 适用场景 |
|---|---|---|
| 恒定码率(CBR) | MPP_ENC_RC_MODE_CBR | 直播推流、带宽受限 |
| 可变码率(VBR) | MPP_ENC_RC_MODE_VBR | 录像存储、质量优先 |
| 恒定质量(CQP / FIXQP) | MPP_ENC_RC_MODE_FIXQP | 转码、后期处理 |
| 自适应(AVBR) | MPP_ENC_RC_MODE_AVBR | 复杂场景自适应 |
rk_venc_rc.h中实际枚举还包含SMTRC/SE等场景化模式,可按需选用。
6. ROI 区域高质量编码
使用 MppEncROICfg 设置最多 N 个 ROI 区域,每个区域指定 x / y / w / h 和 quality(VBR 模式 1-5,5 最高),对人脸、车牌等重点区域分配更多码率。
MppEncROICfg roi_cfg;
memset(&roi_cfg, 0, sizeof(roi_cfg));
roi_cfg.number = 2;
// ROI 1:人脸区域(高质量)
roi_cfg.regions[0].x = 400; roi_cfg.regions[0].y = 200;
roi_cfg.regions[0].w = 200; roi_cfg.regions[0].h = 200;
roi_cfg.regions[0].quality = 5; // VBR 模式 1-5,5 最高
// ROI 2:车牌区域
roi_cfg.regions[1].x = 800; roi_cfg.regions[1].y = 600;
roi_cfg.regions[1].w = 300; roi_cfg.regions[1].h = 100;
roi_cfg.regions[1].quality = 5;
mpi->control(ctx, MPP_ENC_SET_ROI_CFG, &roi_cfg);7. 低延迟配置
零延迟模式:禁用 B 帧(GOP=0)+ 恒定码率 + H.264 CABAC 熵编码,可将端到端延迟压至约 33ms。
// 零延迟模式(禁用 B 帧 + 最小 GOP)
mpp_enc_cfg_set_s32(enc_cfg, "rc:gop", 0); // 无 B 帧
mpp_enc_cfg_set_s32(enc_cfg, "rc:bps_target", bitrate);
// H.264 编码预设(需要通过 MPP_ENC_SET_CFG 或其他接口)
// 建议参考 SDK 文档或示例代码8. 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
MPP_ENC_SET_CFG failed: -3 | mpp_init 在 MPP_ENC_SET_CFG 之后 | 调换顺序:先 mpp_init 再 control |
restore cfg w:h [1280:720] | 分辨率 < 1280×720 | 调到 1280×720 及以上 |
bps out of range 1K~100M | 码率过低 | 调到 1K~100M |
vepu580_status_check wdg timeout | 输入是全灰帧 | 喂真实 YUV420SP 数据 |
output.h264 0 字节 | watchdog 超时或 flush 没做 | 调 mpi->encode_put_frame(ctx, NULL) |
