手里有台 RK3588 的板子(Rockchip EVB,型号 ztl A588),自带一路 HDMI 输入,由内核的 rk_hdmirx 驱动提供,V4L2 节点是 /dev/video52。想法很简单:把台式机的画面接进这块板子,再用板子上的 OBS 采集、录制或推流,等于白捡一张 4K 采集卡。
现实是 OBS 里选完设备,日志直接甩出 Unable to set input -1,采集源永远黑屏。这篇文章把整个排查与修复过程记下来:根因在哪、为什么必须绕一层虚拟摄像头、以及顺手修掉的两个坑(Windows 每 6 秒卡顿、音频恢复后采集不动)。
一、根因:OBS 的 v4l2 插件和这块板子对不上
OBS 30.0.2 自带的 linux-v4l2 采集插件,采集普通 USB 采集卡一直很正常,但在这块板子上有两个硬性冲突:
- 驱动没有 input 概念。
rk_hdmirx不实现VIDIOC_G_INPUT/VIDIOC_S_INPUT,两个 ioctl 都返回ENOTTY。而 OBS 的v4l2_init()把v4l2_set_input()当成必须成功的第一步,失败即中止,于是必然走到Unable to set input -1。 - 它只有多平面能力。该设备只暴露
V4L2_CAP_VIDEO_CAPTURE_MPLANE。系统里的libv4l-mplane.so会把能力位补成普通采集设备,所以它能出现在 OBS 的设备下拉里——看着有戏,实际还是被第一条拦住。
另外还有一个跟驱动无关的坑:在 OBS 里添加「视频采集设备」,或者打开这个源的属性对话框,glibc 会报 free(): invalid pointer,随即 SIGABRT(apport 记录 Signal 6)。这个崩溃实测与设备枚举无关——在单进程里复刻 OBS 的设备枚举跑了 1880 次都不崩,崩的是属性对话框的构建/销毁路径。规避办法只有一条:别让这个源的属性对话框以「空设备」状态保存。
二、思路:OBS 挑食,那就喂它一个它认识的设备
既然 OBS 只认普通 V4L2 采集设备,那就造一个:用 v4l2loopback 建一个虚拟摄像头 /dev/video10,再用 ffmpeg 把 /dev/video52 的画面原样灌进去,OBS 采集 /dev/video10。全程 NV12 透传,不转码不缩放。
这条链路的好处是零编码开销,代价是多占一个 CPU 核(3840x2160 NV12 约 60fps,实测约 1 核)。
2.1 手工编译 v4l2loopback 0.12.7
厂商内核拒绝 dkms 安装,只能手工编译。落盘位置如下:
# 模块
/lib/modules/5.10.198/extra/v4l2loopback.ko
# 自动加载
/etc/modules-load.d/v4l2loopback.conf
# 模块参数
/etc/modprobe.d/v4l2loopback.conf
options v4l2loopback video_nr=10 card_label="HDMI-IN" max_buffers=8编译时的小坑:厂商内核的构建脚本调用的是
python,而这台机器上只有python3,需要临时放一个python→python3的 PATH 垫片才能编过。
2.2 桥接服务
ffmpeg -f v4l2 -input_format nv12 -i /dev/video52 -pix_fmt nv12 -f v4l2 /dev/video10但直接把它写成 systemd 单元会出事,所以外面包了一层脚本 /usr/local/bin/hdmi-in-bridge.sh,单元是 hdmi-in-bridge.service。
2.3 为什么必须包一层:无信号时的重启风暴
关键场景是:HDMI 线插着,但源端停止输出——比如 Windows 按 Win+P 切到「仅电脑屏幕」,或者干脆关掉对这块屏的输出。这时热插拔线(HPD)还是高,但 TMDS 时钟没了,驱动起不了流,ffmpeg 会在 1 秒内以 VIDIOC_DQBUF: Invalid argument 退出。
如果单元直接写 Restart=always,就变成每 3~4 秒重启一次的风暴。每次重启都会触发一次 hdmirx 流启停、一次 12 MB 的 dma_alloc_coherent 失败和一堆内核日志。修复前沿线实测:
- 内核日志 147 条/60 秒
- ffmpeg 启动 19 次/60 秒
脚本的做法是把「等」和「起」分开:
- 先等
/dev/video52和/dev/video10就绪; - 再等信号锁定,用两个独立判据并要求两者一致:
/sys/kernel/debug/hdmirx/status里是否还残留Unlock,以及v4l2-ctl -d /dev/video52 --get-dv-timings是否成功(未锁定时返回ENOLCK)。debugfs 那次是纯文件读、不碰驱动,所以无信号期间不产生任何内核日志; - 锁定后再延迟 1 秒才起 ffmpeg;
- ffmpeg 退出后按退避重试:连续短命运行就 3s → 6s → … → 30s,稳定运行满 10 秒后回落到 3s。
修复后同样是「无信号」状态:内核日志降到 36 条/70 秒(全部来自驱动自身的周期重试,约每 5.8 秒 3 条),ffmpeg 启动 0 次,dma_alloc 失败 0 次,服务稳定停在 active (running) 的等待态。当前脚本在无信号时会保持一个占位生产者:有源端 5V 输出绿色,没有源端供电就输出黑色。
| 场景 | HDMI 热插拔 | 源端信号 | 修复前 | 修复后 |
|---|---|---|---|---|
| 复制/扩展模式(正常采集) | 高 | 锁定 | 正常 | 正常 |
| 仅电脑屏幕/关掉该屏输出 | 高 | 无 | 每 3~4 秒重启风暴 | 静默等待 |
| 拔掉 HDMI 线 | 低 | 无 | 驱动不重试,本就安静 | 同左 |
三、OBS 里的正确用法(顺序很重要)
桥接服务跑起来之后,OBS 这边其实很简单:
- 确认服务在跑:
systemctl status hdmi-in-bridge。看到active (running)、日志里是no locked HDMI-IN signal, waiting,说明服务正常,只是源端此刻没送视频;源端一开始送,1~3 秒内会自动起流,不用手工干预。 - OBS → 源 → 添加 → 视频采集设备 → 设备下拉选
HDMI-IN (platform:v4l2loopback...)。 - 分辨率/帧率保持默认,跟随设备(3840x2160 NV12 60fps)。
- 缩放交给 OBS 自己做(Panfrost 渲染,开销很小)。
必须先起桥接,再起 OBS。
/dev/video10在没有写入端时是打不开的,OBS 会报Unable to open device和Initialization failed, errno: 无效的参数。而 OBS 对采集源只在启动时初始化一次,失败后不会自动重试,于是整个会话里这个源都是黑的,界面上看不到任何输出。已经开着 OBS 而源是黑的,就重启 OBS——不要右键打开属性对话框去「修」,那是已知的崩溃点。
音频是另一条链路:HDMI-IN 的音频不在 v4l2loopback 里(它只有视频),而是 ALSA 卡 rockchip,hdmiin,在 OBS 里用「音频输入采集 (ALSA)」选它。要注意:若 HDMI 源当前没有送音频(驱动控制项 audio_present 为 0),打开该设备会报「没有那个设备」,等源端出声音后再试。
四、进阶:Windows 端每 6 秒卡两下,也得修
桥接脚本只能让服务自己安静下来,管不了内核。2026-10-08 发现:源端关闭对采集屏的输出、但保持 HDMI 5V 时,原驱动失锁后会调用 hdmirx_plugout(),把 HPD 关掉约 1 秒再重新打开,实测两轮周期约 5.73 秒,HPD 软件状态与 CORE_CONFIG/SCDC_CONFIG 硬件控制寄存器一致。用户直接在 Windows 主显示器上看到每轮卡两下;临时把 HPD 保持关闭一分钟就不卡,自动恢复后又卡,触发条件确认。
修补方式是一个可卸载的外部模块 hdmi_hpd_guard:通过 ftrace 只拦截 hdmirx_plugout 对 HPD 的关闭调用,并且要原驱动的 5V 去抖先确认源端仍在供电才抑制。HPD 打开、真拔线、手动 sysfs off/on、EDID 写入这些路径都保持原行为。
配套产物:模块 /lib/modules/5.10.198/extra/hdmi_hpd_guard.ko,管理工具 /usr/local/sbin/hdmi-hpd-guard,开机服务 hdmi-hpd-guard.service(enabled + active),源码在 /root/AI/hdmi-hpd-guard/。加载器会校验内核版本、构建指纹和唯一符号,符号地址每次实时解析、不保存地址配置——换内核必须重新构建并回归,不允许绕过校验。
已验证:仅当前模式不卡;复制模式下正常采集;切回仍不卡;真拔线插回仍能采集。日志确认复制模式下收到帧,最后一轮连续采集 63 秒;模块保留的真实拔线调用计数 2 次;手动 off/on 回归通过,systemd 连续卸载/重载 3 次通过。未做整机重启,也没实测休眠或受保护视频场景,所以不宣称这些场景全部验证过。原厂内核的失锁重试、音频 FIFO overflow、CMA 警告并没有全部消除——音频 overflow 在本次修补前就已存在。
五、低功耗:占位画面固定到小核
绿幕生成,以及无信号时的绿幕/黑屏占位 ffmpeg,用 taskset -c 0-3 固定到小核(逐核查 MIDR 确认 CPU0–3 是 Cortex-A55,不靠编号惯例猜)。帧率做了自适应:没有读取端时 1 fps,检测到 OBS 这类读取端后升到 15 fps。正常的 HDMI 采集分支不加绑定,也不改变 HPD 修补。
六、音频的坑:断开再恢复后,旧 OBS 采集不动了
2026-10-12 补记。当时桥接与 HPD 服务都是 active,OBS 音频源没静音,48 kHz 双声道。但现象是:
- 启动阶段 OBS 反复报 ALSA 设备「没有那个设备」,内核报 audio off;
- 12:30:55 OBS 成功打开设备,12:30:56 驱动 FIFO underflow 并 audio off,12:31:10 又 audio on;
- 电脑持续播放、驱动侧
audio_present=1/48000 时,旧的那个 OBS 采集仍然state: NONE,hw_ptr/appl_ptr停在 22016 不动。
结论:这是音频断开/恢复之后采集没跟着恢复,不是靠调音画偏移能解决的问题。恢复办法很简单——保持 Windows 输出为有效的 HDMI 端点并持续播放声音,然后正常退出、重新打开板子上的 OBS。正在录像/推流时先结束输出,别强杀 OBS,也别反复重启桥接。
只读核验命令如下(先 arecord -l 确认卡号,卡号可能变化):
v4l2-ctl -d /dev/video52 --get-ctrl=audio_present,audio_sampling_rate
arecord -l
cat /proc/asound/card2/pcm0c/sub0/status正常连续采集应该是 RUNNING,重复读取时 hw_ptr/appl_ptr 会前进;audio_present=1 本身不代表 OBS 正在采集。用户 12:33 重开 OBS 后电平恢复:新日志 48000Hz/2channels 成功,两次检查均 RUNNING,采样指针从约 91 万前进到约 233 万。
七、回滚与备选方案
整体回滚:
systemctl disable --now hdmi-in-bridge
rm /etc/systemd/system/hdmi-in-bridge.service
rm /usr/local/bin/hdmi-in-bridge.sh
rm /etc/modprobe.d/v4l2loopback.conf /etc/modules-load.d/v4l2loopback.conf
rm /lib/modules/$(uname -r)/extra/v4l2loopback.ko
rmmod v4l2loopback; depmod -a只回滚到旧版桥接脚本(不做上面那些删除):
cp -a /etc/systemd/system/hdmi-in-bridge.service.bak.<时间戳> \
/etc/systemd/system/hdmi-in-bridge.service
systemctl daemon-reload && systemctl restart hdmi-in-bridge不想装内核模块的备选方案:ffmpeg 用硬件编码推到本地 UDP,OBS 用「媒体源」接。
ffmpeg -f v4l2 -input_format nv12 -i /dev/video52 -pix_fmt nv12 \
-c:v h264_rkmpp -b:v 12M -g 30 -f mpegts udp://127.0.0.1:12345?pkt_size=1316OBS 媒体源 URL 填 udp://127.0.0.1:12345。缺点:OBS 侧要软解 4K H.264(CPU 占用高),还有编码延迟和画质损失。
万一 OBS 再崩,这样抓栈:
echo '/tmp/obscores/core.%e.%p' > /proc/sys/kernel/core_pattern
mkdir -p /tmp/obscores
# 在桌面会话里 ulimit -c unlimited 后启动 OBS,复现崩溃
gdb -batch -ex 'thread apply all bt' /usr/bin/obs /tmp/obscores/core.obs.*
# 恢复 apport
echo '/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -F%F -- %E' > /proc/sys/kernel/core_pattern八、小结
- RK3588 的 HDMI-IN 之所以进不了 OBS,根因是驱动不实现
VIDIOC_G_INPUT,而 OBS 把它当必经步骤——这不是配置问题,改 OBS 设置没有用。 - 绕法是 v4l2loopback + ffmpeg 透传;但直接把 ffmpeg 写成
Restart=always会在无信号时变成重启风暴,必须把「等信号」和「起流」分开。 - 启动顺序不能反:先桥接起流,再开 OBS;源黑屏就重启 OBS,别去碰属性对话框。
- 顺手修掉了源端关闭输出时 Windows 每 5.73 秒的卡顿(HPD 虚假拔插),以及音频断开恢复后采集不动的问题。
目前信号锁定状态下实测 2 分钟:ffmpeg 连续运行 0 次重启、内核 hdmirx 日志 0 条、OBS 稳定采到 3840x2160 NV12 并同时打开 hw:CARD=rockchiphdmiin,DEV=0 音频源,桥接服务内存约 145 MB。