v0.9.0
v0.9.0 — ONVIF 零配置自动发现 · 存储重构 · FFmpeg 可选 · Xiaomi TUTK
⚠️ 重要公告:下一个大版本将引入破坏性变更(Breaking Change)v0.9.0 是自 v0.1.0 发版以来的最后一个稳定兼容版本。 后续版本将进行大规模底层重构与代码规范化,为项目长期演进铺路。届时会精确告知每一处不兼容点(迁移路径、配置变更、影响范围),不会做静默破坏。
同时,下一个大版本将精简前端交互——移除 Recordings 页面的**画廊网格视图(Gallery Grid View)**等低使用率功能,降低用户认知负担,让 NVR 聚焦核心场景(实时监控、录像回放、AI 事件)。建议本次升级的用户在升级前留意:未来版本不再保留画廊视图,时间轴(Timeline)与列表(List)视图将继续保留并强化。
如果你需要长期稳定的部署基线,请锁定 v0.9.0。
本版本涵盖 54 次提交 / 9 个合并 PR(#49–#61),核心主题:ONVIF 类海康零配置自动发现、存储层重写(读写分离池 + 合并完整性)、FFmpeg 降级为可选依赖(纯 Go 默认)、Xiaomi TUTK 旧设备协议、摄像头管理器无锁化重构。
英文摘要见文末 / English summary at the bottom.
🆕 ONVIF 零配置自动发现(类海康即插即用)
PR #55 / #57 / #58 — 后台 ONVIF 自动发现 + 自动注册,对标海康 NVR 即插即用体验。默认关闭,配置/Settings 中开启。
- 双模发现:被动 Hello 监听器(UDP 3702 多播,零延迟)+ 主动周期 Probe 扫描(默认 60s,下限 30s,10s 启动宽限)兜底
- 统一注册管道:去重(按 ONVIF endpoint,覆盖所有协议 + 含归档相机)→ enrich → 分类(active / pending_activation)→ 入库
- 凭据发现机制:无凭据发现的相机进入
pending_activation态;前端CameraCard待激活徽章 + 激活按钮 + 凭据对话框 →POST /api/cameras/{id}/activate - SSE 实时通知:
camera.added命名事件 → 前端 toast(active 成功 / pending 警告)+ 列表自动刷新,断线自动重连 - Settings UI:
/api/settings/auto-discover子端点;扫描间隔、监听接口、默认凭据(has_default_password布尔返回,绝不回传明文)、忽略 scope 黑名单;scan_interval下限 30(RPi-3B 约束)
💾 存储层重写 — 读写分离 + 查询性能 + 合并完整性
PR #53 — 31 commits / 117 文件 / ~10.8k 行的存储可靠性与性能大版本。
- 读写分离连接池:writer=1 串行 / reader=3 并发(WAL 允许并发读),DSN
query_only=1;彻底消除 N+1 录像查询,批量操作,缓存 COUNT(2s TTL,规避已证实的COUNT(*) OVER()全表扫描回归) - DB 维护:冗余索引无条件 DROP、
analysis_limit、auto_vacuum增量迁移(v23);查询指标仪表化,db_size_bytes使用真实 DB 路径,busy 错误重试钩子 - 新
GET /api/recordings/daily-summary+DailyRecordingSummary:日历聚合;前端画廊网格分页加载更多,日历/画廊数据源分离 - 启动合并完整性扫描:重置磁盘上合并 MP4 缺失/空记录的
merge_status→ 回放回退到帧而非死胡同 404;/merged和延时下载os.Stat文件存在性校验 RollingMergeManager合并后校验:合并报告成功但输出 MP4 不存在时,阻止delete_original销毁源帧;准实时滚动合并带 backfill + 质量追踪;实时合并改用阻塞锁 + 重试(替代 try-lock)- 关键帧提取修复:
KeyframeExtractorCodecParamProvider从 live recorder 回取 SPS/PPS/VPS,修复 Xiaomi H.265 等只在 SDP/moov 发送参数集的相机永久合并失败
🚀 摄像头管理器无锁化重构
PR #56 — 用写时复制(COW)原子快照 + 每相机单飞(single-flight)守卫,根除粗粒度 cm.mu RWMutex 在慢 I/O(recorder Start/Stop、ONVIF 握手、磁盘写)下持锁导致的 UI 卡顿和格子卡死。
snapshot atomic.Pointer[snapshot]— 不可变原子发布视图(recorders/hubs/configs/failedStarts)apply(fn)— 单写路径(configMu 仅持有纳秒级 map 拷贝 + 磁盘写,绝不跨 I/O)withCameraLifecycle(id, fn)— 每相机sync.Mutex(sync.Map)串行化 start/stop/restart- 所有读(
GetRecorder/GetHub/Status/statusSnapshot/counts)均无锁 - 修复 ONVIF 握手非阻塞(
ONVIFRecorder.Start不再跨多秒 ONVIF+RTSP+HTTP 握手持r.mu,格子 latest-frame 轮询不再阻塞)
生产实测(Banana Pi M5,9 摄像头):
| 指标 | 之前 | 之后 |
|---|---|---|
/api/cameras 启动延迟 |
14.9s → 1.4s(卡顿) | 稳定 20-23ms |
latest-frame 轮询(格子热路径) |
重连期数百 ms | 稳定 1.5-2ms |
| Recorder 启动 | 串行(一慢全等) | 并行(同秒 4 路同启) |
| ONVIF 重启阻塞其他读 | 是(秒级) | 否(~20ms 稳定) |
🔧 FFmpeg 降级为可选依赖(纯 Go 默认路径)
PR #49 — FFmpeg 从唯一第三方二进制依赖降级为可选加速器。NVR 现在无需安装 FFmpeg 即可完整运行(录像、回放、直播、relay、timelapse、合并全可用),仅 H.265↔H.264 像素级转码需要它。
- 新
internal/mediaprobe包:纯 Go MP4 box 元数据探测(codec/duration/resolution/frame-count),比ffprobe -count_frames快 10-100× transcoding.GetMediaInfo:mediaprobe 优先,ffprobe 兜底;cleanup.probeDuration同样纯 Go 优先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 旧设备协议(7 款老型号)
PR #52 — 从 go2rtc 移植 TUTK (IOT) P2P 传输,支持使用 TUTK 而非 CS2 的老款小米摄像头。
internal/tutk/完整实现:crypto transforms(TransCode/XXTEA)、codec 常量、GenSessionID/ICAM/HL 工具、FrameHandler/Packet、Dial + Conn(TransCode + worker + idle timeout)、session0/16/25(含 5-deep ReorderBuffer 分片重组)、自定义 ChaCha20-Poly1305 DTLS cipher suite- 7 款老型号:Dafang(isa.camera.df3)、Xiaofang(isc5c1)、Aqara G2(g2h)、IMILAB A1、Loock V1 门铃、Xiaobai、Mijia
- MISS 协议增强:双向音频命令、PTZ 电机控制、设备信息(固件/硬件版本)、
wakeUpCamera(猫眼/门铃电池设备需 RPC 唤醒)、quality"auto"/"hd"/"sd"(修复 isa.camera.hlc8 拒 HD,go2rtc issue #2114)、型号专属时间戳怪癖、CS2 TCP 1s PING keepalive(对齐官方 Mi Home) - AGENTS.md go2rtc 追踪策略:审计触发器、移植流程、已知刻意分歧(Pop buffer 溢出我们丢旧帧优于 go2rtc 报错;hdr double-copy bug-for-bug 保留)
- 前端:双向音频 UI、Xiaomi PTZ 控件、设备信息面板
🛠️ 数据修复 CLI + 媒体探测加速
PR #59 / #61 — 按需数据修复工具,镜像 migrate-mjpeg 模式(--dry-run 默认 / --execute 应用)。
repair duration:重探测录像文件修复duration=0卡死,大文件用FastProbeDuration(仅读sttsbox,比全解析快 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,停在sttsbox 而非构建全样本表。生产结果:4285 片段修复 ~30 分钟(原预估 10+ 小时)- 文档:
docs/en+zh/troubleshooting.md新增"时间轴录像缺失"节
🎬 监控格子协议自动选择 + ONVIF 全自动化
PR #54 — 聚焦降低用户配置负担 + 完成 ONVIF 全自动化,顺带修复全部 4 个开放 issue(#36/#48/#50/#51)。
- 监控格子协议自动选择:格子现在按相机逐个选最佳流协议(codec 感知 + 浏览器能力感知),运行时失败级联
webrtc → flv → hls → mjpeg再降级快照;后端早有 codec 感知排名(GET /api/cameras/{id}/protocols),格子之前未消费 - ONVIF 全自动化:发现元数据(品牌/型号/序列号)持久化(之前显示后丢弃);真实流访问测试连接(GetStreamURI + RTSP DESCRIBE,非仅 HTTP HEAD);基于分辨率的 profile 选择(非盲选
profiles[0]);时间偏斜摘要认证修复(fork v1.1.6,修复海康认证);批量"全部添加" + subnet_hints UI 编辑器 + ProfileToken 持久化 - Live-only 模式(issue #36):
recording_enabled=false,recorder 保持连接供直播/relay 但不写分段;4 个 recorder 家族(H264/H265/MJPEG/HTTP-JPEG)全覆盖 - 友好错误提示:friendlyError + 15 个错误码翻译;音频按钮对纯视频相机隐藏;高级录像选项折叠;严重性感知 toast 时长
- 新文档:
streaming-protocol-selection.md(en+zh)— 4 层架构全解
🐛 关键修复
- 摄像头启动生命周期(PR #60):
POST /api/cameras/{id}/start之前把 HTTP request ctx 传给rec.Start,HTTP 响应返回时 ctx 取消,recorder run goroutine 经ctx.Err()分支静默退出 → 相机"启动后立即停止"。改传context.Background(),recorder 生命周期仅由自身Stop()管理 - 时区修复(PR #60):录像时间轴渲染偏移 8h(北京 16:00 录像显示在 08:00 刻度);timelapse 自然日(24h)合并窗口跨 UTC 日边界放大跨日分区 IO。后端
ParseMergeDuration上限 1h(旧 8h/24h/natural-day/7d/30d 静默降级 1h 并告警);前端parseDayStart返回本地午夜 - 录像时间轴截断(PR #60):碎片化严重的一天(Xiaomi 重连)产生 5000+ 分段,
asc+2000静默截断使下午显示空;上限 2000→10000 - AddCamera 去重(PR #56):之前仅按 ID 去重,auto-discover 生成随机 ID → 同一物理 ONVIF 设备注册两次(生产 777MB 冗余视频)。增加 endpoint + stable_id 去重
- AVI 录像(PR #52):MJPEG 相机的 AVI video-only muxer(替代 JPEG 目录);每相机健康隔离(一相机故障不再级联全局);SQLITE_BUSY 修复(DSN busy_timeout 15s + 增量对账 + 批量 DELETE)
- 合并音频配置不匹配(PR #52):G.711/Opus codec 守卫防止音轨损坏
- 数据竞争:
session0.gowriteAndWait、wsstream viewer-not-registered、xiaomi TestCS2ConnError、TestHealth/CameraForm relay 不稳定测试修复
🏗️ 代码规范化 — golangci-lint v2 全量落地
PR #52 + 多个 lint commit — 全面接入 golangci-lint v2.12.2,统一代码规范基线。
- v2 配置硬化:presets、warn-unused、timeout、max-issues、perfsprint strerror;nolint 策略
//nolint:<linter> // TODO(#issue): <reason>(nolintlint 强制) - 新 linter 接入:perfsprint(
fmt.Sprintf→strconv/hex/拼接)、nilerr、nilnesserr、wastedassign、fatcontext、intrange、usestdlibvars、predeclared(修min内建遮蔽) - 重构:
interface{}→any全量迁移 + 编译期断言;%w错误包装 +json.Marshal错误处理;writeError去重;manager.go(2109 LOC)拆分为 6 个内聚子文件 - CI:golangci-lint v2 接入 Makefile 和 CI;所有排除规则带行内理由文档
🔒 安全与依赖
- Go 和 npm 依赖全量升级到最新
gortsplib5.5.2 → 5.6.1,gortmplib0.3.2 → 0.4.0
📥 安装 / Install
# 一键安装
curl -fsSL https://raw.githubusercontent.com/Mi-Bee-Studio/MiBeeNvr/main/install.sh | sudo bash
# 或下载二进制(amd64 / arm64 / armv7)
# Docker
docker compose --project-directory . -f deploy/docker/docker-compose.yml up -dFull Changelog: v0.8.1...v0.9.0
English summary
This release (54 commits, 9 PRs) ships Hikvision-style ONVIF zero-touch auto-discovery + auto-enroll (dual-mode passive Hello listener + active Probe sweep, pending_activation workflow, SSE toasts), a storage rewrite (split read/write pools, query perf, merge integrity, recordings & timelapse hardening), lock-free COW snapshot registry for the camera manager (eliminates UI stutter; /api/cameras startup 14.9s→1.4s→23ms), FFmpeg demoted to optional (pure-Go default paths via internal/mediaprobe; NVR runs fully without FFmpeg, only H.265↔H.264 transcoding needs it), Xiaomi TUTK transport for 7 legacy models + go2rtc quality/wakeup/keepalive ports, a mibee-nvr repair CLI with FastProbeDuration (100× faster duration probe), surveillance grid per-camera protocol auto-selection (webrtc→flv→hls→mjpeg cascade), live-only mode, ONVIF full automation (digest time-skew fix, bulk add, profile selection), and full golangci-lint v2 adoption. Many production-validated fixes across camera lifecycle, timezone, merge integrity, and AVI recording.
Important: v0.9.0 is the last stable/compatible release since v0.1.0. The next major version will introduce breaking changes (precisely documented — migration paths, config deltas, impact scope; no silent breakage) alongside frontend simplification (removing low-usage features like the Recordings Gallery Grid View to reduce cognitive load and refocus on core scenarios: live monitoring, playback, AI events — the Timeline and List views will remain and be strengthened). The underlying architecture will be substantially refactored and the codebase normalized for long-term maintainability. If you need a long-term stable baseline, pin v0.9.0.