做嵌入式视频的朋友拿到RK3588,第一件事基本都想试试这颗SoC自带的硬件视频编码到底行不行。我最近一片RK3588板子做了大半个星期的视频压缩改造,核心就是把FFmpeg与Rockchip MPP打通,用板子内置的VPU做H.265硬件编码,从源码编译FFmpeg到推出实时4K30码流,整个过程其实比想象中要顺。这篇文章就是把我的实操过程完整整理出来,包括方案选型、编译步骤、命令行玩法、C语言集成示例,以及各种能让你少踩坑的排查经验。如果你正要在RK3588上做多路摄像头、边缘AI盒子、USB摄像头RTSP推流,或者想把视频码流转成H.265存档,这篇应该能帮你节省不少时间。
1. 为什么要优先用RK3588的硬件编码
1.1 软编与硬编的分水岭在哪里
很多朋友一开始习惯性用CPU跑x265软编码,尤其在x86服务器上跑惯了,拿到RK3588也顺手编译一个x265然后开始压视频。结果是什么呢?4K分辨率30帧输入,x265软编往往只能跑出个位数帧率,CPU八颗核心全部拉满,A76大核温度直接冲到六七十度,整板功耗也飙得厉害。如果是监控录像、车载记录这种需要24小时跑的场景,散热和耗电根本顶不住。
RK3588之所以值得用,是因为它内部集成了专门的视频编码硬件单元,也就是VPU(Video Processing Unit)。喂给它一帧YUV图像,它自己完成帧内预测、帧间预测、变换量化、熵编码这一整套H.265/H.264编码流程,CPU这边只需要做两件事:把原始图像数据交进去,再把编码后的H.265码流取出来。这个活儿单靠ARM CPU的通用算力去硬算,效率差着几个量级。实测下来,同为4K30的H.265编码,x265软编CPU占用几乎打满、帧率不到10fps,而硬编可以稳定跑满30fps,CPU占用维持在5%到10%左右,这个差距在实际业务里就是“能不能实时上线”和“只能离线压片”的区别。
1.2 RK3588的VPU和Rockchip MPP是什么关系
VPU是硬件实体,但硬件不会自己干活,软件得给它下发任务、设置编码参数、搬运数据。Rockchip MPP(Media Process Platform)就是Rockchip官方提供的用户态媒体处理库,它负责封装底层的VPU操作,把硬件的能力以一套相对友好的API暴露给上层应用。
FFmpeg集成MPP之后,系统里就会多出h264_rkmpp、hevc_rkmpp两个硬件编码器。你不需要自己直接调用MPP的复杂接口,FFmpeg的命令行和API已经把底层包装好了。同时它还多了rkmpp这个硬件加速解码后端,视频转码场景下解码也可以交给VPU,CPU占用能压到非常低。这也是为什么先在RK3588上把FFmpeg编译好,后面接摄像头、做转码、推流都会方便很多。Rockchip MPP在社区里比较活跃,源码在GitHub上一直有更新,Ubuntu的rockchip镜像里一般也带了库,整个RKVLC、GStreamer、FFmpeg这条生态链都是通的,比很多其他平台闭门造车强不少。表格整理一下RK3588这颗SoC比较核心的编解码能力,方便后面选型参考:
| 能力项 | 参数 |
|---|---|
| H.265编码 | 硬件支持,最大可支持到8K30级别 |
| H.264编码 | 硬件支持,最大支持较高分辨率实时编码 |
| H.265/H.264解码 | 硬件支持,8K解码能力 |
| VP9/AV1解码 | 硬件支持,8K级别 |
| 软件接口 | Rockchip MPP,FFmpeg/GStreamer均可集成 |
| 典型编码码率 | 1080P建议2M-6M,4K建议8M-20M |
2. FFmpeg集成MPP:完整编译过程
2.1 先确认板子系统里有没有VPU驱动
在动手编译之前,先确认底层的设备节点是否正常。MPP最终是通过访问内核驱动来操作VPU的,RK3588常见的设备节点包括/dev/mpp_service、/dev/rga,以及/dev/media*系列节点。你可以在板子上执行下面几条命令快速检查:
ls /dev/mpp_service ls /dev/rga ls /dev | grep media如果/dev/mpp_service存在,说明内核里的VPU驱动已经加载,这是能跑硬编的前提。如果这个文件不存在,后边怎么编译都是白搭,大概率是内核配置里少了CONFIG_ROCKCHIP_MPP_SERVICE,或者设备树里VPU节点没有使能。我遇到过买回来的开发板系统镜像本身没问题,但自己换了一个裁剪内核之后就找不到节点的情况,排查的时候优先往内核配置这个方向想。
2.2 安装和编译Rockchip MPP
MPP库的源码在GitHub上维护,仓库名是rockchip-linux/mpp。如果你的板子系统镜像里已经装了MPP,可以直接用,用dpkg -L rockchip-mpp或者直接搜头文件来确认:
find /usr -name "rk_mpi.h" 2>/dev/null如果找不到,就选择源码编译。整个编译过程比较简单:
git clone https://github.com/rockchip-linux/mpp.git cd mpp mkdir build && cd build cmake .. -DCMAKE_INSTALL_PREFIX=/usr make -j$(nproc) sudo make install编译之后需要关注几个文件是否到位:头文件一般会装在/usr/include/rockchip或者/usr/include,动态库一般是librockchip_mpp.so和librockchip_mpp_ai.so,pkg-config文件是rockchip_mpp.pc。这些在后面编译FFmpeg的时候都会用到。有些板子系统里MPP版本比较老,如果后边FFmpeg编译出来运行报MppCtx相关段错误,可以考虑升级MPP后再编译一次。
2.3 从源码编译FFmpeg并开启rkmpp
这里最重要的是不要用系统自带的FFmpeg。Ubuntu软件源里的FFmpeg默认不带rkmpp支持,哪怕你后边装好了MPP库,系统FFmpeg也不认识hevc_rkmpp。必须自己拉FFmpeg源码编译。
git clone https://github.com/FFmpeg/FFmpeg.git cd FFmpeg ./configure --prefix=/usr/local/ffmpeg \ --enable-libdrm \ --enable-rkmpp \ --enable-avdevice \ --enable-avfilter \ --enable-gpl \ --disable-doc \ --disable-stripping make -j$(nproc) sudo make installconfigure的时候,FFmpeg会通过pkg-config去找MPP库。如果它提示ERROR: rkmpp not found,通常是pkg-config路径没设置对,可以手动指定一下:
export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure ...编译安装完成后,验证一下:
/usr/local/ffmpeg/bin/ffmpeg -version /usr/local/ffmpeg/bin/ffmpeg -encoders | grep rkmpp /usr/local/ffmpeg/bin/ffmpeg -hwaccels看到hevc_rkmpp和h264_rkmpp出现在编码器列表里,同时-hwaccels输出里有rkmpp,集成就算成功了。这一步搞完,后面所有FFmpeg命令都能直接用硬件编码,不需要再回头折腾库的事情。
2.4 编译依赖的几个小坑
编译前建议先装好常用依赖,避免configure在检查阶段报错:
sudo apt update sudo apt install build-essential cmake git \ libdrm-dev libavcodec-dev libavformat-dev \ libswscale-dev libavutil-dev pkg-config另外FFmpeg版本建议用最新的master分支,或者至少4.4以上。老版本对RKMPP的支持不够完善,某些参数传不进去,编码器初始化的行为也不一致。我一开始图省事用Ubuntu源里自带的“带rkmpp”第三方包,结果编码出来的文件播放器不认,折腾半天,最后还是老老实实自己编译。
3. 命令行实操:5分钟压一段H.265
3.1 最快验证硬件编码的命令
板子准备就绪之后,第一条命令可以用FFmpeg内部的测试画面源生成视频,快速验证编码链路是否正常工作:
/usr/local/ffmpeg/bin/ffmpeg \ -f lavfi -i testsrc2=size=3840x2160:rate=30 \ -c:v hevc_rkmpp -b:v 8M \ -f mp4 /tmp/test_4k_hevc.mp4这条命令生成一段4K30的测试画面,然后用RK3588的VPU编码成H.265,输出到MP4文件。命令跑起来之后观察日志,可以看到编码帧率,正常情况下应该能接近或超过30fps。这时候另开一个终端执行top,查看ffmpeg进程的CPU占用,如果CPU占用只有个位数或百分之十几,基本可以确认走的确实是硬件编码。
如果要针对真实视频文件测试,可以准备一个大码率的MP4或RAW YUV文件试跑:
/usr/local/ffmpeg/bin/ffmpeg -hwaccel rkmpp \ -i input_4k.mp4 \ -c:v hevc_rkmpp -b:v 6M \ -y output_4k_hevc.mp4-hwaccel rkmpp是让解码端也用VPU硬件解码,这样转码过程整条链路都在VPU上完成,CPU占用基本可以忽略。
3.2 摄像头实时拉流硬编成H.265
RK3588的典型应用场景是把USB摄像头或MIPI摄像头的画面硬编成H.265码流,然后推到RTSP或者存成文件。USB摄像头在RK3588上通常表现为/dev/video0,用v4l2采集再直接送硬编:
/usr/local/ffmpeg/bin/ffmpeg \ -f v4l2 -input_format yuyv422 \ -video_size 1920x1080 -framerate 30 \ -i /dev/video0 \ -c:v hevc_rkmpp -b:v 4M -bf 0 \ -f rtsp rtsp://192.168.1.100:8554/live这里-bf 0是关闭B帧,对低延迟推流场景很有用。如果不关B帧,编码器会为了重组帧顺序引入好几帧的延迟,对于实时预览和远程控制类应用来说体感明显。存文件的命令更简单,把输出的-f rtsp部分换成MP4文件路径即可。
之前看到有人问RK3588怎么把USB摄像头转成RTSP流,上面这条命令就是现成的方案,而且不是CPU软编,CPU占用极低,甚至同一时间再跑一个YOLOv8检测线程都毫无压力。
3.3 关键参数怎么选
命令行里几个关键参数值得单独说一下:
-b:v目标码率,决定画质和文件大小。1080P一般给2M-6M,4K给8M-20M。码率给太低会出现明显的块状模糊。-g关键帧间隔,单位是帧。比如-g 30表示每隔30帧一个关键帧,一秒一个I帧;低延迟和拖动预览场景建议小一点,存储场景可以适当加大。-bfB帧数量,-bf 0可以关闭B帧,降低延迟和内存占用。-r输出帧率,需要跟输入帧率匹配,乱配会掉帧或者快放。-colorspace bt709 -color_primaries bt709 -color_trc bt709颜色元数据,建议加上。H.265默认不带颜色信息时,很多播放器会按BT.601去解,导致颜色发灰、偏色。
我实测发现不写颜色参数时,编码出来的文件在VLC和手机播放器上都会有不同程度的偏色现象,加上BT.709三件套之后颜色才恢复正常。
3.4 怎么能确认真的用了硬件编码
除了top看CPU占用,还有一个更直接的检查方式:运行FFmpeg时仔细看日志里编码器的名字。如果日志显示Stream #0:0: Video: hevc (MPEG4-10) (HEVC),说明输出确实是HEVC,但看不出软硬。想确认编码器本体,用这个命令:
/usr/local/ffmpeg/bin/ffmpeg -hide_banner -h encoder=hevc_rkmpp能打印出Encoder hevc_rkmpp的帮助信息,说明编码器可用,运行命令时指定它就不会落到软编上。另外运行过程中可以在另一个终端用htop或者perf top观察CPU。RK3588硬编4K30的情况下CPU占用如果高于30%,就要怀疑是不是哪里参数写错,让FFmpeg回退到软编了。
4. C语言集成:自己程序里调用RK硬件编码
4.1 为什么还需要C接口
命令行能做的事情很多,但实际业务里往往要在自定义程序里内嵌编码能力。比如你在做RK3588上的YOLOv8检测盒子,一边要做AI推理,另一边需要把原始画面H.265编码后推到后端;或者做视觉SLAM时想把采集的视频流同时录成文件,这时候用C/FFmpeg API集成会更灵活,帧数据可以从推理管线里直接复用,不用再经过命令行进程间的数据搬运。
用FFmpeg API调用hevc_rkmpp,本质上和调用其他编码器没有太大差别:创建编码器上下文、打开编码器、喂YUV帧、拿H.265包。真正要注意的是像素格式对齐和内存释放,这两个地方是新手最容易踩坑的。
4.2 编码器创建与参数配置
下面这段代码展示了核心流程。为了简洁,这里用固定的YUV文件作为输入,实际项目中你可以把frame->data指向摄像头采集或AI预处理后的图像数据。
#include <stdio.h> #include <libavcodec/avcodec.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> int main() { int width = 1920, height = 1080; int fps = 30; const AVCodec *codec = avcodec_find_encoder_by_name("hevc_rkmpp"); if (!codec) { fprintf(stderr, "hevc_rkmpp encoder not found\n"); return -1; } AVCodecContext *ctx = avcodec_alloc_context3(codec); ctx->width = width; ctx->height = height; ctx->time_base = (AVRational){1, fps}; ctx->framerate = (AVRational){fps, 1}; ctx->pix_fmt = AV_PIX_FMT_YUV420P; ctx->bit_rate = 4 * 1000 * 1000; // 4 Mbps ctx->gop_size = fps * 2; // 2秒一个关键帧 ctx->max_b_frames = 0; // 低延迟:关B帧 if (avcodec_open2(ctx, codec, NULL) < 0) { fprintf(stderr, "open rkmpp encoder failed\n"); return -1; } AVFrame *frame = av_frame_alloc(); frame->format = ctx->pix_fmt; frame->width = ctx->width; frame->height = ctx->height; av_frame_get_buffer(frame, 64); FILE *yuv = fopen("input_1080p.yuv", "rb"); FILE *out = fopen("output_1080p.h265", "wb"); AVPacket *pkt = av_packet_alloc(); int ret = 0; while (1) { // 读取一帧YUV420P数据 int plane_size = width * height; size_t read_size = fread(frame->data[0], 1, plane_size, yuv); fread(frame->data[1], 1, plane_size / 4, yuv); fread(frame->data[2], 1, plane_size / 4, yuv); if (read_size <= 0) break; frame->pts = pts++; ret = avcodec_send_frame(ctx, frame); if (ret < 0) break; while (ret >= 0) { ret = avcodec_receive_packet(ctx, pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; fwrite(pkt->data, 1, pkt->size, out); av_packet_unref(pkt); // 重要:不unref会内存泄漏 } } // 冲刷编码器,输出缓冲中的剩余码流 avcodec_send_frame(ctx, NULL); while (avcodec_receive_packet(ctx, pkt) >= 0) { fwrite(pkt->data, 1, pkt->size, out); av_packet_unref(pkt); } fclose(yuv); fclose(out); av_packet_free(&pkt); av_frame_free(&frame); avcodec_free_context(&ctx); return 0; }这段代码需要注意几个点。首先是avcodec_find_encoder_by_name("hevc_rkmpp"),如果你编译FFmpeg时没开rkmpp,这里会直接返回NULL。其次是ctx->pix_fmt,MPP硬编通常支持YUV420P,如果你的源数据是NV12或NV21,需要先用sws_scale做一次格式转换,否则编码器会报错或者输出花屏。
编码结束后,一定要调用avcodec_send_frame(ctx, NULL)做flush,把编码器内部缓冲的帧冲出来。这个动作很多人会漏掉,漏掉的后果就是输出视频文件时长比实际短,或者文件末尾缺几帧关键信息,播放器能放但时长不对。
4.3 延迟和内存方面的心得
硬编码器的数据通路本质是异步的,你送进去一帧,编码器不会马上吐包,内部有流水线缓冲区。这带来的直接问题就是“编码延迟”不是零。如果对实时性有要求,比如图传或远程控制,建议把max_b_frames设为0,并且把gop_size控制小一点,必要时可以尝试通过-muxdelay 0这样的参数进一步缩短缓冲区。如果做的是录像存档,B帧开着没有关系,压缩率会略好一点。
内存方面,avcodec_receive_packet拿到AVPacket后,里面的data指针指向的是动态分配的内存。每轮循环处理完必须av_packet_unref(pkt),否则内存会随编码帧数持续上涨。我见过有人编码半小时后进程内存涨到2GB,就是因为漏了这一步。同样,每一帧从输入源读取数据后,如果不再复用,也要及时释放或重新调用av_frame_get_buffer。
5. 性能实测与场景调优
5.1 软编和硬编的实测对比
我在同一块RK3588板子上分别用x265软编和hevc_rkmpp硬编跑过4K30的H.265编码,参数尽量保持一致,对比结果如下:
| 方案 | 分辨率 | 输出帧率 | CPU占用 | 发热情况 |
|---|---|---|---|---|
| x265 soft encode | 3840x2160 | 5-8 fps | 8核基本满载 | 大核温度很高,需要散热 |
| hevc_rkmpp encode | 3840x2160 | 30 fps稳定 | 5%-10% | 基本感觉不到额外热量 |
| h264_rkmpp encode | 1920x1080 | 30 fps实时 | 3%-5% | 很低 |
这个对比非常直观。软编在4K场景下根本跑不动,硬编则完全游刃有余。唯一需要认清的现实是,硬件编码器追求的是“实时、低功耗、低延迟”,在同等码率下压缩效率会比x265的veryslow预设差一些。也就是说,如果在服务器上用x265慢慢压,可能2M码率就达到的画质,在RK3588上硬编可能需要3M或4M才能接近。业务设计时不要把码率压得太极限,留一点冗余,画质和稳定性都会好很多。
5.2 不同场景下的编码参数建议
不同业务对编码的要求差异很大,整理了三类常见场景的参数参考:
- 多路监控存储:分辨率1080P或4K,码率4M到8M,GOP设大一点(60-120帧),正常开B帧,优先保证压缩率和存储空间。监控画面本身变动不大,长GOP不会带来太多画质问题。
- 实时预览和图传:分辨率720P到1080P,码率2M到6M,关键帧间隔缩短到1到2秒,必须关闭B帧,码控优先CBR。这样解码端能快速出图,网络波动时也不会长时间黑屏。
- 离线归档和分享:分辨率4K,码率可以给到20M甚至更高,GOP可以加大,B帧正常开,画质优先。反正不是实时链路,编码延迟无所谓。
5.3 一套参数打天下的错觉
很多人习惯从网上抄一套FFmpeg参数,改个码率就上生产。实际上RK3588硬编在不同分辨率、不同输入格式下,对参数的处理方式会有区别。比如输入源如果是NV12,而编码器上下文里给的是YUV420P,FFmpeg会自动做转换,但转换会多耗CPU;如果输入源是RGB,转换开销就更大了。实际项目中最好在采集端就统一输出NV12或YUV420P格式,把格式转换的代价降到最低。
另外RGA设备在RK3588上也值得关注。如果要做缩放、旋转、裁剪,与其用CPU sws_scale,不如直接用RGA硬件加速,一条链路CPU占用可以压到非常低。MPP和RGA是Rockchip平台两个常用的硬件加速单元,搭配着用效果最好。
6. 常见问题与方法速查表
6.1 编译阶段高频问题
ERROR: rkmpp not found- 原因:pkg-config找不到MPP库。
- 解决:确认
rockchip_mpp.pc文件位置,设置PKG_CONFIG_PATH,或者使用--extra-cflags和--extra-ldflags直接指定头文件和库路径。 - 验证:执行
pkg-config --exists rockchip_mpp && echo ok。
编译FFmpeg时提示
libdrm not found- 原因:缺少libdrm开发包。
- 解决:
sudo apt install libdrm-dev,RK3588的rkmpp依赖DRM接口。
运行时报
Encoder (rkmpp) cannot be opened- 原因:有两种可能,一是系统里MPP库版本太老,二是驱动设备节点权限不够。
- 解决:先
ls -l /dev/mpp_service确认节点存在,如权限不足用chmod 666 /dev/mpp_service测试,长期使用写udev规则。
6.2 运行阶段高频问题
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| ffmpeg命令报编码器找不到 | 编译没开启rkmpp | 重新编译FFmpeg,用`ffmpeg -encoders |
| 编码输出花屏 | 输入像素格式不匹配 | 确认YUV数据是YUV420P或NV12,宽高是否对齐 |
| 输出文件时间长度不对 | 编码结束未flush | C代码里调用avcodec_send_frame(NULL)冲刷编码器 |
| CPU占用异常高 | 实际回退到软编 | 检查命令行里-c:v是否确实指定了hevc_rkmpp |
| 编码器打开成功但推流卡顿 | 码率过大或GOP过小 | 适当降低码率,增大GOP,或者检查网络带宽 |
| 程序长时间运行内存膨胀 | packet未释放 | 每次avcodec_receive_packet后必须av_packet_unref |
6.3 一个容易忽略的底层验证方法
如果FFmpeg层面的问题不好定位,可以先绕开FFmpeg,直接用MPP自带的测试工具验证VPU硬件是否正常。MPP源码的test目录下有很多可执行文件,编译后跑一下编码测试:
mpi_enc_test -w 1920 -h 1080 -t 7 -n 100 -o /tmp/out.h265这个工具会直接调用MPP底层编码接口,不经过FFmpeg。如果它能正常编码出文件,说明VPU硬件和MPP库链路是好的,问题出在FFmpeg集成或参数传递上。如果这步就跑不通,那基本就是内核驱动、设备树或MPP库版本的问题,再往上层排查都是白费功夫。
另外提醒一句,有不少人在qemu仿真环境里尝试跑RK3588的Android或Ubuntu镜像,想验证硬件编解码,这个思路基本走不通。VPU是SoC上一块实实在在的硬件单元,qemu仿真出来的CPU能跑代码,但仿不来真实的VPU行为。想做硬编开发,还是建议老老实实在实体板子上调试,硬件加速这种东西指望不了纯软件模拟。
做完整套集成之后,我个人最大的体会是RK3588的硬件编码链路已经没有过去那种“只能看不能用”的尴尬了。MPP库接口稳定,FFmpeg集成路径成熟,从命令行到C API都有现成的方案可以抄。最满意的是把hevc_rkmpp接进自己的采集线程之后,AI推理、视频编码、网络推流三件事并行,CPU占用依然很宽裕,这在以前用软编的板子上想都不敢想。如果你也在RK3588上做视频相关业务,建议先花一下午把FFmpeg和MPP这条链路打通,后面写业务代码会顺手非常多。