ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3588硬编硬解实战:用FFMedia调用VPU实现H.264低CPU占用

RK3588硬编硬解实战:用FFMedia调用VPU实现H.264低CPU占用 凡是做过视频处理的朋友应该都有同感一台RK3588CPU规格看着不算差A76大核也给了四个可真要拿它做一路1080P的H.264实时软编码x264一跑起来四个大核直接被你拉去干苦力剩下跑算法、跑业务逻辑的资源所剩无几。这不是板子不行是“软编软解”这条路本身就不是嵌入式平台该走的道。RK3588板卡上明明有一颗专门干视频编解码的VPUVideo Processing Unit却被晾在一边这种浪费太可惜了。这次要聊的FFMedia说白了就是把RK3588这颗VPU用起来的常规打开方式。FFMedia这个叫法社区里有人叫rockchip-ffmpeg有人叫rkffmpeg本质都是同一个东西基于FFmpeg做封装把Rockchip MPP的硬件编解码能力包装成FFmpeg标准的解码器/编码器比如大家常见的h264_rkmpp、hevc_rkmpp就是它的核心成员。搞定了它你就能直接在命令行或C代码里走硬件编解码通道H.264的1080P/4K实时编解码CPU占用可以压到很低。这篇教程适合三类人在RK3588上做视频监控、图传、边缘AI盒子、录播一体机的开发被软编CPU占用折磨过、想换硬编又怕踩坑的新手还有手里有板子但一直没跑通rkmpp的朋友。我会按“原理 → 环境 → 编码 → 解码 → 排错”的顺序把从编译FFMedia到跑通H.264硬编硬解的全过程拆开讲清楚每一步都给可直接抄的配置和代码。1. 先搞清楚FFMedia在RK3588上到底解决了什么问题1.1 CPU软编解码为什么是“吃亏”的先算一笔账。H.264编码本身是个计算密集型的活运动估计、DCT变换、熵编码每一步都在做大量重复运算。用软件做比如x264靠的是CPU通用算力硬扛。1080P30的H.264编码在RK3588上用x264的veryfast档位实测下来CPU占用大概在300%到400%之间也就是四个A76大核基本被吃满。这时候你如果还想在板子上跑一个yolov8检测、推一个GUI界面资源就非常紧张了。软件解码相对好一点但也谈不上轻松。纯软解1080P H.264一个A76大核能吃掉30%到50%的占用4K就更明显了。问题是嵌入式设备往往同时要做好几路视频比如四路摄像头同时解码显示一路软解你都嫌多四路软解直接能把你CPU打穿。那VPU是什么VPU就是一颗专门的视频处理单元芯片里集成的“编解码专用员工”。你给它一份H.264码流它自己完成所有解码运算把YUV帧吐给你你要编码把原始YUV帧喂给它它把H.264码流给你。整个过程CPU只需要做“调度”和“搬运”这类轻量工作而不是参与像素级计算。用生活类比来说软编软解就像你让一个十项全能运动员既跑马拉松又去拧螺丝硬编硬解则是给这个运动员配了一个专用工具台他只需按一下按钮工序就自动完成了。所以结论很直接在RK3588这种带VPU的平台上做视频处理硬编硬解不是“可选项”而是“默认项”。CPU应该留给业务逻辑、留给AI推理不该去干像素苦力。1.2 FFMedia与MPP、RGA的分工逻辑要理解FFMedia得先知道它底下站着谁。Rockchip MPPMedia Process Platform是瑞芯微官方提供的媒体处理用户态库负责直接跟VPU驱动打交道封装了硬件编解码、JPEG编解码等能力。但MPP的接口是偏底层的你要自己管理输入输出缓冲、处理帧率控制、处理codec的数据格式开发效率很低。FFMedia做的事情就是把MPP包进FFmpeg的框架里。这样一来你不需要学MPP那套复杂的API只要按FFmpeg的习惯创建AVCodecContext、调avcodec_send_frame和avcodec_receive_packet就行。FFMedia在内部帮你完成了AVFrame/AVPacket和MPP内部的MppFrame/MppPacket之间的转换。这就是h264_rkmpp这个编码器/解码器的来历。另外还有一个容易被忽略的组件RGARaster Graphic Acceleration。它是RK平台上的2D硬件加速单元负责图像格式转换、缩放、旋转、裁剪。解码之后得到的NV12原始帧如果要转成RGB送显示、缩放后送NPU推理用CPU做会拖慢整条流水线用RGA做CPU占用几乎可以忽略。rockchip的FFmpeg分支也集成了rkrga相关的滤镜比如 scale_rkrga这样你在FFmpeg命令行里也能直接调用RGA能力。把这三层关系理清之后你就知道FFMedia的实际价值它把“用VPU编解码”这件事从底层开发降维成了普通FFmpeg开发。你的技术栈只需要懂FFmpeg不需要懂MPP内部细节。1.3 哪些场景真正需要这套方案不是所有项目都需要硬编硬解但以下场景基本属于“用了就真香”的类型。第一类是视频监控类。典型需求是拉取一路或多路RTSP摄像头流在板卡上做实时预览、录制或算法分析。用FFMedia硬解可以同时处理多路1080PCPU占用比纯软解低一个数量级。第二类是图传和直播推流。无人机图传、机器人第一视角、直播推流都需要实时把视频编码成H.264/H.265再发送。这类场景对延迟敏感要求编码不能拖后腿。硬编码的延迟通常在毫秒级到十几毫秒级而且不会占用CPU能保证主控系统稳定运行。第三类是边缘AI盒子。很多人拿RK3588做目标检测比如用yolov8跑检测。但摄像头采集回来的通常是一路H.264码流你必须先解码成YUV帧再缩放成模型需要的尺寸然后送给NPU。如果这整条链路都用CPU硬扛NPU算力还没用完CPU先成了瓶颈。用硬解RGA缩放CPU可以被释放出来跑后处理逻辑。第四类是录播一体机或视频会议终端。需要把采集到的HDMI或摄像头画面实时编码存储同时可能还要解码显示远端画面。这类设备普遍是多路并发硬编硬解几乎是刚需。2. 环境准备刷系统、确认VPU、编译FFMedia2.1 板子系统与基础依赖准备先说系统。RK3588上最常见的开发系统是Ubuntu20.04、22.04、24.04我都试过FFMedia这套方案在20.04和22.04上最稳定网上资料也最多。不管你用的是官方Ubuntu镜像还是自己移植的根文件系统底层原理差别不大这套教程通用。拿到板子之后第一步先更新软件源并安装编译FFMedia需要的基础依赖。在终端里执行sudo apt update sudo apt install -y build-essential \ libdrm-dev \ libx11-dev \ libxext-dev \ libxfixes-dev \ libomxil-bellagio-dev \ libx264-dev \ cmake \ gitlibdrm比较关键rockchip-ffmpeg在编译时处理DRM内存相关逻辑会用到建议提前装好。如果你后面还想用OpenGL显示或硬解转给GPU可以再加libegl1-mesa-dev和libgles2-mesa-dev。2.2 确认VPU设备是否正常工作编译前先确认你的板子VPU驱动是正常的不然编译半天最后跑不起来排查起来很痛苦。先看设备节点ls -l /dev/mpp_service正常情况下应该能看到类似/dev/mpp_service的字符设备。如果没有说明内核配置里MPP驱动没编进去或者系统镜像本身不带MPP支持。这时候优先检查内核配置确认CONFIG_ROCKCHIP_MPP_SERVICE这类选项有没有打开。再看内核日志dmesg | grep -i mpp能看到类似 “rockchip-mpp” 的初始化信息就说明驱动加载正常。如果这段日志都没有多半是设备树或者内核模块有问题。最后可以用一个最简单的命令验证VPU能不能干活直接让ffmpeg硬解一段小视频。不过这一步要等FFMedia编译完才能做所以先记着这个方法后面会用到。还有一个小经验有些板子的mpp_service设备节点权限是root且是600普通用户访问会报错最简单粗暴的做法是sudo chmod 666 /dev/mpp_service或者写一个udev规则让开机自动处理权限。后面在代码里跑通之后建议补上udev规则不然每次重启都要手动改权限。2.3 编译rockchip-ffmpegFFMedia的完整步骤下载源码建议直接拉rockchip-linux的官方仓库git clone https://github.com/rockchip-linux/ffmpeg.git cd ffmpeg如果直接拉GitHub觉得慢也可以用能找到的国内镜像或离线包版本逻辑是一样的。拉下来之后编译配置是关键。RK3588平台我建议这样configure./configure \ --target-oslinux \ --prefix/usr/local/ffmpeg-rk \ --enable-gpl \ --enable-version3 \ --enable-rkmpp \ --enable-rkrga \ --enable-libdrm \ --enable-opengl \ --enable-ffplay \ --enable-ffprobe \ --enable-ffmpeg \ --disable-x86asm解释几个关键点--enable-rkmpp必须开不开就没有h264_rkmpp解码器/编码器。--enable-rkrga必须开这是启用RGA滤镜的开关后面做NV12转RGB、缩放就靠它。--enable-libdrm配合MPP的DRM内存管理建议开。--enable-opengl如果后续要硬解显示到屏幕这个有用可以一并编上。--disable-x86asm因为是ARM平台x86优化汇编完全不适用不开会影响部分组件的编译。配置完成后make -j$(nproc) sudo make install编译时间取决于你的板子性能。直接在RK3588上编大概十几到二十分钟如果你用交叉编译环境时间会快很多但交叉编译的环境变量配置比较繁琐这个后面有机会单独展开。编译完验证一下/usr/local/ffmpeg-rk/bin/ffmpeg -decoders | grep rkmpp /usr/local/ffmpeg-rk/bin/ffmpeg -encoders | grep rkmpp如果能分别看到 h264_rkmpp、hevc_rkmpp 甚至 av1_rkmpp 相关的解码器以及 h264_rkmpp、hevc_rkmpp 编码器说明FFMedia编译成功了。注意如果你系统里原来装过Ubuntu自带的ffmpeg建议直接用/usr/local/ffmpeg-rk/bin/下的可执行文件避免和系统版本混用。你可以在/usr/local/bin下做一个软链接方便全局调用。3. H.264硬编码实操推流、直播和本地录制都能用3.1 编码前的参数选择逻辑硬件编码器和软件编码器的参数逻辑基本一致但硬编有它自己要注意的地方直接说结论。码率控制。H.264硬编码推荐用固定码率ABR或恒定质量模式CQP具体看场景。直播、网络传输场景用固定码率更稳1080P30通常给2Mbps到4Mbps本地录制存储场景可以用CQP画质更稳定但码率会波动。命令行里固定码率用-b:v 4MCQP用-qp 22这类参数。GOP大小。GOP就是两个关键帧之间的距离。直播场景建议设置成帧率的整数倍比如30fps给60也就是2秒一个IDR帧如果网络环境差可以把GOP缩短到1秒这样丢包后恢复更快但码率会有所上升。B帧。B帧能提升压缩率但会增加延迟。低延迟图传、视频通话场景直接把B帧关掉也就是-bf 0。大部分RK硬编码配置里B帧设置比较保守你显式指定0会更稳妥。Profile和Level。RK3588硬编码基本支持到High profile1080P建议设-profile:v high。Level可以直接交给编码器自动算也可以手动指定4.1或4.2取决于你的码率和分辨率。分辨率与帧率。VPU不是万能的8K编码虽然标称支持但实际项目里跑4K30或1080P60最稳。你要明白硬编码虽然不占CPU但还是占VPU算力的分辨率越高、帧率越高VPU负载越大建议按需配置别盲目上高规格。3.2 用FFMedia实现H.264硬编码的代码骨架命令行能跑通之后迟早要落到代码里。下面给一个C语言硬编码的最小骨架逻辑是读取NV12原始帧用h264_rkmpp编码成H.264码流写到文件里。#include stdio.h #include stdint.h #include libavcodec/avcodec.h int main() { const AVCodec *codec avcodec_find_encoder_by_name(h264_rkmpp); if (!codec) { fprintf(stderr, h264_rkmpp encoder not found\n); return -1; } AVCodecContext *ctx avcodec_alloc_context3(codec); ctx-width 1920; ctx-height 1080; ctx-time_base (AVRational){1, 30}; ctx-framerate (AVRational){30, 1}; ctx-pix_fmt AV_PIX_FMT_NV12; ctx-bit_rate 4 * 1000 * 1000; ctx-gop_size 60; ctx-max_b_frames 0; ctx-profile FF_PROFILE_H264_HIGH; if (avcodec_open2(ctx, codec, NULL) 0) { fprintf(stderr, open codec failed\n); return -1; } FILE *out fopen(output.h264, wb); AVFrame *frame av_frame_alloc(); frame-format ctx-pix_fmt; frame-width ctx-width; frame-height ctx-height; av_frame_get_buffer(frame, 32); AVPacket *pkt av_packet_alloc(); for (int i 0; i 300; i) { // 正常情况下这里应该读摄像头或解码器的NV12数据 // 测试时可以直接用av_frame_fill? 手动在data数组里填Y和UV frame-pts i; avcodec_send_frame(ctx, frame); while (avcodec_receive_packet(ctx, pkt) 0) { fwrite(pkt-data, 1, pkt-size, out); av_packet_unref(pkt); } } // 冲刷编码器 avcodec_send_frame(ctx, NULL); while (avcodec_receive_packet(ctx, pkt) 0) { fwrite(pkt-data, 1, pkt-size, out); av_packet_unref(pkt); } fclose(out); return 0; }有几个细节要注意输入格式用NV12。RK平台的硬编码器对NV12支持最好虽然你传YUV420P有时也能转但多一次转换就多一分CPU占用不如直接用对齐格式。av_frame_get_buffer(frame, 32)里的32是字节对齐数。NV12的宽高对齐在硬件编码里很重要1080P没问题但如果分辨率不是常规值比如1280x720这种还简单遇到特殊尺寸建议手动按16或32对齐。编码完成后必须冲刷一次编码器把缓冲区的帧吐出来不然文件结尾会少GOP数据的尾巴。3.3 命令行也能直接硬编从YUV到MP4再到RTSP代码骨架懂原理但日常调试我更多直接用命令行。最基础的YUV转MP4硬编命令/usr/local/ffmpeg-rk/bin/ffmpeg \ -s 1920x1080 \ -pix_fmt nv12 \ -r 30 \ -i input.yuv \ -c:v h264_rkmpp \ -b:v 4M \ -g 60 \ -bf 0 \ -profile:v high \ -f mp4 output.mp4如果采集设备是USB摄像头或者MIPI摄像头直接让ffmpeg读v4l2设备/usr/local/ffmpeg-rk/bin/ffmpeg \ -f v4l2 \ -video_size 1920x1080 \ -i /dev/video0 \ -c:v h264_rkmpp \ -b:v 4M \ -g 60 \ -bf 0 \ -f flv rtmp://192.168.1.100:1935/live/stream0对于图传或直播推流这个命令基本够用。我实测过一路1080P30硬编码推流CPU占用能控制在30%到50%左右注意这里面还包含v4l2采集、内存拷贝和网络发送的开销。如果去掉采集和推流单纯硬编码1080P30CPU占用可以更低。同样场景用x264软编CPU直接被打到300%以上对比非常明显。我建议你拿到板子后先跑这个软硬对比实验同一个YUV文件分别用libx264和h264_rkmpp编码top命令记录CPU占用再看输出文件大小和画面质量。做完这个实验你对硬编的价值会非常有体感。4. H.264硬解码实操从RTSP拉流到YUV输出4.1 解码链路与数据格式硬解码的链路和编码正好相反。输入是H.264的AVPacket经过h264_rkmpp解码器输出是AVFrame。重点在于输出的像素格式。Rockchip MPP硬件解码器最擅长的输出格式是NV12也就是YUV420SPY平面单独一份UV交错存放。你在FFmpeg里设置ctx-pix_fmt AV_PIX_FMT_NV12后解码器会尽量直接输出NV12避免额外的格式转换。另一个需要了解的是DRM内存。MPP解码时使用的不一定是普通内存可能是DRM/ION分配的内存。在FFmpeg框架里这通常体现为帧的data指针指向映射后的用户态地址。如果只是普通取帧数据不需要关心底层但要追求零拷贝直接把解码后的DRM buffer送到显示或NPU那就要用到Rockchip特定的硬件缓冲管理复杂度会上去。新手阶段建议先走普通内存拷贝性能足够满足大部分场景。4.2 硬解码代码骨架解码的代码骨架和编码对称核心就是avcodec_send_packet和avcodec_receive_frame的循环。下面是从H.264文件解码成NV12 YUV文件的例子#include stdio.h #include stdint.h #include libavcodec/avcodec.h int main() { const AVCodec *codec avcodec_find_decoder_by_name(h264_rkmpp); if (!codec) { fprintf(stderr, h264_rkmpp decoder not found\n); return -1; } AVCodecContext *ctx avcodec_alloc_context3(codec); ctx-pix_fmt AV_PIX_FMT_NV12; if (avcodec_open2(ctx, codec, NULL) 0) { fprintf(stderr, open codec failed\n); return -1; } FILE *in fopen(input.h264, rb); FILE *out fopen(output.yuv, wb); AVPacket *pkt av_packet_alloc(); AVFrame *frame av_frame_alloc(); uint8_t buf[512 * 1024]; int len; while ((len fread(buf, 1, sizeof(buf), in)) 0) { pkt-data buf; pkt-size len; avcodec_send_packet(ctx, pkt); while (avcodec_receive_frame(ctx, frame) 0) { // NV12: Y平面和UV平面分开输出 fwrite(frame-data[0], 1, frame-linesize[0] * frame-height, out); fwrite(frame-data[1], 1, frame-linesize[1] * frame-height / 2, out); av_frame_unref(frame); } } // 冲刷解码器 avcodec_send_packet(ctx, NULL); while (avcodec_receive_frame(ctx, frame) 0) { fwrite(frame-data[0], 1, frame-linesize[0] * frame-height, out); fwrite(frame-data[1], 1, frame-linesize[1] * frame-height / 2, out); av_frame_unref(frame); } return 0; }这里有一个新手容易踩的坑linesize不等于width。硬件解码器输出的帧为了提高访问效率往往会对每一行做对齐实际一行的字节数会比图像宽度多。写入YUV文件时绝对不能一行一行按width写要按linesize写完整行数据。上面的代码里我直接用linesize乘高度能保证写出来的YUV文件没有花屏和错位。如果你是读取MP4这类容器而不是裸H.264文件建议用avformat打开输入、avcodec参数从容器流里拷贝也就是走avformat_find_stream_info、avcodec_parameters_to_context这条路。裸流用parser切帧也可以但要处理H.264的起始码和SPS/PPS稍微麻烦一点。4.3 用硬件解码拉RTSP低延迟参数调优实际项目里解码对象很少是本地文件更多是RTSP摄像头流。命令行硬解RTSP最简形式/usr/local/ffmpeg-rk/bin/ffmpeg \ -rtsp_transport tcp \ -max_delay 100000 \ -probesize 32 \ -fflags nobuffer \ -i rtsp://192.168.1.64:554/stream0 \ -c:v h264_rkmpp \ -f rawvideo \ -pix_fmt nv12 output.yuv几个参数的经验-rtsp_transport tcp局域网内优先用TCPUDP虽然延迟略低但容易丢包导致花屏。想稳就用TCP。-fflags nobuffer告诉ffmpeg不要做深缓冲降低首帧延迟。-probesize 32因为已知是RTSP的H.264流不需要花太多时间去探测格式提高启动速度。-max_delay 100000限制缓冲包的时长单位是微秒100000就是100ms。数值越大越稳但延迟越高越小延迟越低但网络抖动时更容易卡顿自己按实际网络调整。我实测拉一路1080P H.264的RTSP摄像头流硬解码CPU占用大概在10%到20%之间而且这个占用主要花在内存拷贝和RTSP协议解析上解码本身几乎不占CPU。同样一路流用h264软解CPU占用轻松上到60%以上。多路摄像头场景下这个差距会被指数级放大硬解几乎是唯一可行方案。5. 常见问题与排查技巧实录5.1 编译和初始化踩坑编译方面最常见的报错是找不到rkmpp头文件和库。原因通常是系统里没有装rockchip-mpp的开发包或者configure时没有指定mpp的路径。RK3588的Ubuntu官方镜像一般自带MPP但如果你是精简根文件系统可能没有。建议先把rockchip-mpp源码编一遍并安装到系统再回头编FFMedia顺序不要搞反。另一个高频问题是我前面提到过的设备节点权限。如果你运行ffmpeg时出现类似 “mpp_create_group failed” 或 “open mpp_service failed” 的错误第一反应去查/dev/mpp_service的权限。还有一点很多人在代码里用avcodec_find_decoder(AV_CODEC_ID_H264)去找解码器结果找到的是软解码器而不是h264_rkmpp。FFmpeg的默认解码器查找顺序不保证返回硬解你必须显式用名字查找avcodec_find_decoder_by_name(h264_rkmpp)5.2 花屏、丢帧、时间戳问题硬解码花屏问题往往不在解码器而在输入码流。常见原因是RTSP传输丢包导致H.264码流里出现了残缺的NALU解码器输出就会花屏或者直接丢帧。排查方法是先用软解跑一遍同样的流如果软解也花屏那铁定是码流问题别去怀疑硬解。帧率不对、线程不同步这类问题多半是时间戳没有正确传递。硬编场景里你喂给编码器的AVFrame的pts必须是递增的否则封装进MP4后播放器会乱跳。硬解场景如果拿到的AVFrame的pts是NOPTS说明容器层的解析没把时间信息传过来需要自己在外部维护帧序。首帧延迟大主要看两点一是GOP长度IDR帧间隔越长设备启动后要等关键帧才能出图二是ffmpeg的探测缓冲用-probesize和-analyzeduration控制默认值偏大实时流场景要调小。5.3 解码后图像去哪NV12转RGB与送显解码得到NV12后最常见的需求是转RGB送显示或者缩放后送NPU推理。如果用CPU做NV12转RGB1080P每帧大概要消耗不少CPU时间视频只有一路还能接受两路以上就开始吃力。更推荐用RGAFFMedia编译时如果开了--enable-rkrga可以使用rkrga滤镜/usr/local/ffmpeg-rk/bin/ffmpeg \ -c:v h264_rkmpp \ -i input.h264 \ -vf scale_rkrga1920:1080:formatrgb0 \ -f rawvideo output.rgb这个滤镜内部走RGA硬件CPU占用非常低。如果你的业务是“解码后直接送NPU推理”建议把解码输出的NV12直接通过RGA缩放到模型输入尺寸转成RGB并做数据布局调整最后一次性拷给NPU。这样做整条流水线CPU占用都极低yolov8这类推理业务才能跑得流畅。5.4 问题速查表现象大概率原因解决方案ffmpeg看不到h264_rkmpp编译时没开--enable-rkmpp重新configure并编译avcodec_open2报mpp相关错误/dev/mpp_service权限或驱动异常查看设备节点、dmesg、chmod 666硬解码花屏输入码流丢包/NALU不完整先用软解对比优化RTSP传输为TCP编码MP4播放器不识别GOP、profile或level不合理显式设置profile high、bf 0、gop 60延迟过大缓冲过多或探测时间过长加-fflags nobuffer、调小probesize解码输出帧数据错位忽略了linesize对齐写入时按linesize逐行处理RGA滤镜找不到编译没开--enable-rkrga重新编译FFMedia编码帧率低于预期分辨率/帧率超VPU能力范围降低到4K30或1080P60检查输入帧率最后再分享一个我个人的实操体会。刚开始接触RK3588硬件编解码的时候我最大的问题是迷信“代码”。其实最快的路径是先把ffmpeg命令行跑通用命令行验证板子的VPU、验证码流、验证参数然后再把同样的参数迁移到C/C代码里。命令行能跑通说明驱动、权限、库都OK这时候再调试代码就只剩下业务逻辑。编码那块也是一样先用软解软编确定源没问题再切硬编硬解出了问题才有方向。至于后续扩展我最近正在做的一件事就是把解码后的NV12帧直接通过RGA缩放再送给RK3588的NPU跑yolov8检测。做完之后整个链路CPU占用都很低视频流水线几乎不抢AI算力的资源。等我把这套完整流水线再打磨得顺一点后面单独整理一篇出来到时候再跟你细聊那些具体的坑。
返回列表