一句话总结:这是 MiBeeNvr 自 0.8.0 以来最大的一次发版,主题不是"加功能",而是把底层清干净——H.265 全链路打通、Timelapse 重写到 v3、MJPEG 低延迟化、认证安全现代化,以及一轮贯穿 config / DI 根 / API 路由 / 前端播放器的架构清理。这一切,都是为后续sidecar AI 路线铺路。
⚠️这是 Preview 版(Release Candidate),不建议用于生产。含破坏性变更,升级前务必阅读文末"破坏性变更"一节与官方升级指南。
本次发版包含54 次提交、273 个文件,变动量+27,246 / −12,719行(feat 9 / fix 29 / refactor 9 / test 2 / cleanup 2 / docs 2 / perf 1)。
为什么是这个时候做重构?
v0.9 把累积的工程债一次还清 之后,NVR 主线要往两个方向走:引入更强的 AI 能力、接入更多摄像头。这两件事都要求主进程结构清晰、协议可插拔、AI 推理不阻塞视频管线。
前面几篇调研里讨论的取舍,在这版开始落地:
- H.264/H.265/H.266 Web 前端播放选型
- 流媒体传输协议全景
- NVR 远程访问 P2P 技术方案全面调研
- P2P 信令与中继服务器技术调研
- SD/TF 卡选购分析
先说清楚一件事:v0.9 帖里预告过——v0.9.x 是自 v0.1.0 以来最后一个稳定兼容版本,下一个大版本会引入破坏性变更,并精确告知迁移路径。这版兑现了那个承诺:所有破坏性变更都列在文末,给了 grep 命令和迁移步骤,没有静默破坏。
一、H.265 全链路打通(直播 + 录制 + 合并 + 回放)
0.9.x 之前 H.265 是个半成品状态:直播依赖 WebCodecs,而 WebCodecs 需要 HTTPS secure context,局域网裸 HTTP 直接放不出来;合并产物在 Edge 上经常黑屏;延时摄影的合并根本不工作。这版把整条 H.265 链路——直播、录制、合并、回放——端到端修通了。
1.1 直播侧:libde265 WASM 解码器
最大的变化是引入了@yume-chan/libde265这个 WASM 解码器。纯 HTTP 的局域网也能播 H.265 直播,不再绑死 HTTPS。前端做了一个自动探测:HTTPS 环境优先走 WebCodecs(拿到硬件加速),不可用时回退 WASM 软解。
WebCodecs 是最优解但有 secure context 门槛,WASM 是兜底,这版把两者串成了一条带降级的链。
1.2 前端播放器重构:Player Orchestrator 状态机
光有解码器还不够。多路 H.265 网格(grid)在浏览器里会触发一种很恶心的**“冻结风暴”**——一路解码卡住,整个 grid 的 frame 轮询全被拖死。这版把前端播放器抽象成了一个可测试的状态机——Player Orchestrator,代码在web/src/lib/player/。
状态机有三个状态:
ok:稳定运行 30 秒以上;degraded:检测到解码异常但不立刻动手,先观察8 秒防抖(避免网络抖一下就切协议);failed:确认挂了,立刻降级到候选链里的下一个协议。
webrtc → flv → hls → mjpeg全部降级失败就退化为静态快照。这个8s 防抖窗口是踩坑踩出来的——太短会被瞬时网络抖动误触发,太长用户盯着卡画面干等。Player Orchestrator 被设计成 DOM 无关的纯逻辑模块,可以在不启动 jsdom 的情况下做单元测试,dispatch.ts里详细记录了 Svelte 5 同步发射的陷阱和绕法。
1.3 链路剩下的几块(时序与数据一致性)
- 编码持久化——“probe once, persist forever”。ONVIF 相机的编码探测结果以前每次启动都要重探,探测结果和实际流的编码不一致时会出现黑屏和协议风暴。现在探测一次双写到 YAML + DB,后续启动直接读。
- 参数集时序修复——SPS/PPS/VPS 的捕获挪到了
RecordEnabledgate 之前。之前的时序是先判断是否启用录制、再抓参数集,结果 live-only(不录制)模式下参数集永远抓不到,回放黑屏。 - 回放路由——按运行时编解码 + 权威的 MSE 探测来决定路由,H.265 强制走 WASM 而不是错误地走 MSE 路径。
- 延时合并可播放——修正了 hvcC box 的
numNalus;用保守的 Main-tier 默认值(不去解析可能自相矛盾的 SPS,这一步解决了 Edge HEVC 扩展拒绝播放的问题);周期合并现在正确区分 H.265/H.264 帧——之前只收集 JPEG,H.265 相机的延时合并必然失败。
二、Timelapse v3 重写
延时摄影系统这版整体重写到了 v3。v0.7 那次重写 把合并路径从 JPEG 序列改成了纯 Go 的 NAL→MP4 muxer,但合并窗口还是有个1h 硬上限——8h/12h/24h/7d/30d/natural-day这些值在代码里被静默 clamp 到 1h。v3 把这个限制真正去掉了。
v3 的几条主线:
- 合并窗口解禁。
natural-day/8h/12h/24h/7d/30d现在真正生效,不再 clamp。默认值从1h改成natural-day(本地午夜对齐的 24h 窗口)。natural-day的时区对齐也修了——之前Local时区在某些配置下会让窗口偏移。 - 周期合并 DB 记录。新增
timelapse_merges表(schema v28→v29),每个长窗口的合并输出都有完整的 DB 记录:相机、窗口、时长、codec、帧数、状态。前端通过/api/timelapse/merges*端点发现、播放、删除合并产物。以前这些中间产物是游离的文件,没有结构化元数据。 - codec 感知播放。合并端点响应里带
X-Timelapse-Codec头,前端据此选择播放器——H.264/H.265 用<video>元素,MJPEG 用 JPEG 帧轮播。 - 连续合并链。周期合并会折叠掉之前的中间产物,避免无限累积。如果要留中间文件做调试,有可选的
retain_intermediate_mp4配置(默认 false,每天能省 ~1.5GB/相机)。 repair remerge-h265CLI。专门修历史上 Edge 播不了的 H.265 延时合并。
三、MJPEG 低延迟化 + WebRTC 跨网
ESP32 MiBeeCam 这类 MJPEG 相机以前走 HTTP 轮询,每帧一次请求,延迟和连接数都下不来。这版改成了WebSocket 流式传输(wsstream),单连接持续推帧,延迟和连接开销同时降下来。
WebRTC 这边加了streaming.webrtc.ice_servers配置——STUN/TURN 服务器列表。以前 WebRTC 只能局域网用,现在配上 ICE 服务器就能跨网访问了。远程访问的具体方案选型(自建信令、商用 P2P 方案、白标 P2P 的安全红线)之前在 NVR 远程访问 P2P 调研 和 P2P 信令与中继服务器调研 里详细对比过,这版把 ICE 配置层先接上。
四、架构大清理(真正的主线)
这一节是这次发版的真正主线,也是标题里"为 sidecar AI 铺路"的落点。重构集中在三个地方:
- config 按子系统拆分。以前所有配置挤在一个文件里(1946 行),camera / merge / timelapse / transcoding / streaming / health / ingest / ai / server 这些子系统的配置互相穿插。现在每个子系统独立成文件(入口只剩 155 行 + 13 个子系统文件),入口只负责加载和拼装。public API 全部保留,运行时行为零变化。
- DI 组合根拆分。
pkg/app/run.go是依赖注入的组装点,以前把 helper 逻辑和路由组装混在一起。现在拆出独立的helpers.go和router.go,组合根只做"把谁注入给谁"这一件事。 - 路由按领域注册。以前所有 API 端点在一个
Routes()方法里集中注册(685 行的 god-method)。现在拆成 14 个按领域分的注册器——摄像头、录像、延时、转码、流媒体、设置各有自己的注册函数,入口循环调用它们。端点零丢失。
拆分本身不改变运行时行为,但改变了"加新东西要动哪里"这个问题的答案。以前加一个新协议、新 AI 能力,要在那一个巨大的 config 里找位置、在 god-method 里塞端点;现在每个领域是独立文件,改动收敛在自己的子系统里。这是 sidecar 路线能推进的前提——主进程内部结构清晰,外部能力才能干净地挂上去。
配套的工程改动:
- API 带宽优化:gzip 压缩(实测体积下降一个数量级)、ETag(
If-None-Match→304)、view=summary轻量端点、录像列表 limit clamp(默认 50 / 最大 500)、CameraRow字段omitempty。前端 grid 轮询的流量大幅下降。 - Settings 信息架构重写:左侧边栏导航 + 统一保存 + 破坏性操作确认。修了切换分类丢未保存更改的回归。
- 前端 merge-utils 提取:合并工具函数从路由组件挪到
lib/,路由组件不再背业务逻辑。 - 录像页移除 gallery 视图:这是 v0.9 预告过的——默认时间轴,聚焦核心场景。
- Surveillance 网格拖拽排序:监控网格支持拖拽重新排列摄像头单元,顺序持久化。
清理里两个值得单独拎出来的:
4.1 TUTK dtls 死代码删除
一套完整的 ChaCha20Poly1305 DTLS 实现(1420 行),确认零引用后整段删掉。TUTK 协议本身的 DTLS 用的是另一个路径,这套实现是早期实验遗留。死代码删干净,后面接 sidecar 协议适配层时就不会踩到这些地雷。
4.2 AIINVALID_PROTOBUF根因
这个 bug 花了最多时间。最初的怀疑方向是 CDN 截断了模型下载,但真正的 root cause 是streaming-gzip 中间件给二进制响应追加了错误的 gzip trailer。AI 模型文件是二进制 protobuf,经过 gzip 中间件时被错误地"补全"了 trailer 字节,导致 ONNX Runtime 解析失败。修复是4 层防御:
- 中间件 root cause——二进制 content-type 不进 gzip。
- 下载层——retry + Range 请求 + 原子写。
- Docker 镜像——预置 AI 模型,运行时不再下载。
- 前端缓存自愈——检测到损坏自动重下。
详细 postmortem 在docs/known-issues-ai-onnx-gzip-trailer.md。这个修复对 sidecar 路线有直接意义——AI 推理挪到 sidecar 之后,模型分发和完整性校验就是核心问题,这版的 4 层防御是基础。
4.3 CI flake 根治
goroutine 生命周期管理(startup-bg service + backfillWg + service-layer goroutine joins)消除了 TempDir 清理竞态,全量go test ./...第一次做到一次性 0 失败。CI 升级到 Node 24 actions。这些看着不起眼,但 sidecar 架构会引入更多并发进程和异步生命周期,CI 不稳根本没法迭代。
五、关键稳定性修复
挑几个跨模块的修复单独列:
- 合并 MP4 泄漏——以前删录像只删 DB 行和源帧,合并产物 MP4 留在磁盘上不管。这版删录像时同步删合并产物,并新增
repair reclaim-orphan-mergesCLI 清理历史泄漏。升级后建议跑一次--dry-run看看能回收多少空间(默认 20ms 删除间隔,对 USB-HDD 友好)。 - 录像时间线截断——新增轻量级
/api/recordings/timeline端点,避免全量加载。Xiaomi 频繁重连一天能产生 5000+ 碎片,全量加载直接卡死。 - autodiscover 稳定化——IP 漫游去重、未变端点的 recorder 风暴、endpoint URL 规范化(scheme/host/port/trailing-slash),经过 4 轮修复后趋于稳定。
- 启动扫描阻塞——异步启动扫描 + 有界 reconcile,8 分钟阻塞 → 8 秒启动。
- 合并回填积压——退役历史 singleton 解除回填循环阻塞,8500+ pending 排空。
- ONVIF
recording_enabled=false静默录制、MJPEG 迁移 OOM(限制迁移帧数)、平台感知 AVI 分段时长(≤2GB RAM→30s,>2GB→5min)。
六、破坏性变更(升级前必读)
完整迁移步骤见 官方升级指南。挑关键的:
1. 配置:组合协议串被拒(硬错误,阻塞启动)
0.10.0 启动时直接拒绝旧格式的组合协议串。先 grep 检查:
grep-nE'protocol:\s*".*_(h264|h265|mjpeg|jpeg)"'/path/to/mibee-nvr.yaml命中的行必须拆开:
# ❌ 旧格式(0.9.x 接受,0.10.0 拒绝)-id:"front-door"protocol:"rtsp_h264"# ✅ 新格式-id:"front-door"protocol:"rtsp"encoding:h264适用所有带下划线的 protocol 值:rtsp_h264/rtsp_h265/rtsp_mjpeg/http_jpeg/onvif_jpeg等。
2. DB schema v28 → v29
recordings.merged列被删除(迁移前自动VACUUM INTO备份到<db>.pre-v29-backup,merged=1的行会先更新到merge_status='merged'作为安全网)。< v0.9.x 不支持直升 0.10.0,必须先升到 0.9.x。
3. API 端点移除
GET /api/timelapse/{id}/thumbnail和/preview返回 404,替换为/api/timelapse/merges系列。外部脚本需要迁移,NVR 自己的前端已经切到新端点。
4. 默认值变化
cleanup.disk_threshold_percent95→85(避免 HDD 在 90% 满之后的性能悬崖);timelapse.merge_duration默认"1h"→"natural-day"(注意滚动窗口合并merge.rolling_window仍 cap 在 1h)。
5. 升级后建议
跑一次mibee-nvr repair reclaim-orphan-merges --dry-run检查历史泄漏的合并 MP4。
七、Sidecar AI 架构愿景
这一节讲方向,不展开实现——具体设计会在后续专题里写。
前面那些架构清理不是为清理而清理。NVR 往下走有两个明确方向:引入更强的 AI 能力(更复杂的检测模型、多模态推理、事件触发)、接入更多摄像头(更多协议、更多厂商、更多边缘场景)。这两个方向有一个共同约束:AI 推理和协议适配都不能拖累主进程的视频管线——录像、直播、合并这些核心功能必须保持稳定低延迟。
sidecar 模式的核心思路是:主进程保持轻量和稳定,把可变的能力(AI 推理、协议适配、数据 enrichment)以独立进程/容器的方式挂在旁边,通过明确定义的接口通信。这种结构在服务网格里被验证过——Envoy、Linkerd 这些都是 sidecar 模式。NVR 场景的特殊性在于,视频帧是高吞吐数据流,sidecar 和主进程之间的数据通道要专门设计。
这张草图说明三件事:
- 主进程保持轻量——录像、直播、合并这些核心视频管线不动,AI 推理挪出去。
- AI 和协议适配是独立 sidecar——可以独立升级、独立扩缩、独立崩溃不影响主进程。新接一个厂商的私有协议,加一个协议适配 sidecar 就行,主进程代码不碰。
- 两条通道分离——帧总线走高吞吐数据流(共享内存或 Unix Domain Socket),控制/健康通道走配置下发和指标上报。两条通道的 QoS 要求完全不同,物理上分开。
为什么这版的架构清理是前置条件?因为 sidecar 要能挂上去,主进程内部必须先做到领域边界清晰——config 按子系统拆分、路由按领域注册、认证无状态化(HMAC token 不占主进程内存)。这些做完,sidecar 的接入点才干净。AI 模型分发的 4 层防御(gzip 中间件 root cause 那个)也是同一件事的基础——sidecar 场景下模型完整性校验会更关键。
具体 sidecar 的进程间通信协议、部署形态(同机进程 vs 容器 vs Pod)、AI 模型分发机制,会在后续专题里展开。这版先把主进程的底子打牢。
八、升级
# 一键安装(amd64 / arm64 / armv7)curl-fsSLhttps://raw.githubusercontent.com/Mi-Bee-Studio/MiBeeNvr/main/install.sh|sudobash# Docker(镜像已内置 AI 模型,无需运行时下载)dockercompose --project-directory.-fdeploy/docker/docker-compose.yml up-d这是 preview 版,不建议用于生产。升级前务必:备份 DB;grep 检查组合协议串;读一遍 升级指南。长期稳定部署建议继续锁定 v0.9.x,等 0.10.0 正式版出来后再升级。
相关链接
- MiBeeNvr GitHub
- v0.10.0-preview.1 Release Notes
- 升级指南(中英双语)
- MiBeeNvr v0.9.0 → v0.9.1 发布帖
- MiBeeNvr v0.7.0 发布帖
- H.264 / H.265 / H.266 Web 前端播放选型
- 流媒体传输协议全景
- NVR 远程访问 P2P 技术方案全面调研
- P2P 信令与中继服务器技术调研
- SD/TF 卡选购分析
本文由 MiBee 开源项目实践系列整理,原文发布于 Mi&Bee Blog,Release Notes 同步于 GitHub。转载请注明出处。