适配 buildroot (树梅派4B) 的 gStreamer MJPG 图传方案

可行方案

流路径: RPi4(Buildroot, 内核 6.12.61)→ Ubuntu PC,RTP/UDP over WiFi

编码格式: MJPEG(VPU 硬件 JPEG 编码器)。H.264 在当前内核/固件组合下不可用——见问题 #6。

发送端流水线(RPi4)

gst-launch-1.0 v4l2src device=/dev/video0 ! \
    videoconvert ! videorate ! \
    video/x-raw,format=NV12,width=640,height=480,framerate=30/1 ! \
    v5l2jpegenc ! rtpjpegpay ! \
    udpsink host=192.168.1.100 port=5000 sync=false async=false

接收端流水线(Ubuntu PC)

gst-launch-1.0 udpsrc port=5000 caps="application/x-rtp" buffer-size=0 ! \
    rtpjitterbuffer latency=100 drop-on-latency=true do-retransmission=false ! \
    rtpjpegdepay ! jpegdec ! videoconvert ! autovideosink sync=false

Buildroot 配置清单

组件 menuconfig 路径
/dev 管理方式 System configuration → /dev management → Dynamic using devtmpfs + eudev
eudev Target packages → Hardware handling → eudev
gstreamer1 + 工具 Target packages → Audio and video apps → gstreamer 1.x
videoconvertscale .. → gst1-plugins-base → base-videoconvertscale
videorate .. → gst1-plugins-base → base-videorate
v4l2 .. → gst1-plugins-good → v4l2
v4l2-probe .. → gst1-plugins-good → v4l2-probe (m2m)
rtp + rtpmanager + udp .. → gst1-plugins-good
videoparsersbad .. → gst1-plugins-bad → videoparsersbad(可选,为 h264parse 准备)
CMA(64 MB) Kernel → fragment:board/raspberrypi4-64/linux-fragment-v4l2-codecs.conf
cmdline cma=128M board/raspberrypi/cmdline.txt

内核 fragment 内容

CONFIG_CMA_SIZE_MBYTES=64

遇到的问题与解决过程

问题 #1:v4l2codecs 插件依赖 udev

现象:v4l2codecs 选项在 gst1-plugins-bad 中不可见。

根因:v4l2codecs GStreamer 插件依赖 libgudev,而 libgudev 依赖 eudev。没有 udev 的话,该选项会被隐藏。

解决:必须先在 System configuration 中将 /dev management 设为 Dynamic using devtmpfs + eudev,再在 Hardware handling 中启用 eudev。两者都设置后,v4l2codecs 才会出现。(注:后来发现 v4l2codecs 是无状态解码器接口,实际并未使用——有状态编码器 v4l2h264enc 来自 gst-plugins-good。)


问题 #2:eudev 无法勾选

现象:Hardware handling 下显示的是 "eudev needs eudev /dev management" 这条注释,而非可勾选的选项。

根因:必须先设置 /dev management 方式,eudev 包才会变为可选。

解决:两步流程:(1) System configuration → /dev management → Dynamic using devtmpfs + eudev,(2) 然后 Hardware handling → eudev 变为可勾选。


问题 #3:内核 fragment 目录选错

现象:不确定该用 board/raspberrypi/ 还是 board/raspberrypi4-64/ 下的 fragment 文件。

解决:检查了 BR2_ROOTFS_OVERLAY 及其他 board 路径——全部使用 board/raspberrypi4-64/。fragment 路径应保持一致。


问题 #4:找不到 h264parse 元素

现象:WARNING: erroneous pipeline: no element "h264parse"

根因:h264parse 在 gst1-plugins-bad → videoparsersbad 中,该选项未启用。

解决:在 menuconfig 中启用 videoparsersbad,或临时移除 pipeline 中的 h264parse 进行测试。


问题 #5:/dev/video0 不存在

现象:Cannot identify device '/dev/video0': No such file or directory

根因:USB 摄像头没插到 RPi 上。

解决:插上摄像头。如有需要,运行 modprobe uvcvideo。


问题 #6:H.264 硬件编码器失败(内核报错)

现象:

bcm2835-codec: bcm2835_codec_start_streaming: Failed enabling i/p port, ret -3
WARNING: at vb2_start_streaming+0xec
ERROR: Failed to process frame. Maybe due to not enough memory or failing driver

排查过程:

  1. 第一个猜想:缺少 CONFIG_MEDIA_CONTROLLER_REQUEST_API=y。

    • 结论:检查了内核 6.12 的 Kconfig——此选项不存在。在 6.12 中它已合并进 CONFIG_MEDIA_CONTROLLER=y。内核 fragment 中设定此项被静默忽略。
  2. 第二个猜想:CMA(连续内存分配器)太小。默认只有 5 MB。

    • 结论:在内核命令行中添加了 cma=128M。通过 /proc/meminfo 确认 CMA 已扩至 128 MB。报错依旧。
  3. 第三个猜想:GPU 显存(gpu_mem)太小,VPU 编码器不够用。

    • 结论:增加到 256 MB。报错依旧。
  4. 第四个猜想:摄像头格式不匹配。

    • 结论:加上了 videoconvert 和显式的 format=NV12。报错依旧。
  5. 隔离测试:用 videotestsrc 替换摄像头。编码器依旧失败——问题出在编码器硬件路径本身,与摄像头无关。

  6. 隔离测试 #2:尝试 v4l2jpegenc(JPEG 硬件编码器)。它可以正常工作。 这证明 ARM 与 VPU 之间的 MMAL/VCHIQ 通信路径是通畅的。

  7. 确认根因:vchiq_mmal_port_enable() 从 VPU 的 ril.video_encode 组件收到 MMAL_MSG_STATUS_EINVAL(-3)。H.264 编码器固件组件拒绝了端口启用请求。这是内核 6.12 与 RPi 固件不兼容问题,仅影响 H.264 编码器。JPEG 编码器(ril.image_encode)不受影响。

变通方案:改用 MJPEG(硬件加速 JPEG 编码)。代价:带宽更高(~5-10 Mbps vs H.264 的 ~1-2 Mbps),但延迟更低(无 GOP,无 B 帧),且完全由硬件加速。

长期修复:将 rpi-firmware 或内核升级到兼容版本。


问题 #7:接收端防火墙拦截 UDP

现象:发送端流水线在 RPi 上正常运行,但 PC 端接收流水线无画面。tcpdump 显示零 UDP 数据包到达。

根因:Ubuntu 的防火墙(iptables/ufw)拦截了 UDP 5000 端口。

解决:

sudo iptables -A INPUT -p udp --dport 5000 -j ACCEPT

问题 #8:RTP 能力集限制过严

现象:UDP 数据包已到达 PC(tcpdump 已确认),但接收流水线无画面弹出。

根因:接收端的 caps 指定了 payload=26 或 encoding-name=JPEG,但 RPi 的 rtpjpegpay 可能协商了不同的 payload type。

解决:使用 caps="application/x-rtp",不加任何 payload 限制,让 rtpjpegdepay 动态协商。


问题 #9:手机热点 WiFi 抖动

现象:RPi 到 PC 的 ping 延迟剧烈波动(5ms → 3760ms),偶有丢包。

根因:手机热点使用 2.4GHz WiFi。RPi 和 PC 都在 WiFi 上,每个数据包要竞争两次无线信道。

缓解措施:

  • 接收端加 rtpjitterbuffer latency=100,吸收中等程度的网络抖动
  • 加 drop-on-latency=true,丢弃已过时的帧
  • udpsrc 加 buffer-size=0,避免内核 socket 缓冲
  • 通过 videorate 降低帧率(framerate=15/1)以减少带宽
  • MJPEG 天然健壮——每帧独立,无 GOP 依赖

问题 #10:摄像头不支持非原生帧率

现象:直接在 v4l2src 上使用 framerate=15/1 时,出现 streaming stopped, reason not-negotiated (-4)。

根因:USB UVC 摄像头仅支持 30fps 原生输出。caps 过滤器试图强制摄像头输出 15fps,但摄像头做不到。

解决:在 videoconvert 和 caps 过滤器之间加 videorate 元素。videorate 通过丢帧/复制帧来达到目标帧率,而非要求摄像头改变其原生输出。

流水线顺序:v4l2src → videoconvert → videorate → capsfilter → v4l2jpegenc


问题 #11:v4l2jpegenc 没有 quality 属性

现象:WARNING: erroneous pipeline: no property "quality" in element "v4l2jpegenc"

根因:V4L2 编码器暴露的是 V4L2 控制接口,而非 GStreamer 属性。JPEG 压缩质量通过 V4L2_CID_JPEG_COMPRESSION_QUALITY 控制字(CID 0x00990a67)设置,GStreamer 可能不会将其暴露为命名属性。

解决:使用默认质量。如需控制带宽,通过 videorate 调整帧率即可。


问题 #12:摄像头不支持 320×240 分辨率

现象:请求 320×240 分辨率时出现同样的 not-negotiated (-4) 错误。

根因:USB 摄像头仅支持特定分辨率(可能只有 640×480 及以上)。

解决:保持 640×480 分辨率。如需更小的画面尺寸,可使用 videoscale。


问题 #13:cmdline.txt 源文件路径错误

现象:修改了 board/raspberrypi4-64/cmdline.txt,但重新构建的镜像中改动并未生效。

根因:Buildroot 配置中 BR2_PACKAGE_RPI_FIRMWARE_CMDLINE_FILE="board/raspberrypi/cmdline.txt"——实际源文件是 board/raspberrypi/ 下的,不是 board/raspberrypi4-64/ 下的。

解决:改为修改 board/raspberrypi/cmdline.txt。


经验总结

  1. 隔离问题:用 videotestsrc 替代摄像头、分别测试编码器和解码器、用 tcpdump 确认数据是否真的在传输。

  2. 内核版本很重要:内核 6.12 修改了 Media Controller Request API 的实现方式。先确认 Kconfig 选项是否还存在,再假定它需要被设置。

  3. V4L2 编码器的属性名不同于软件编码器:gop-size、quality、bitrate 并非通用属性。先用 gst-inspect-1.0 查看可用属性。

  4. CMA 对 VPU 至关重要:默认 5 MB 远远不够。视频编码需要 64-128 MB。

  5. MJPEG 是 H.264 的低延迟可行替代方案:全关键帧、无 GOP 伪影、RPi 硬件加速。带宽虽高,但在局域网中无关紧要。

  6. WiFi 是视频流的最大敌人:手机热点的 2.4GHz 频段可产生数秒级别的抖动尖峰。强烈建议使用有线网络或 5GHz WiFi 以保证稳定传输。

posted @ 2026-05-26 13:50  BorisDimitri  阅读(58)  评论(0)    收藏  举报