视频编解码(MPP)
目标平台: RK3576 | NPU 算力: 6 TOPS | 文档版本: V1.0
适用板型: C-3576BQ 核心板 + M4-R2 底板
最后更新: 2026-10-23
1. 概述
1.1 功能简介
RK3576 搭载 Rockchip MPP(Media Process Platform)硬件编解码引擎,支持 4K 级别视频的硬件加速解码与编码,可有效降低 CPU 负载,适用于视频播放、监控、会议等场景。
硬件编解码单元:
| 模块 | 寄存器基地址 | 功能 |
|---|---|---|
| rkvdec2 | <base_addr>(以实际 SDK 为准) | 视频解码器,支持 H.264/H.265/VP9 |
| rkvenc | <base_addr>(以实际 SDK 为准) | 视频编码器,支持 H.264/H.265 |
上表中寄存器基地址为占位符,RK3576 实际的 VPU 设备树节点名与寄存器基地址以实际发布的 RK3576 SDK 为准(待板上确认)。
编解码能力:
| 编解码类型 | 分辨率/帧率 | 说明 |
|---|---|---|
| H.264 解码 | 4K@30fps | 最高支持 4K 解码 |
| H.265 解码 | 4K@30fps | 支持 4K@30 解码 |
| VP9 解码 | 4K@30fps | Google VP9 格式 |
| H.264 编码 | 4K@30fps | 硬件编码 |
| H.265 编码 | 4K@30fps | 高压缩率编码 |
以上编解码能力基于 M4-R2 规格书与 RK3576 平台通用资料整理,实际帧率与分辨率需在板端实测(待板上验证)。
核心特性: 多路并行编解码、低延迟模式、硬件后处理(去块、色彩空间转换)、DRM/GBM Buffer 支持、NV12/NV21/YUV420P 等多种像素格式。
MPP 库: librockchip_mpp.so
1.2 应用场景
| 应用场景 | 使用的编解码能力 | 说明 |
|---|---|---|
| 智能安防监控 | 多路解码 + 编码 | 同时解码多路摄像头流并编码存储 |
| 视频会议终端 | H.264/H.265 编解码 | 实时双向视频通话 |
| 4K 媒体播放 | H.265/VP9 解码 | 4K 超高清视频播放 |
| 视频转码服务 | 编码 + 解码 | 格式转换与转码服务 |
| AI 视频分析 | 解码 + 后处理 | 视频帧提取用于 AI 推理 |
1.3 工作原理
┌─────────────────────────────────┐
│ RK3576 MPP 架构 │
└─────────────────────────────────┘
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ 视频输入 │────>│ rkvdec2 │────>│ MPP 框架 │
│ (编码流) │ │ 硬件解码器 │ │ (用户态库) │
└──────────┘ │ <base_addr> │ └──────┬───────┘
│ H.264/H.265│ │
│ VP9 │ │
└──────────────┘ ┌──────┴───────┐
│ │
┌──────────┐ ┌──────────────┐ │ 应用程序 │
│ 视频输出 │<────│ rkvenc │<────│ │
│ (编码流) │ │ 硬件编码器 │ └──────────────┘
└──────────┘ │ <base_addr> │
│ H.264/H.265│
└──────────────┘2. 硬件准备
2.1 硬件清单
| 序号 | 硬件名称 | 型号/规格 | 说明 |
|---|---|---|---|
| 1 | 核心板 | C-3576BQ | RK3576 主芯片,含 VPU 编解码单元 |
| 2 | 底板 | ShimetaPi M4-R2 V10 | 扩展接口底板 |
| 3 | 电源 | 5V/2A(Type-C 或 6pin) | M4-R2 供电要求 |
| 4 | HDMI 输出 | HDMI 线缆 | 用于显示输出 |
| 5 | 存储 | eMMC 32GB 或 TF 卡 | 用于系统和测试码流 |
| 6 | 串口调试 | USB 转 TTL 串口线 | 用于串口调试 |
| 7 | 网线 | 千兆网线 | 用于网络传输和 SSH |
2.2 硬件确认
# 确认 RK3576 芯片
cat /proc/device-tree/model
# 确认内核版本(应为 6.1.x)
uname -r
# 确认 MPP 相关内核模块
lsmod | grep mpp
# 预期看到 rkvdec2, rkvenc 等模块
# 确认设备树中的 VPU 节点(节点名/地址以实际 SDK 为准)
find /sys/firmware/devicetree/base/ -name "*vdec*" -o -name "*venc*"
cat /proc/device-tree/vpu@*/compatible 2>/dev/null
# 预期输出: rockchip,rkvdec2 / rockchip,rkvenc3. 软件准备
3.1 内核配置项
# MPP 相关配置(配置项以实际发布的 RK3576 SDK 内核为准)
CONFIG_ROCKCHIP_MPP=y
CONFIG_ROCKCHIP_VCODEC=y
CONFIG_ROCKCHIP_RKVDEC2=y
CONFIG_ROCKCHIP_RKVENC=y
# V4L2 相关配置
CONFIG_V4L2_VIDEODEV=y
CONFIG_V4L2_MEM2MEM=y
CONFIG_MEDIA_SUPPORT=y
CONFIG_MEDIA_CONTROLLER=y
# DMA-BUF 相关配置
CONFIG_DMABUF=y
CONFIG_DMA_BUF_SYNC=y
CONFIG_DMABUF_HEAPS=y
CONFIG_DMABUF_HEAPS_ROCKCHIP=y3.2 设备树配置
以下为 RK3576 平台通用结构示例,具体时钟、中断、电源域名称以实际发布的 RK3576 SDK 设备树源码为准(待板上确认):
/* 解码器节点(示例) */
vpu_dec@<base_addr> {
compatible = "rockchip,rkvdec2";
reg = <0x0 0x<base_addr> 0x0 0x10000>;
interrupts = <GIC_SPI xxx IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_VDEC>, <&cru HCLK_VDEC>;
clock-names = "axi", "ahb";
resets = <&cru SRST_A_VDEC>, <&cru SRST_H_VDEC>;
reset-names = "axi", "ahb";
status = "okay";
};
/* 编码器节点(示例) */
vpu_enc@<base_addr> {
compatible = "rockchip,rkvenc";
reg = <0x0 0x<base_addr> 0x0 0x10000>;
interrupts = <GIC_SPI xxx IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_VENC>, <&cru HCLK_VENC>,
<&cru CLK_VENC_CORE>;
clock-names = "axi", "ahb", "core";
resets = <&cru SRST_A_VENC>, <&cru SRST_H_VENC>;
reset-names = "axi", "ahb";
status = "okay";
};3.3 MPP 库安装
# 方法1:使用预编译库(推荐)
ls /usr/lib/librockchip_mpp.so*
ldconfig -p | grep rockchip_mpp
# 方法2:从源码编译安装
git clone https://github.com/rockchip-linux/mpp.git
cd mpp/build/linux/aarch64/
./make-Makefiles.bash
make -j$(nproc)
sudo cp librockchip_mpp.so* /usr/lib/
sudo cp librockchip_mpp_*.so* /usr/lib/
sudo ldconfig
# 确认安装
ls -la /usr/lib/librockchip_mpp*3.4 ffmpeg 编译(带 MPP 硬件加速)
# 获取 Rockchip 定制 ffmpeg 源码
git clone https://github.com/rockchip-linux/ffmpeg.git
cd ffmpeg
./configure \
--enable-rkmpp \
--enable-libdrm \
--enable-gpl \
--enable-nonfree \
--prefix=/usr/local
make -j$(nproc)
sudo make install
# 验证 rkmpp 支持
ffmpeg -hwaccels 2>&1 | grep rkmpp
ffmpeg -encoders 2>&1 | grep rkmpp
# 预期看到 h264_rkmpp, hevc_rkmpp4. 操作步骤
4.1 MPP 测试工具编译
git clone https://github.com/rockchip-linux/mpp.git
cd mpp && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DROCKCHIP_MPP=ON
make -j$(nproc)
# 编译产物
ls -la mpi_dec_test # 解码测试工具
ls -la mpi_enc_test # 编码测试工具4.2 解码测试
# H.265 解码
./mpi_dec_test -t 2 -i test.h265 -o output.yuv
# H.264 解码
./mpi_dec_test -t 1 -i test.h264 -o output.yuv
# VP9 解码
./mpi_dec_test -t 4 -i test.vp9 -o output.yuv
# 指定分辨率输出
./mpi_dec_test -t 2 -i test.h265 -o output.yuv -w 1920 -h 10804.3 编码测试
# H.264 编码
./mpi_enc_test -t 1 -w 1920 -h 1080 -i input.yuv -o output.h264
# H.265 编码
./mpi_enc_test -t 2 -w 1920 -h 1080 -i input.yuv -o output.h265
# 参数说明:
# -t 1 H.264 格式
# -t 2 H.265 格式
# -w/-h 宽度/高度
# -i/-o 输入/输出文件
# 1080p H.265 高质量编码
./mpi_enc_test -t 2 -w 1920 -h 1080 -i input_1080p.yuv \
-o output_1080p.h265 -b 10000000 -fps 30 -qp 184.4 多路编解码
#!/bin/bash
# multi_decode.sh - 多路并行解码
STREAMS=("stream1.h265" "stream2.h265" "stream3.h265")
OUTPUTS=("out1.yuv" "out2.yuv" "out3.yuv")
for i in "${!STREAMS[@]}"; do
./mpi_dec_test -t 2 -i "${STREAMS[$i]}" -o "${OUTPUTS[$i]}" &
done
wait
echo "All decoding finished"多路并行能力与路数上限需在 C-3576BQ + M4-R2 V10 板端实测(待板上验证)。
4.5 ffmpeg 集成
# 硬件解码
ffmpeg -hwaccel rkmpp -i input.h265 -f rawvideo output.yuv
# 硬件编码(H.264)
ffmpeg -f rawvideo -pix_fmt yuv420p -video_size 1920x1080 \
-i input.yuv -c:v h264_rkmpp -b:v 10M output.h264
# 硬件编码(H.265)
ffmpeg -f rawvideo -pix_fmt yuv420p -video_size 1920x1080 \
-i input.yuv -c:v hevc_rkmpp -b:v 10M output.h265
# 视频转码(全硬件加速)
ffmpeg -hwaccel rkmpp -i input.mp4 -c:v h264_rkmpp -b:v 8M -c:a copy output.mp44.6 视频转码
# H.265 -> H.264 转码
ffmpeg -hwaccel rkmpp -i input.h265 -c:v h264_rkmpp -b:v 8M output.h264
# 1080p -> 720p 降分辨率转码
ffmpeg -hwaccel rkmpp -i input_1080p.mp4 -vf scale=1280:720 \
-c:v h264_rkmpp -b:v 4M -c:a copy output_720p.mp4
# 批量转码脚本
#!/bin/bash
for f in *.mp4; do
ffmpeg -hwaccel rkmpp -i "$f" -c:v h264_rkmpp -b:v 8M -c:a copy "${f%.mp4}_h264.mp4"
done4.7 Python 使用示例
#!/usr/bin/env python3
"""RK3576 MPP 视频编解码 Python 封装"""
import subprocess
import os
import time
import json
class RK3576Codec:
def __init__(self):
result = subprocess.run(["ffmpeg", "-hwaccels"], capture_output=True, text=True)
if "rkmpp" not in result.stdout:
raise RuntimeError("ffmpeg 未编译 rkmpp 支持")
print("rkmpp 硬件加速:已支持")
def decode(self, input_file, output_file):
"""硬件解码视频文件"""
cmd = ["ffmpeg", "-y", "-hwaccel", "rkmpp", "-i", input_file,
"-f", "rawvideo", "-pix_fmt", "yuv420p", output_file]
start = time.time()
result = subprocess.run(cmd, capture_output=True, text=True)
elapsed = time.time() - start
if result.returncode != 0:
raise RuntimeError(f"解码失败:\n{result.stderr}")
info = self._probe(input_file)
info["decode_time"] = elapsed
return info
def encode(self, input_yuv, output_file, width, height,
codec="h264_rkmpp", bitrate="8M", fps=30):
"""硬件编码 YUV 数据"""
cmd = ["ffmpeg", "-y", "-f", "rawvideo", "-pix_fmt", "yuv420p",
"-video_size", f"{width}x{height}", "-framerate", str(fps),
"-i", input_yuv, "-c:v", codec, "-b:v", bitrate, output_file]
start = time.time()
result = subprocess.run(cmd, capture_output=True, text=True)
elapsed = time.time() - start
if result.returncode != 0:
raise RuntimeError(f"编码失败:\n{result.stderr}")
return {"encode_time": elapsed, "file_size": os.path.getsize(output_file)}
def transcode(self, input_file, output_file,
codec="h264_rkmpp", bitrate="8M", scale=None):
"""硬件转码视频文件"""
cmd = ["ffmpeg", "-y", "-hwaccel", "rkmpp", "-i", input_file,
"-c:v", codec, "-b:v", bitrate, "-c:a", "copy"]
if scale:
cmd.extend(["-vf", f"scale={scale[0]}:{scale[1]}"])
cmd.append(output_file)
start = time.time()
result = subprocess.run(cmd, capture_output=True, text=True)
elapsed = time.time() - start
if result.returncode != 0:
raise RuntimeError(f"转码失败:\n{result.stderr}")
return {"transcode_time": elapsed}
@staticmethod
def _probe(filepath):
cmd = ["ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", "-show_streams", filepath]
result = subprocess.run(cmd, capture_output=True, text=True)
try:
info = json.loads(result.stdout)
for s in info.get("streams", []):
if s.get("codec_type") == "video":
return {"width": s.get("width"), "height": s.get("height"),
"codec": s.get("codec_name")}
except (json.JSONDecodeError, ValueError):
pass
return {}
if __name__ == "__main__":
codec = RK3576Codec()
print("=== 解码示例 ===")
r = codec.decode("input.h265", "output.yuv")
print(f" {r.get('width')}x{r.get('height')}, 耗时: {r['decode_time']:.2f}s")
print("=== 编码示例 ===")
r = codec.encode("input.yuv", "output.h264", 1920, 1080)
print(f" 耗时: {r['encode_time']:.2f}s, 大小: {r['file_size']/1024/1024:.1f}MB")5. 代码详解
5.1 MPP API 详解
#include <rockchip/mpp.h>
#include <rockchip/mpp_mem.h>
#include <rockchip/mpp_buffer.h>
/* MPP 编解码上下文结构 */
typedef struct MppContext {
MppApi *mpp_api;
MppCtx ctx;
MppBufferGroup frm_grp;
MppBufferGroup pkt_grp;
MppPacket packet;
MppFrame frame;
} MppContext;
/* 创建并初始化解码器 */
int init_decoder(MppContext *mpp, MppVideoCodingType coding)
{
MPP_RET ret;
ret = mpp_create(&mpp->ctx, &mpp->mpp_api);
if (ret != MPP_OK) return -1;
ret = mpp_init(mpp->ctx, MPP_CTX_DEC, coding);
if (ret != MPP_OK) return -1;
ret = mpp_buffer_group_get_internal(&mpp->frm_grp, MPP_BUFFER_TYPE_ION);
if (ret != MPP_OK) return -1;
return 0;
}
/* 创建并初始化编码器 */
int init_encoder(MppContext *mpp, RK_U32 width, RK_U32 height,
RK_U32 fps, RK_U32 bps)
{
MPP_RET ret;
ret = mpp_create(&mpp->ctx, &mpp->mpp_api);
if (ret != MPP_OK) return -1;
ret = mpp_init(mpp->ctx, MPP_CTX_ENC, MPP_VIDEO_CodingAVC);
if (ret != MPP_OK) return -1;
/* 配置编码参数 */
MppEncCfg cfg;
mpp_enc_cfg_init(&cfg);
mpp_enc_cfg_set_u32(cfg, "prep:width", width);
mpp_enc_cfg_set_u32(cfg, "prep:height", height);
mpp_enc_cfg_set_u32(cfg, "rc:bps_target", bps);
mpp_enc_cfg_set_u32(cfg, "rc:bps_max", bps * 120 / 100);
mpp_enc_cfg_set_u32(cfg, "rc:fps_in_num", fps);
mpp_enc_cfg_set_u32(cfg, "rc:fps_out_num", fps);
mpp_enc_cfg_set_u32(cfg, "rc:gop", fps * 2);
ret = mpp->mpp_api->control(mpp->ctx, MPP_ENC_SET_CFG, cfg);
mpp_enc_cfg_deinit(cfg);
if (ret != MPP_OK) return -1;
return 0;
}5.2 解码流程
int decode_video(MppContext *mpp, uint8_t *data, size_t len)
{
MPP_RET ret;
/* 创建并设置输入包 */
ret = mpp_packet_init(&mpp->packet, mpp->pkt_grp, len);
if (ret != MPP_OK) return -1;
mpp_packet_set_data(mpp->packet, data);
mpp_packet_set_size(mpp->packet, len);
mpp_packet_set_pos(mpp->packet, data);
mpp_packet_set_eos(mpp->packet);
/* 送入解码器 */
ret = mpp->mpp_api->decode_put_packet(mpp->ctx, mpp->packet);
if (ret != MPP_OK) return -1;
/* 获取解码结果 */
ret = mpp->mpp_api->decode_get_frame(mpp->ctx, &mpp->frame);
if (ret != MPP_OK) return -1;
if (mpp->frame) {
RK_U32 w = mpp_frame_get_width(mpp->frame);
RK_U32 h = mpp_frame_get_height(mpp->frame);
MppBuffer buf = mpp_frame_get_buffer(mpp->frame);
printf("Decoded: %dx%d, buf=%p\n", w, h, mpp_buffer_get_ptr(buf));
mpp_frame_deinit(&mpp->frame);
}
return 0;
}5.3 编码流程
int encode_video(MppContext *mpp, uint8_t *yuv_data, size_t yuv_size,
FILE *output_file)
{
MPP_RET ret;
ret = mpp_frame_init(&mpp->frame);
if (ret != MPP_OK) return -1;
mpp_frame_set_fmt(mpp->frame, MPP_FMT_YUV420SP);
mpp_frame_set_width(mpp->frame, 1920);
mpp_frame_set_height(mpp->frame, 1080);
mpp_frame_set_ver_stride(mpp->frame, 1080);
mpp_frame_set_hor_stride(mpp->frame, 1920);
MppBuffer buf;
ret = mpp_buffer_get(mpp->frm_grp, &buf, yuv_size);
if (ret != MPP_OK) return -1;
memcpy(mpp_buffer_get_ptr(buf), yuv_data, yuv_size);
mpp_frame_set_buffer(mpp->frame, buf);
mpp_frame_set_eos(mpp->frame, 0);
/* 送入编码器 */
ret = mpp->mpp_api->encode_put_frame(mpp->ctx, mpp->frame);
if (ret != MPP_OK) return -1;
/* 获取编码包 */
ret = mpp->mpp_api->encode_get_packet(mpp->ctx, &mpp->packet);
if (ret != MPP_OK) return -1;
if (mpp->packet) {
void *pkt_data = mpp_packet_get_data(mpp->packet);
size_t pkt_len = mpp_packet_get_length(mpp->packet);
fwrite(pkt_data, 1, pkt_len, output_file);
mpp_packet_deinit(&mpp->packet);
}
mpp_frame_deinit(&mpp->frame);
mpp_buffer_put(buf);
return 0;
}5.4 MPP API 参考速查
| API 函数 | 功能 |
|---|---|
mpp_create() | 创建 MPP 实例 |
mpp_init() | 初始化编/解码上下文 |
mpp_destroy() | 销毁 MPP 实例 |
mpp_packet_init() | 初始化输入包 |
mpp_frame_init() | 初始化输出帧 |
decode_put_packet() | 送入编码数据(解码) |
decode_get_frame() | 获取解码帧 |
encode_put_frame() | 送入 YUV 帧(编码) |
encode_get_packet() | 获取编码后的码流包 |
control() | 控制操作(如设置编码参数、请求 I 帧) |
编码类型常量:
| 常量 | 值 | 说明 |
|---|---|---|
MPP_VIDEO_CodingAVC | 1 | H.264 |
MPP_VIDEO_CodingHEVC | 2 | H.265 |
MPP_VIDEO_CodingVP9 | 4 | VP9 |
像素格式常量:
| 常量 | 说明 |
|---|---|
MPP_FMT_YUV420SP | NV12 |
MPP_FMT_YUV420P | YUV420P (I420) |
MPP_FMT_YUV422SP | NV16 |
MPP_FMT_RGB888 | RGB888 |
MPP_FMT_RGBA8888 | RGBA8888 |
6. 常见问题
6.1 MPP 库加载失败
现象: error while loading shared libraries: librockchip_mpp.so
ls -la /usr/lib/librockchip_mpp*
ldconfig -p | grep rockchip
# 如果不存在,手动安装
sudo cp /path/to/librockchip_mpp.so* /usr/lib/
sudo ldconfig6.2 解码输出异常(花屏/绿屏)
ffprobe -i test.h265 # 检查文件完整性
./mpi_dec_test -t 2 -i test.h265 -o output.yuv -w 1920 -h 1080 # 指定分辨率6.3 编码性能不足
sudo cpupower frequency-set -g performance # CPU 性能模式
./mpi_enc_test -t 2 -w 1280 -h 720 -i input.yuv -o output.h265 # 降低分辨率
cat /sys/kernel/debug/clk/clk_summary | grep -i venc # 检查 VPU 时钟6.4 多路解码内存不足
free -h # 检查系统内存
cat /sys/kernel/debug/dma_buf/bufinfo # 检查 DMA-BUF
cat /sys/kernel/debug/ion/heaps/system # 检查 ION 分配
# 减少同时解码路数或降低分辨率6.5 ffmpeg 硬件加速不生效
ffmpeg -hwaccels 2>&1 | grep rkmpp # 检查 rkmpp 是否可用
ls -la /dev/mpp_service # 检查 MPP 设备节点
ffmpeg -hwaccel rkmpp -i input.mp4 -f rawvideo output.yuv # 明确指定6.6 解码器初始化失败
lsmod | grep rkvdec2 # 检查内核模块
sudo modprobe rkvdec2 # 加载模块
find /sys/firmware/devicetree/base/ -name "*vdec*" 2>/dev/null # 检查设备树
dmesg | grep -i "rkvdec\|rkvenc" # 查看内核日志
sudo chmod 666 /dev/video* # 修复权限7. 相关文档
| 文档名称 | 路径 | 说明 |
|---|---|---|
| 显示框架 | 显示框架 | DRM 显示输出 |
| 摄像头应用 | 摄像头应用 | 摄像头采集与 ISP |
| 音频开发与 ALSA | 音频开发与ALSA | 音频采集与播放 |
| RGA 图像处理 | RGA图像处理 | 解码后格式转换与缩放 |
| C/C++ 应用开发入门 | C/C++应用开发入门 | 编译环境与基础开发 |
| 人脸检测与识别 | 人脸检测与识别 | 解码帧用于 NPU 推理 |
