MiBeeNvr v0.4.0: 音频录制管线与多层健康监控架构

v0.3.1 发布之后又肝了 196 个提交。v0.4.0 是一个功能密度很高的版本:音频录制管线、多层健康监控引擎、HLS/LL-HLS 播放稳定性优化、UI 大改版。完整更新列表见 GitHub Release Notes

上一篇介绍了 v0.3.x 的多协议流媒体和小米摄像头支持(v0.3.0 技术帖),没看过第一篇的可以先看 MiBeeNvr 介绍

音频录制:从静音到有声

v0.3.x 时代录制的 MP4 文件只有视频轨。v0.4.0 引入了完整的音频捕获和封装管线,支持 RTSP 摄像头的 AAC 音频和 ONVIF/小米摄像头的 G.711 音频。

音频管线架构

音频处理的核心挑战在于:不同协议的摄像头使用不同的音频编码,而最终的 MP4 容器需要统一封装。

mermaid
flowchart LR
    subgraph "RTSP 摄像头"
        A1[RTSP Source]
        A2[AAC Audio]
    end
    
    subgraph "ONVIF / 小米"
        B1[ONVIF/Xiaomi]
        B2[G.711 μ-law]
        B3[G.711 A-law]
    end
    
    subgraph "MiBeeNvr 音频管线"
        C[StreamHub 音频广播]
        D[MP4 Muxer]
    end
    
    subgraph "输出"
        E[MP4 Segment<br/>Video + Audio]
    end
    
    A1 --> A2 --> C
    B1 --> B2 --> C
    B1 --> B3 --> C
    C --> D
    D --> E
    
    classDef input fill:#E3F2FD,stroke:#1565C0,color:#1565C0
    classDef process fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef output fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
    
    class A1,A2,B1,B2,B3 input
    class C,D process
    class E output

每摄像头音频开关

音频录制默认关闭,通过 audio_enabled 配置逐摄像头开启:

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
cameras:
  - id: "front-door"
    name: "门口"
    protocol: "rtsp"
    encoding: "h264"
    audio_enabled: true
  - id: "baby-room"
    name: "婴儿房"
    protocol: "xiaomi"
    encoding: "h264"
    audio_enabled: true
  - id: "driveway"
    name: "车道"
    protocol: "rtsp"
    encoding: "h265"
    audio_enabled: false   # 不需要声音的摄像头

G.711 音频封装

G.711(μ-law 和 A-law)是 IP 摄像头和小米摄像头的常见音频编码。MP4 容器本身不直接支持 G.711,所以 v0.4.0 使用了自定义 box type(ulaw / alaw)来封装 G.711 音频帧:

  • AAC 音频 → 标准 mp4a box
  • G.711 μ-law → ulaw box
  • G.711 A-law → alaw box

这种方案的优点是不需要转码,直接把原始 G.711 帧塞进 MP4,零 CPU 开销。缺点是某些通用播放器可能不认识自定义 box,但 MiBeeNvr 的 Web 播放器能正确处理。

StreamHub 音频广播

v0.3.1 引入的 StreamHub 在 v0.4.0 中扩展了音频支持。录制器不仅广播视频帧,还同时广播音频帧。所有实时消费者(WebRTC、HTTP-FLV 等)都能获得音频数据,实现"录像有声音,实时预览也有声音"。

合并时的音频保留

segment 合并流程也做了适配,确保音频轨在合并后完整保留。之前合并只处理视频轨,现在会同时检查并保留音频轨信息。

多层健康监控:不只是"连没连上"

健康监控是 v0.4.0 最复杂的子系统。internal/health/ 包含 17 个源文件,覆盖了从连接检测到自动恢复的完整链路。

三层检测模型

mermaid
flowchart TB
    subgraph "Layer 1: 连接层"
        A[Connection Monitor]
        A1[RTSP 会话状态]
        A2[ONVIF 响应检测]
        A3[CS2 P2P 心跳]
    end
    
    subgraph "Layer 2: 码流层"
        B[Stream Stats Collector]
        B1[帧率统计]
        B2[码率监测]
        B3[关键帧间隔]
    end
    
    subgraph "Layer 3: 画面层"
        C[Freeze Detector]
        C1[画面冻结检测]
        C2[黑屏检测]
    end
    
    A --> A1 & A2 & A3
    B --> B1 & B2 & B3
    C --> C1 & C2
    
    A --> B --> C
    
    classDef layer1 fill:#E3F2FD,stroke:#1565C0,color:#1565C0
    classDef layer2 fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef layer3 fill:#FFEBEE,stroke:#C62828,color:#B71C1C
    
    class A,A1,A2,A3 layer1
    class B,B1,B2,B3 layer2
    class C,C1,C2 layer3

每层检测独立运行,有各自的阈值配置。检测结果汇总到健康评分引擎(health_score.go),生成一个综合分数。

健康评分引擎

评分引擎为每个摄像头维护一个实时健康分数,综合考虑三层检测结果:

  • Layer 1 权重最高 — 连接断了什么都不用说
  • Layer 2 次之 — 码流异常意味着画面质量下降
  • Layer 3 兜底 — 连接和码流都正常但画面冻住了,可能是摄像头本身的问题

每层都有独立的阈值和冷却时间,避免短暂抖动触发不必要的告警。

自动恢复引擎

检测到问题之后怎么办?auto_remediate.go 实现了一个带安全边界的自动恢复引擎:

mermaid
flowchart LR
    A[健康检测异常] --> B{是否在安全边界内?}
    B -->|是| C[指数退避重试]
    B -->|否| D[停止恢复<br/>标记为故障]
    C --> E{恢复成功?}
    E -->|是| F[恢复正常<br/>重置计数器]
    E -->|否| G[增加退避时间]
    G --> B
    
    classDef detect fill:#E3F2FD,stroke:#1565C0,color:#1565C0
    classDef decision fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef success fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
    classDef failure fill:#FFEBEE,stroke:#C62828,color:#B71C1C
    
    class A detect
    class B,E decision
    class C,F success
    class D,G failure

关键设计决策:

  1. 安全边界 — 最多连续恢复 N 次,超过后停止,避免无限循环
  2. 指数退避 — 重试间隔逐渐增大(1s → 2s → 4s → 8s → …),不会把设备或网络搞崩
  3. 告警冷却 — 同一摄像头在冷却时间内不重复告警,避免告警风暴

健康历史与可视化

健康事件持久化存储,Web 界面提供时间线可视化:

  • 每个摄像头的健康事件历史
  • 异常发生时间和恢复时间
  • 摄像头卡片上的实时健康指示灯

健康 API 端点:

  • GET /api/health — 系统整体健康状态
  • GET /api/cameras/{id}/health — 单个摄像头健康详情

HLS/LL-HLS 稳定性优化

这次对 HLS 播放做了大量打磨,特别是针对树莓派等低性能设备。

IDR 帧等待

之前录制器可能在任意帧开始写 segment,导致播放器拿到一个不以关键帧开头的 segment —— 表现为黑帧。v0.4.0 改为等待下一个 IDR 帧才开始写入新 segment,牺牲一点 segment 边界精度,换来的是每个 segment 第一帧就是可解码的画面。

基于信用的帧率限流

引入了 credit-based FPS throttling 机制。核心思想是给消费者一个"帧预算",消费者消费一帧就减少一个信用,生产者只在有信用时才发送新帧。这让帧传递节奏更平滑,避免低性能设备上突发帧导致卡顿。

LL-HLS 参数调优

针对树莓派上的播放稳定性,调整了两个关键参数:

  • backBuffer:0.5 → 2.0 秒 — 更大的回放缓冲区
  • liveSync:2 → 3 秒 — 更宽松的直播同步距离

这两个参数的调整让树莓派上的 LL-HLS 播放从"经常卡顿"变成了"基本流畅"。

子流降级

子流(sub-stream)失败时自动回退到主流。之前子流断了就直接停止播放,现在会尝试切换到主流继续,虽然分辨率可能降低但至少画面不断。

UI 大改版

摄像头页面 Tab 导航

摄像头页面重构为 Tab 导航:

  • Active — 正在录制的摄像头
  • Archived — 已归档的摄像头,展开式录像列表

之前活跃和归档摄像头混在一个列表里,现在分类清晰多了。

设置页面流媒体专区

设置页面的 Advanced 标签新增了流媒体协议配置专区,包括 WebRTC、HTTP-FLV、RTMP、SRT 的详细配置项。

健康历史页面

新增健康历史页面,支持中英文切换(i18n)。时间线可视化展示每个摄像头的健康事件。

其他改进

ARMv7 支持

新增 ARMv7 二进制构建,覆盖树莓派 2/3 等老设备。现在 Docker 镜像支持三种架构:

架构适用设备
linux/amd64PC、服务器
linux/arm64树莓派 4/5、Mac M 系列
linux/arm/v7树莓派 2/3

ONVIF 编码自动检测

添加 ONVIF 摄像头时,自动检测编码格式(H.264/H.265),不需要手动选择。

小米云端同步

新增 Xiaomi cloud sync 端点,可以同步摄像头元数据(名称、型号等),省去手动填写的麻烦。

安全加固

延续 v0.3.0 的安全加固传统,v0.4.0 又加固了:

  • health/readyz 端点的速率限制
  • 下载/FTP/WebDAV 的路径穿越防护
  • 摄像头 URL 和 ONVIF IP 的输入验证
  • SQL LIKE 注入防护
  • 初始化密码最低 8 位
  • 未配置密码时的认证绕过防护

质量保障

1651 个测试通过,60.7% 覆盖率。这个版本新增了健康监控的完整测试套件(连接检测、评分引擎、自动恢复),以及 HLS 播放的端到端测试。

总结

从 v0.3.1 到 v0.4.0,196 个提交,73 个新功能,54 个修复,21 个重构,9 个测试,16 个文档。

音频录制补上了 MiBeeNvr 最大的功能短板,健康监控系统让它在无人值守场景下更可靠,HLS 优化让低性能设备上的播放体验有了质的提升。

如果说 v0.3.x 是在解决"能不能用"的问题(多协议、小米摄像头),那 v0.4.0 开始解决"好不好用"的问题 —— 有声音、能自愈、不卡顿。

开源地址:

如果你在找一个轻量级、能录音、能自愈的开源 NVR,v0.4.0 值得试试。