每日 LLM 健康检查报告显示 10 台摄像头中有 8 台当天崩溃次数达到数百次。 追查下去,根因是两台婴儿监控摄像头、一个 go2rtc 重连窗口,以及一次 vaapi 级联崩溃——没有一个环节是直接显而易见的。以下是完整的排查与修复过程。
问题是如何发现的#
我在搭建一个每日家庭健康代理——一个定时脚本,查询所有家庭服务(Frigate、Home Assistant、Paperless、arr 媒体栈),然后将数据交给本地 LLM 分析。核心思路是:不再手动逐个检查仪表盘,而是每天早上收到一份摘要,自动标出异常项。
Frigate 的检查项查询 /api/stats,提取每台摄像头的崩溃次数。某天早上,报告返回了这样的数据:
| |
如果没有健康检查,我根本不会注意到——Frigate 容器本身从未重启,Web UI 上各摄像头仍然显示"在线",也没有任何告警弹出。
根因:崩溃级联#
顺着日志往前追,每天上午 9 点的事件链如下:
- Cron 停止
nanit容器(婴儿监控只需要夜间运行) - nanit RTMP 推流断开,go2rtc 失去数据源
- go2rtc 在内部保持 nanit RTSP 流存活约 37 分钟(其重连窗口)
- 约 09:37,go2rtc 放弃,对两路 nanit 流返回
404 Not Found - Frigate 的 ffmpeg 在 404 上崩溃;watchdog 立即重启 → 约 10 秒一次的紧密崩溃循环
- 两路 nanit ffmpeg 进程同时崩溃循环,耗尽 Intel iGPU vaapi 上下文
- 其他所有摄像头——共用同一 vaapi 设备——崩溃,报错
Failed to sync surface - 非 nanit 摄像头在 5–6 小时内逐个自我恢复,随着各自 ffmpeg 进程陆续重启
Frigate 容器本身 0 次重启。所有问题发生在 ffmpeg 子进程层面。崩溃计数是准确的——2228 次大约是 8 小时内每 13 秒一次,与 10 秒的 watchdog 周期完全吻合。
RTMP 架构背景#
nanit 婴儿监控通过第三方容器(ghcr.io/gregory-m/nanit)与 Nanit 云端认证,并在主机端口 1935 上运行 RTMP 服务端。go2rtc(内嵌于 Frigate)作为客户端从中拉流:
| |
修复目标:确保 go2rtc 始终有有效的 RTMP 流可以拉取,即使 nanit 容器已停止。
失败的尝试#
go2rtc ffmpeg: 备用源#
go2rtc 支持为每个流配置多个源——主源失败时自动切备源。方案:添加一个用 ffmpeg 生成黑屏的备用源。
| |
问题一: Frigate 会将 {...} 预处理为环境变量模板,{output} → Invalid substitution found。用 {{output}} 双花括号转义可绕过。
问题二: go2rtc 的 ffmpeg: 源不会对参数做 shell 分割,整个字符串作为单一 token 传入。结果:Error opening input file -re.
{output} 和 #video 简写方式均告失败。
使用 exec:ffmpeg 加 GO2RTC_ALLOW_ARBITRARY_EXEC=true#
go2rtc 有 exec: 源类型,支持运行任意命令。设置环境变量、配置正确语法后,/dev/shm/go2rtc.yaml 中 exec: 行显示正常——但 ffmpeg 进程从未实际启动。无报错,ps aux 中也看不到进程。原因不明,疑似静默启动失败。
独立占位容器使用不同端口#
创建 nanit-placeholder 容器,将黑屏帧推送到 go2rtc 的不同路径,再利用 go2rtc 的备用源:主源用 1935,备源用 1936。
致命缺陷: go2rtc 切到备源后,即使主源恢复也不会自动切回,只有当前会话断开才重新尝试。强制切回需要重启 go2rtc 子进程,会导致所有摄像头中断 2–3 秒。不可接受。
占位容器推流到 go2rtc RTMP#
占位容器尝试通过 frigate_default Docker bridge 网络推流到 rtmp://frigate:1935/...,结果:Connection refused。
go2rtc 的 RTMP 端口只能从 network_mode: host 的容器访问(如真实的 nanit 容器),bridge 网络容器无法访问。原因:go2rtc 的 RTMP 端口绑定在主机网卡上,而非 Docker bridge。
修复方案:基于 mediamtx 的同端口切换#
核心洞察:如果占位服务接管同一个端口(1935),go2rtc 根本察觉不到切换。不需要备用源逻辑,不需要重连触发,不影响其他摄像头。
| |
占位服务使用 mediamtx(bluenviron/mediamtx:latest-ffmpeg),内置 ffmpeg 生成黑屏 H264 帧:
docker-compose.yml:
| |
mediamtx.yml:
| |
几个关键 ffmpeg 参数:
| 参数 | 原因 |
|---|---|
-profile:v baseline -level 3.0 | 最大 H264 兼容性,防止 vaapi 解码报错 |
-g 1 | 每帧都是关键帧,确保流启动干净,RTSP 中继不出现"Invalid data" |
不用 -tune stillimage | 该 tune 产生非标准 H264 结构,约 40 秒后 RTSP 中继断流 |
yang@debian.lan 的 cron:
| |
切换过程中有约 2–3 秒断流,go2rtc 重连期间 Frigate 会立刻恢复——这只是短暂的连接中断,而非持续的 404,不会触发 crash loop。
结果#
修复后,所有摄像头崩溃次数降为零。此前每天上午因 vaapi 级联故障而耗费数小时恢复的非 nanit 摄像头,现在全天稳定运行。
go2rtc 配置保持简洁——每路流只配一个源,无需备用:
| |
总结#
健康检查让这个问题得以曝光。没有每日摘要里的各摄像头崩溃计数,这一切会悄无声息地持续——Frigate 显示绿色,而摄像头的 ffmpeg 进程每天早上循环崩溃数千次,其他摄像头则花半天时间慢慢恢复。
找到根因之后,修复本身并不复杂。难的是走到那一步。