Skip to content

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 18 Jul 23:36
6644b1e

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_limitauto_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)
  • 关键帧提取修复KeyframeExtractor CodecParamProvider 从 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(仅读 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 而非构建全样本表。生产结果: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.go writeAndWait、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.Sprintfstrconv/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 依赖全量升级到最新
  • gortsplib 5.5.2 → 5.6.1,gortmplib 0.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 -d

Full 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.