MiBeeNvr v0.9.0 → v0.9.1: ONVIF 零配置自动发现 + 存储重写 + 摄像头管理器无锁化 + FFmpeg 可选化 + i18n 全面修复

🔊

9 路摄像头挂在 Banana Pi M5 上跑了大半年,从 v0.6 开始累积的工程债到了不得不还的时候。/api/cameras 接口在 9 个录制器全部启动的过程中要卡 14.9 秒——不是遍历慢,是 cm.mu RWMutex 在阻塞,任何读操作都进不来。ONVIF 摄像头添加还得手动填 IP,家里装了 CW500、DS-2CD2047G2 这类设备之后,每多一台就要开一次终端,不可持续。FFmpeg 作为 NVR 唯一的第三方二进制依赖,每次部署都要确认装没装,树莓派上还要操心硬件编码器版本。更扎心的是,Xiaomi 的那几个旧设备——Dafang、Xiaofang、Aqara G2——因为走了 TUTK 协议,一直没法接入。这些问题都不是新技术挑战,是工程债,v0.9 一次性还了。

这版做了 54 次提交 / 9 个合并 PR(#49–#61),覆盖了 ONVIF 零配置自动发现、存储层读写分离重写、摄像头管理器无锁化重构、FFmpeg 降级为可选依赖、Xiaomi TUTK 旧设备协议支持、格子协议自动选择与 ONVIF 全自动化、数据修复 CLI,以及关键修复与代码规范化。每个主题下面展开。

v0.9.0 在 9 台真实摄像头(CW500 双摄 ×2、DS-2CD2047G2、户外摄像头 4、IMILAB A1、Dafang、Xiaofang、Aqara G2、小米云台)上完成了回归验证。有一点需要强调:这是自 v0.1.0 发版以来的最后一个稳定兼容版本。下一个大版本将引入破坏性变更——精确告知迁移路径、配置变化、影响范围,无静默破坏。记录器 Gallery Grid View 也将移除(Timeline 和 List 加强)。长期稳定部署建议锁定 v0.9.x 系列。

v0.9.0 发布后第二天(7-19)就跟进了一个 patch——v0.9.1。原因是发布当天密集收到了几轮社区反馈,主要集中在 i18n 完整性、录像碎片、以及若干 UX bug(详见 issue #64/#68/#70)。这些不是 v0.9.0 的新功能没做完,而是几个历史遗留的硬编码字符串和一个回归 bug 被这次回归测试暴露出来了。文末单开一节讲 v0.9.1 的具体修复。v0.9.0 完整变更见 v0.9.0 Release Notes,v0.9.1 见 v0.9.1 Release Notes

ONVIF 零配置自动发现

v0.9 之前的 ONVIF 设备添加流程是:知道 IP → 进 Settings 页 → 手动填 Profile → Hail Mary。这在 3 台以内没问题,但超过 5 台就变成负担了。v0.9 的自动发现实现了 Hikvision 级别的一键添加——通电解锁、不用配置、NVR 自己找到设备。

mermaid
sequenceDiagram
    participant CAM as Camera (ONVIF)
    participant NVR as MiBeeNvr
    participant UI as Frontend
    participant DB as SQLite

    CAM->>NVR: WS-Discovery Hello<br/>UDP 3702 multicast
    NVR->>DB: Dedup (endpoint + stable_id)
    NVR->>NVR: Enrich (brand/model/serial)
    NVR->>UI: SSE camera.added
    UI->>UI: Toast + pending_activation badge
    UI->>NVR: POST activate (credentials)
    NVR->>CAM: GetStreamURI + RTSP DESCRIBE
    NVR->>DB: Update active status
    NVR->>UI: SSE camera.activated

自动发现分两种模式并行工作:被动模式监听 UDP 3702 端口的 WS-Discovery Hello 多播消息(零延迟,设备上线即感知);主动模式每 60 秒做一次 Probe 扫描(可配置,最小 30 秒,RPi-3B 性能约束)。两种模式下发现的摄像头都进入统一的注册管线:去重(按 ONVIF endpoint + stable_id,覆盖所有协议包括已归档设备)→ 信息补全 → 分类(active / pending_activation)→ 持久化。

没有凭据的设备进入 pending_activation 状态,前端 CameraCard 上显示 pending badge 和激活按钮,点击弹出凭据对话框调用 POST /api/cameras/{id}/activate。激活成功后 SSE 发出 camera.activated 事件,前端自动刷新列表。Settings 页面新增自动发现配置子端点 /api/settings/auto-discover,可调扫描间隔、监听接口、默认凭据(返回 has_default_password 布尔值,不返回明文)、作用域黑名单。启动时有 10 秒的 grace period,避免 NVR 刚启动时设备还没有完成 DHCP+ONVIF 初始化就被 Probe 扫描错过。

存储层重写

PR #53 是这版改动量最大的单个 PR——31 次提交 / 117 文件 / ~10800 行。驱动力是 v0.8 开发中反复出现的存储瓶颈:N+1 查询、全表扫描的 COUNT(*)、以及录制器写入时读取端被锁住。

mermaid
flowchart TD
    W["Write Pool<br/>1 连接, 串行"] --> DB[(SQLite<br/>WAL mode)]
    R["Read Pool<br/>3 连接, 并行"] --> DB
    R --> CACHE["COUNT 缓存<br/>2s TTL"]
    CACHE --> API["daily-summary API"]
    DB --> MERGE["RollingMerge<br/>合并后验证"]

    classDef w fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef r fill:#E3F2FD,stroke:#1565C0,color:#1565C0
    classDef s fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
    classDef m fill:#F3E5F5,stroke:#9C27B0,color:#6A1B9A
    class W w
    class R,CACHE r
    class DB,API s
    class MERGE m

核心改动是读写分离连接池:writer 使用 1 个串行连接,reader 使用 3 个并行连接(WAL 模式支持并发读取),reader DSN 带 query_only=1。写路径的 COUNT(*) OVER() 全表扫描之前被证明有严重的性能回退,替换为 COUNT 缓存(2s TTL)加批量操作。冗余索引无条件 DROP,auto_vacuum 增量迁移(v23),查询指标全链路埋点。

新增 GET /api/recordings/daily-summary 接口提供日历聚合数据,前端日历视图和 Gallery 数据源分离,日历翻页不再卡住 Gallery 分页。启动时做合并完整性扫描:检查已标记为 merged 的 MP4 文件是否实际存在,不存在则重置 merge_status,让播放回退到帧而不是返回 404。RollingMergeManager 增加了合并后验证——合并报告成功但输出 MP4 缺失时,阻止 delete_original 销毁源帧。live merge 使用 blocking lock + retry 替代之前的 try-lock,减少极端情况下的合并遗漏。

存储层重写里顺带修了一个困扰很久的 keyframe 问题:某些 Xiaomi H.265 摄像头只在 SDP 或 moov box 里发送参数集,KeyframeExtractor 取不到 SPS/PPS 就永久合并失败。现在通过 CodecParamProvider 从 live recorder 实时拉取参数集,H.265 合并不再报错。

摄像头管理器无锁化重构

v0.8 的摄像头管理器在 9 路摄像头上暴露了一个设计缺陷:cm.mu RWMutex 保护整个管理器状态,recorder Start/Stop、ONVIF 握手、磁盘写入这些慢操作都持有写锁,导致任何读操作——包括 GET /api/cameras 和 grid 的 latest-frame 轮询——必须等待。更扎心的是这个锁是在 v0.1 就有的原始设计,一直没被质疑过。

重构解掉了这个锁,方案是 Write-on-Copy(COW)原子快照加 per-camera single-flight guard:

  • snapshot atomic.Pointer[snapshot]:不可变的原子发布视图,包含 recorders / hubs / configs / failedStarts 的完整快照。
  • apply(fn):唯一的写入路径,configMu 只持有纳秒级的 map 拷贝加磁盘写入,从不跨 I/O 操作。
  • withCameraLifecycle(id, fn):per-camera sync.Mutex,通过 sync.Map 管理,串行化 start/stop/restart。
  • 所有读路径(GetRecorder/GetHub/Status/statusSnapshot/counts)完全无锁。

ONVIFRecorder.Start 不再在握手期间持有 r.mu——ONVIF+RTSP+HTTP 的三次握手秒级阻塞不再阻塞 grid 轮询。

生产实测数据(Banana Pi M5, 9 路摄像头):

指标重构前重构后
/api/cameras 冷启动14.9s → 1.4s(仍然卡顿)稳定 20-23ms
latest-frame 轮询(grid 热路径)重连期间数百毫秒稳定 1.5-2ms
Recorder 启动串行(一个慢阻塞全部)并行(4 个同时同一秒)
ONVIF 重启阻塞读取是(秒级)否(~20ms 稳定)

重构前 14.9s 里大部分时间不是在做有用的事——几个 recorder Start 串行执行,每个做 ONVIF 握手加 RTSP 连接,期间写锁把其他 8 个录制器和所有读路径全部挡在外面。改成 COW + per-camera guard 之后,一个摄像头的握手不再拖累全局。

FFmpeg 降级为可选依赖

FFmpeg 之前是 NVR 唯一的第三方二进制依赖,负责媒体探测、转码、延时合并。v0.9 把它降级为可选加速器:NVR 所有的核心功能(录制、回放、直播、Relay、延时摄影、合并)在无 FFmpeg 的情况下都能跑;只有 H.265↔H.264 像素级转码需要 FFmpeg。

新增 internal/mediaprobe 纯 Go MP4 盒元数据探测包,支持 codec/duration/resolution/frame-count 读取,比 ffprobe -count_frames 快 10-100 倍。transcoding.GetMediaInfo 优先走 mediaprobe,FFmpeg 做 fallback;cleanup.probeDuration 同理。延时摄影的 timelapse selectTier 默认走 TierGo,TierFFmpeg 作为可选。FFmpeg 不存在时缩略图使用纯 Go 占位图生成。

顺便修了 4 个真实摄像头录制的 bug:G.711 ParseSegment 失败阻塞(之前报错但没做降级)、FFmpeg -c:a copy 在 G.711 上失败(改为 -c:a aac)、MJPEG 备份目录残留导致的二次处理失败、MJPEG→H265 误拒绝。install.sh 增加可选的 FFmpeg 自动安装(apt/dnf/yum/apk),非阻塞。

Xiaomi TUTK 旧设备协议

Xiaomi 的旧款摄像头(Dafang、Xiaofang、Aqara G2 等)用的是自研 TUTK(IOT)P2P 协议,不走标准 RTSP/ONVIF。之前这些设备只能通过 Mi Home app 查看,没法接入任何 NVR。PR #52 从 go2rtc 移植了 TUTK 传输层,实现了完整的独立实现。

internal/tutk/ 包含:TransCode/XXTEA 加密变换、codec 常量、GenSessionID/ICAM/HL 工具函数、FrameHandler/Packet、Dial + Conn(TransCode + worker + idle timeout)。支持 session0/16/25 三种会话类型,其中 session25 带 5 深度的 ReorderBuffer 分片重组。自定义 ChaCha20-Poly1305 DTLS 密码套件。

支持的旧设备型号(7 个):Dafang(isa.camera.df3)、Xiaofang(isc5c1)、Aqara G2(g2h)、IMILAB A1、Loock V1 门铃、小白(Xiaobai)、米家云台(Mijia)。MISS 协议新增双向语音、PTZ 云台控制、设备信息查询(固件/硬件版本)、wakeUpCamera(猫眼/门铃等电池设备需要 RPC 唤醒)、画质选择 "auto"/"hd"/"sd"(修复 isa.camera.hlc8 拒绝 HD 的问题,对应 go2rtc issue #2114),以及各型号的时间戳 quirks。CS2 TCP 增加 1 秒 PING 保活(对齐米家官方行为)。

与 go2rtc 上游的策略差异文档化在 AGENTS.md:Pop buffer 溢出时丢弃旧帧(go2rtc 报错)、hdr 双拷贝问题 bug-for-bug 保留。

前端新增双向语音 UI、Xiaomi PTZ 控制面板、设备信息面板。

格子协议自动选择 + ONVIF 全自动化

v0.9 之前,格子(Surveillance Grid)每路摄像头的协议选择是手工的——默认走 HLS,如果摄像头是 H.265 或者浏览器不支持就手动切。PR #54 把协议选择做成了自动化。

格子现在根据每路摄像头的编码类型和浏览器的解码能力自动选择最佳流协议,运行时支持失败降级:webrtc → flv → hls → mjpeg,全部降级失败后切换为静态快照。后端之前已有支持 codec 感知排序的 GET /api/cameras/{id}/protocols 端点,但格子一直没有消费它。这次补上了。

ONVIF 全自动化关闭了 4 个历史 issue(#36/#48/#50/#51):发现阶段获取的摄像头元数据(品牌/型号/序列号)现在持久化保存,之前是展示完就丢。新增真实的流测试连接(GetStreamURI + RTSP DESCRIBE,不再只做 HTTP HEAD)。按分辨率选择 Profile,不再盲选 profiles[0]。修复了 Hikvision 鉴权的 digest time-skew 问题(fork v1.1.6)。支持批量 add all,新增 subnet_hints 编辑器和 ProfileToken 持久化。

Live-only 模式(issue #36):recording_enabled=false 时录制器保持连接用于直播/Relay但不写入段文件,四个录制器家族(H264/H265/MJPEG/HTTP-JPEG)全部覆盖。

友好的错误提示也在这版落地:friendlyError 加 15 个错误码翻译,纯音频摄像头隐藏音频按钮、高级录制选项折叠、严重度感知的 toast 时长。新增文档 docs/en+zh/streaming-protocol-selection.md,详细解释了格子的四层协议选择架构。

数据修复 CLI + FastProbeDuration

录制文件的数据一致性问题一直在积累:网络不稳定导致录制段丢失、合并输出不完整、SPS/PPS 变化导致段永远无法合并。之前修复这些需要手写 SQL 脚本,v0.9 把它们做成了 CLI 命令。

mibee-nvr repair duration:重新探测录制文件修正 duration=0 的粘滞问题。大文件使用 FastProbeDuration(只读取 stts box,比全解析快 100 倍);MJPEG 帧目录使用帧计数估算。--prune 删除不可修复的损坏片段(DB 行 + 文件)。

repair merge-status:重置 merge_status 为缺失合并输出文件的条目(从启动流分离,避免每次启动全表扫描加 os.Stat)。

repair fragments:清理 merge_status='incompatible' 的死段(SPS/PPS 改变,永远不可合并)。dry-run 按摄像头列出数量和空间占用。--retry 重置为 pending,--force-delete 删除 DB 行和文件。

FastProbeDuration 来自新增的 merge.ParseSegmentDurationOnly + mediaprobe.FastProbeDuration,在 stts box 处停止解析,不构建完整 sample table。生产结果:4285 个片段在大约 30 分钟内修复完成(原来估计需要 10+ 小时)。

文档新增 docs/en+zh/troubleshooting.md 的「timeline recording missing」章节。

关键修复

多个跨 PR 的修复值得单独列出来。

Camera 启动生命周期(PR #60)POST /api/cameras/{id}/start 之前把 HTTP 请求的 ctx 传给了 rec.Start。HTTP 响应返回后 ctx cancel,recorder 的 run goroutine 通过 ctx.Err() 分支直接退出——现象是摄像头「启动了但马上又停」。修复改为传 context.Background(),recorder 生命周期完全由自己的 Stop() 管理。

时区偏移(PR #60):北京时间 16:00 的录制在 timeline 上显示在 08:00 的位置。后端 ParseMergeDuration 对 8h/24h/natural-day/7d/30d 合并窗口做了降级(cap 1h + warn),前端 parseDayStart 返回本地午夜。

Timeline 截断(PR #60):Xiaomi 频繁重连产生的 5000+ 碎片,asc+2000 的 limit 让下午的记录完全不显示。上限从 2000 提到 10000。

AddCamera 去重(PR #56):之前只按 ID 去重,auto-discover 生成的随机 ID 导致同一台 ONVIF 设备被注册两次,生产环境积累了 777MB 冗余视频。新增 endpoint + stable_id 双重去重。

AVI 录制(PR #52):MJPEG 摄像头的 AVI 视频-only muxer(替换 JPEG 目录);per-camera 健康隔离避免单摄像头故障级联全局;SQLITE_BUSY 修复(DSN busy_timeout 15s + 增量 reconcilation + batch DELETE)。

合并音频编码不匹配(PR #52):G.711/Opus 编码保护防止音轨损坏。

代码规范化 — golangci-lint v2(PR #52+):全面采用 golangci-lint v2.12.2。新增 linters 包括 perfsprint(fmt.Sprintfstrconv/hex/concat)、nilerr、nilnesserr、wastedassign、fatcontext、intrange、usestdlibvars、predeclared(修复 min 内建变量名遮蔽)。interface{}any 全量迁移加编译时断言。manager.go(2109 LOC)拆分为 6 个文件。//nolint:<linter> // TODO(#issue): <reason> 格式强制,所有 exclusion rules 附了行内 rationale。

数据竞争修复session0.go writeAndWait 竞争条件、wsstream viewer-not-registered panic、xiaomi TestCS2ConnError 测试 flake、TestHealth/CameraForm relay 测试 flake 全部修掉。

依赖升级:Go 和 npm 依赖全部升到最新。gortsplib 5.5.2 → 5.6.1,gortmplib 0.3.2 → 0.4.0。

重要公告

v0.9.0 是 MiBeeNvr 自 v0.1.0 以来的最后一个稳定兼容版本。下一个大版本会做一次全面的代码规范和架构重构,届时将引入破坏性变更。所有破坏性变更都会精确告知迁移路径、配置变化、影响范围——不会静默破坏。前端的 Recordings Gallery Grid View 将会移除,Timeline 和 List 视图会加强来覆盖其功能。

如果需要长期稳定部署,建议锁定 v0.9.x 系列(当前 v0.9.1),等下一个大版本的迁移指南出来后再考虑升级。

v0.9.1 补丁:i18n 全面修复 + 录像碎片治理 + 社区反馈

v0.9.0 发布当天集中收到一批社区反馈(issue #64/#68/#70),暴露出几个回归 bug 和历史遗留的硬编码字符串。v0.9.1 是次日跟进的 patch release,聚焦四件事:i18n 完整性、录像碎片治理、UX 修复、文档校准。无破坏性变更,直接替换二进制即可。

i18n 全面修复(#68)

这是这次 patch 最重的一项。写了个脚本把代码里所有 t('...') 引用和翻译字典做了交叉比对,发现两类系统性遗漏:

  • 19 个 key 被代码引用但 en.jsonzh.json 都没有。用户在 UI 上看到的是原始 key 名——cameras.darkFrameFiltercameras.passwordSetcameras.recordingSchedulecameras.twoWayAudioEnabledcommon.closelive.retrytimeline.frameDropxiaomi.twoWayAudio* 等等。这些大多是 v0.6/v0.7 加功能时漏接的,不是 v0.9.0 新引入的。本次全部补全。
  • 转推(Push-Out)配置整段是硬编码英文。平台选择器、转码策略下拉、整个"预设覆盖"面板(分辨率/帧率/码率/GOP/Profile/B 帧/重置)完全没接 i18n。新增 14 个 key,硬编码字符串全部改接 t()

最终 en.json / zh.json 从 1341 → 1377 个 key,parity 校验 0 缺失。排查脚本保留,后续会纳入 CI 防止再犯。

录像碎片治理

v0.9.0 把合并积压问题解决后,碎片数量本身还是偏高,这次做了一次根因治理:

  • 平台感知的分段时长上限。MP4 muxer 在分段关闭前把所有样本驻留内存(moov 最后写),所以分段越长内存占用越高。之前没有上限约束,低内存设备上有 OOM 风险。现在按可用 RAM 自动设上限:≤2GB 可用内存(如树莓派 3B)封顶 30 秒(OOM 安全余量);>2GB(Banana Pi M5、x86 等)封顶 2 分钟——直接把碎片数量减半,缓解滚动合并压力。超过平台上限的配置值会被静默 clamp 并告警。
  • 周期性回填清扫。新增 rolling_backfill_interval(默认 10m)和 rolling_backfill_batch(默认 500)两个配置。之前回填只在启动时做一次,长时间运行后历史 pending 分段会重新累积。现在后台周期性清扫,设为 "0" 可禁用周期清扫(仅保留启动回填)。

社区反馈修复(#64 / #68 / #70)

  • Xiaomi TUTK 不再被添加预检拦截(#64):v0.9.0 已经支持了 TUTK 协议,但添加摄像头时的 check-vendor 预检接口没同步更新,仍然把 TUTK 标记为 compatible: false,前端弹出"不兼容"对话框把用户挡在外面。修复后 CS2 和 TUTK 都返回 compatible: true。这是一个典型的"功能接通但入口漏改"的回归 bug。
  • Docker 自定义端口(#64):群晖 NAS 这类设备 9090 端口经常被占用。补充了 docker-compose 的 HOST:CONTAINER 端口映射说明和 host 网络模式下 server.listen 的配置指引;troubleshooting 新增"Docker / NAS 9090 端口冲突"章节。
  • Xiaomi 摄像头隐藏凭据框(#68):Xiaomi 走小米账号 token 认证,不走摄像头级用户名/密码,但添加表单还是显示这两个字段,容易让用户误以为必填。已按协议类型隐藏。
  • 合并策略"使用全局默认"真正生效(#68):这是一个藏得很深的 bug。DELETE 接口调 UpsertCameraMerge(nil...),但这个函数用 COALESCE 保留原值——传 nil 等于 no-op,覆盖配置永远清不掉,前端点了"使用全局默认"实际不生效。新增 ClearCameraMerge 真正把所有字段置 NULL;GET 接口返回 customized 标志让前端区分"有覆盖"和"全默认"两种状态。
  • 转推地址一键复制(#70):摄像头卡片上的"1/1"徽章现在可点击,弹出浮层列出每个转推目标(名称/协议/启用状态/完整 URL),每个地址旁有复制按钮。之前要查看转推地址只能进设置页翻配置。

文档全面校准

发版前对全部公开文档(README + docs/en + docs/zh)做了一轮准确性审计,修了 9 个 CRITICAL + 3 个 MODERATE + 1 个 MINOR,18 文件、+664/-809 行。挑几个有代表性的:

  • xiaomi-setup.md 双向音频描述反转:文档说"仅 CS2 支持双向音频",实际代码是"仅 TUTK 支持"(CS2 会返回 two-way audio requires TUTK transport)。文档和代码完全相反。
  • timelapse.md 移除"v2"框架 + 修正 merge_duration 误导:文档推崇 8h/12h/24h/natural-day/7d/30d 这些合并窗口,但代码实际上把这些值静默 clamp 到 1h(legacy 字符串)或者校验报错(其他 >1h 值)。文档描述的是一个并不存在的能力。已准确描述 1h 上限;中文版文件之前还被翻译工具污染(每行带 #XX| 前缀),干净重写。
  • transcoding.md Prometheus 指标名错误:文档列的 mibee_nvr_transcoding_* 指标不存在,实际是 nvr_transcoding_*。已对齐 metrics.md。
  • api/xiaomi.md check-vendor 示例过时:示例里 TUTK 还返回 compatible: false,已更新为 v0.9.0 后的真实行为。
  • troubleshooting.md 补全 repair 子命令:之前只文档了 repair duration,补充了 repair merge-statusrepair fragments(含 --reset-fake-merged / --max-duration 标志)。

另外 README 头部把树莓派从"旗舰定位"降级为"支持的低功耗 ARM 平台之一"——badge 从 Raspberry Pi 改成通用的 ARM,hero slogan 从 Turn any Raspberry Pi... 改成 Turn any low-power ARM device...。项目实际早就从"树莓派专属 NVR"演进成跨平台轻量 NVR(x86 服务器 / Banana Pi / 群晖 NAS / 树莓派都是目标平台),文档这次跟上了。技术约束(RPi 3B 作为最低端验证基准)全部保留。

已知问题(后续版本跟进)

三个问题这次没修,原因都是需要更深层的改动或真实设备验证环境:

  • TUTK 摄像头反复重连(#68-7):根因已经定位——我们的 TUTK worker 比 go2rtc 多了一个 30s idle timeout,这是导致重连的源头。但修复涉及协议层改动,需要真实 TUTK 设备验证环境,暂缓。临时可以用 go2rtc 转推绕过。
  • 录像页进度条点击重载闪烁(#68-6):涉及播放器架构改动,已记录跟进。
  • 转推鉴权(#70-2):已记录跟进。

完整变更见 v0.9.1 Release Notes

升级

配置向后兼容,直接替换二进制即可:

bash
1
2
3
4
5
# Docker
docker compose --project-directory . -f deploy/docker/docker-compose.yml up -d

# 或下载二进制
curl -fsSL https://raw.githubusercontent.com/Mi-Bee-Studio/MiBeeNvr/main/install.sh | sudo bash

升级后建议检查 config.example.yaml 中的新配置项(ONVIF 自动发现参数、自动发现凭据、存储池配置等),按需补充。ONVIF 自动发现默认启用,如果不需要可以在 Settings 里关闭。

相关链接