本系列第 1 篇 | 2026-05 | 标签:STM32MP2、GStreamer、V4L2、硬件编解码
一切从最小验证开始:不写业务逻辑,先用 GStreamer 把 STM32MP25 上的
Verisilicon/Hantro VPU 跑通 ------ 能编码、能解码、能封装成文件。
这一篇记录跑通过程,以及三个不报错、只是结果不对 的安静坑:
裸流 VLC 打不开、NV12 带 padding、"JPEG 那条没问题"推不出"H264 也没问题"。
硬件形态:两个 V4L2 设备
VPU 驱动在内核侧是 drivers/media/platform/verisilicon/,对外呈现为两个独立的
V4L2 mem2mem 实体:
| 实体 | 方向 | 输入 → 输出 |
|---|---|---|
| venc(编码) | 原始帧 → 码流 | NV12/YUY2/UYVY/I420/RGB16/BGRx → H264/VP8/JPEG |
| vdec(解码) | 码流 → 帧 | H264/VP8/JPEG → NV12/NV16,上限 1080p(JPEG/编码可 4K) |
它们各占一个 /dev/videoN 和一个 /dev/mediaN。注意:号不是固定的 ,
每次开机都可能变 ------ 这是下一篇的主题,本篇先按下不表。
关键点:H264 走的是 stateless (无状态)接口,码流格式是"Parsed Slice Data"
------ pipeline 里必须 有 h264parse 把码流整理成驱动要的格式。漏掉它不会报
"缺元件",而是协商直接失败。
第一条编码管线:先不写代码
300 帧彩条、720p、CBR 2Mbps、GOP=30:
sh
gst-launch-1.0 -e videotestsrc num-buffers=300 ! \
video/x-raw,width=1280,height=720,format=NV12,framerate=30/1 ! \
v4l2slh264enc bitrate=2000000 rate-control=cbr keyframe-interval=30 ! \
h264parse ! mp4mux ! filesink location=/tmp/test.mp4
v4l2slh264enc(stateless encoder)的关键属性全走 GStreamer 属性,没有
extra-controls:
bitrate(bit/s)、rate-control=cbr|cqpkeyframe-interval(GOP,帧数)- 输入 caps 原生接受
RGB16
最后一条在本项目里价值千金,后面几篇会反复用到:DCMIPP 摄像头主路只输出 RGB
(RGB565/RGB888),不出 YUV;而编码器原生吃 RGB16 ------ 所以摄像头可以直喂编码器,
不需要 videoconvert,省掉一次整帧 CPU 颜色转换。在双核 A35 上,"省一次 640×480
的逐像素转换"就是省出一个核的三分之一。
接摄像头:第一条真实管线
sh
# 设备号按实体名查(为什么不能写死,见第 2 篇)
M=$(for m in /dev/media*; do media-ctl -d $m -p 2>/dev/null | grep -q 'driver *dcmipp' && echo $m; done | head -1)
CAM_NODE=$(media-ctl -d $M -e dcmipp_main_capture)
gst-launch-1.0 -e v4l2src device=$CAM_NODE io-mode=dmabuf ! \
video/x-raw,format=RGB16,width=640,height=480,framerate=30/1 ! \
v4l2slh264enc bitrate=2000000 ! h264parse config-interval=1 ! mpegtsmux ! \
filesink location=/tmp/cam.ts
三个细节:
- 必须显式给 caps 。不给的话协商出非法分辨率,报
Wrong width or height
/Invalid format------ 这算报错的,不算安静坑。 io-mode=dmabuf:见下一节。h264parse config-interval=1:让 SPS/PPS 随关键帧重复发送。第 6 篇会讲丢掉它
引发的"中途接入黑屏",这里先记住:只要有人可能中途开始看流,参数集就必须
周期性出现。
dmabuf:零拷贝不是玄学,是少两次 memcpy
io-mode=mmap 时数据路径是:DCMIPP 的 DMA buffer → 拷到用户态 → 再拷进 VPU
输入队列。640×480@30 下每帧 ~600KB × 2 次 memcpy,白烧 CPU。
io-mode=dmabuf:DCMIPP 的 DMA buffer 以 dmabuf fd 直接挂到 VPU 输入队列,
零拷贝。这个思想后面还会以另一种形式出现(第 5 篇:共享内存环里传 fd 引用而不是
像素,CPU 从 47.5% 干到 1.7%)。
安静坑一:裸流 .h264,VLC 打不开
把编码输出直接写文件(.h264,Annex-B 裸流),VLC 报"无法解复用"或秒退。
第一反应往往是怀疑码流坏了 ------ 其实码流完全合法,可以用三重方式证实:
od 看 NAL 起始码、手解 SPS 头、板上硬解回放。
真正原因:.h264 是裸 ES,无容器无时间戳,VLC 对 raw-ES 的兜底探测很弱。
解法任选:
sh
mux 封 TS 给 VLC 直接播 # 推荐,MPEG-TS 兼容性最好
ffplay /tmp/cam.h264 # 或用 ffplay 播裸流
教训:验证码流要用对工具,别让播放器的探测能力背锅。
应用骨架:appsink 收尾
封装好容器后不落盘、交给程序处理的最小骨架(appsink 回调示意):
c
static GstFlowReturn on_new_sample(GstAppSink *sink, gpointer user) {
GstSample *s = gst_app_sink_pull_sample(sink);
GstBuffer *buf = gst_sample_get_buffer(s);
GstMapInfo map;
if (gst_buffer_map(buf, &map, GST_MAP_READ)) {
fwrite(map.data, 1, map.size, out_fp); /* 这里换成推流/转发即可 */
gst_buffer_unmap(buf, &map);
}
gst_sample_unref(s);
return GST_FLOW_OK;
}
要接 RTMP/WebRTC/RTSP,替换回调里这一处就行 ------ 第 4 篇的 WebRTC 推流正是从
这里长出来的。
解码与最阴险的坑:NV12 带 padding
解码管线对称简单,输入容器按扩展名分流(.mp4→qtdemux、.ts→tsdemux、
.h264→直接 h264parse):
sh
gst-launch-1.0 filesrc location=/tmp/cam.ts ! tsdemux ! h264parse ! \
v4l2slh264dec ! videoconvert ! waylandsink # 硬解上屏
然后是这个项目踩过最安静的坑。我们想当然地按紧凑公式 w*h*3/2 切解码输出的
NV12 帧,存出来的文件用 ffplay -f rawvideo -s 1280x720 播,花屏。
实测对比(720p):
| 字节数 | |
|---|---|
紧凑值 1280×720×3/2 |
1382400 B |
v4l2slh264dec 实际每帧 |
1612832 B(+230432 B) |
原因:v4l2slh264dec 的输出 buffer 带 stride / 高度对齐填充。按紧凑公式切帧,
拿到的是一张"斜图"。
更阴险的地方在于:同一颗 VPU 上,另一个解码器行为不一样 ------ 我们此前用
v4l2jpegdec 做过 JPEG 解码验证,输出就是精确的紧凑 1382400 B。于是形成了
错误归纳:"这条链路这么写没问题"。正确结论是:
"JPEG 解码器输出紧凑" 推不出 "H264 解码器也紧凑"。
换一个 IP/驱动路径,所有关于内存布局的假设都要重新验证。
正确姿势二选一:
sh
# ① 让 GStreamer 自己处理(肉眼确认内容,最省事):
... ! v4l2slh264dec ! videoconvert ! jpegenc ! multifilesink location=/tmp/dec_%03d.jpg
# ② 自己取紧凑数据:gst_buffer_get_video_meta() 按 stride[]/offset[] 逐行拷
⚠ 用 ① 时别把慢当成硬解性能:videoconvert + jpegenc 是软件元件,686 帧在 A35 上
跑了 7 分半(≈1.5 fps),慢的是软 JPEG 编码,硬解本身毫秒级。
新板子到手的第一件事:能力探测
写一个探测脚本做两件事:枚举编解码设备能力 + 摄像头各格式直采测试,输出一份
info 文件。思路核心:
sh
# 枚举编码器能力(解码同理)
v4l2-ctl -d /dev/videoN --list-formats-ext
# 摄像头某格式能不能直采?开 10 帧试试,成功与否都记下来
v4l2-ctl -d $CAM_NODE --set-fmt-video=width=640,height=480,pixelformat=RGBP \
--stream-mmap --stream-count=10
后来排查过的很多问题(下一篇的格式钉错节点、aux/dump 开反)都能在这份探测输出里
提前发现。新硬件先摸清能力边界,再写业务代码 ------ 这条纪律贯穿整个系列。
小结
- stateless 编码器必须配
h264parse;中途接入的场景必须config-interval - 编码器吃 RGB16,摄像头直喂,免一次整帧软转
- dmabuf 零拷贝 = 省两次整帧 memcpy
- 裸流打不开先怪工具再怪码流
- 解码输出的内存布局要实测,同类设备的经验不可平移
下一篇:02 · 摄像头这条路坑更多
------ 设备号每次开机都变、media link 默认关闭、STREAMON 报 -22 但所有 S_FMT 全成功。