MiBee Eye 蜂眼:把闲置的 Linux 小板做成一台能进国标平台的摄像头

国内做视频接入,绕不开两条协议线。一条是 ONVIF,摄像机被 NVR 自动发现、拉流,靠它。另一条是 GB/T 28181,视频进各类行业平台项目,靠它。

ONVIF 设备端写起来还算直白,SOAP 服务加 WS-Discovery,把 Device / Media / Imaging 三个服务实现出来就行。GB/T 28181 设备端麻烦得多:SIP 事务和摘要认证要自己维护,MANSCDP 那套 XML 查询要一条条应答,实时流和回放流都得打成 PS 再封进 RTP,回放还得支持平台中途发来的暂停、恢复、拖动、变速,另外还有抓拍指令和心跳超时。一套能过平台测试的设备端代码,工作量不亚于写一个小型服务。

另一边,手边常常有闲置的板子。旧的树莓派、工控板、带 MIPI CSI 接口的国产 SoC 小板,甚至一台常开的 NAS。板子有,摄像头模块也有,缺的是把它们变成"平台能认的设备"的那层软件。

MiBee Eye(蜂眼)做的是这一层:把一个 Linux 板变成一台 ONVIF Profile S + GB/T 28181 设备端的网络摄像头。项目给了两个独立实现,Go 版和 Rust 版,两边实现同一套协议、同一套 Web API,NVR 那边看不出区别。

它是什么

设备起来之后对外暴露三个端口:

端口 用途
8080 ONVIF 设备服务(Device / Media / Imaging)+ WS-Discovery
8554 RTSP 视频流,RTP over TCP 交错或 UDP
8088 内嵌 Web 管理界面,中英文双语

具体能力:

  • ONVIF Profile S 设备端:Device、Media、Imaging 三个 SOAP 服务,加 WS-Discovery 组播宣告,NVR 不用手工填地址就能发现它。
  • GB/T 28181 设备端:SIP 注册(UDP/TCP)+ 摘要认证,Catalog / DeviceInfo / RecordInfo 查询,实时流、回放流、下载流的 RTP-PS 输出,SIP INFO 回放控制(暂停、恢复、拖动、变速),平台抓拍指令应答。
  • GB 35114 A 级(可选):SM2 证书做注册认证,keyed-SM3 做完整性校验,编译期开关控制。
  • 本地连续录像:H.264 分段落盘,配 index.jsonl 索引,支持保留天数与容量上限。这份录像同时是 GB28181 回放流的源。
  • 推流与快照:RTMP 推到云端;HTTP GET 取 JPEG 快照。Go 版另有纯 Go 的 MPEG-TS 分段器,浏览器直接放 HLS,不依赖 ffmpeg。
  • 可选 AI 检测:NanoDet-Plus 的 ONNX 模型在板子上跑目标检测,结果走 GET /api/detections 和 SSE 事件推给前端,视频不出设备。
  • 图像调节:亮度、对比度、饱和度、锐度、白平衡、曝光模式,水平垂直翻转。Rust 版另有 OSD 水印,自定义文字加实时时钟烧进每一路输出。

设备内部的数据流是这样的:

mermaid diagram

所有消费者共用同一份编码后的帧。AI 检测是其中一个订阅者,抽帧走的是旁路,不参与采集和推流,检测慢了或者模型加载失败都不影响出流。

协议不写在应用里:四个可以单独用的库

上面这些能力里的 ONVIF 与 GB28181 协议逻辑,都不长在应用代码里,而是被拆成了四个独立的库:Go 一套、Rust 一套,ONVIF 一个、国标一个。四个都是 MIT 许可,比这两个设备实现自己用的 Apache-2.0 更短。

mermaid diagram

语言 最新 tag 覆盖角色
onvif-go Go v2.0.0-rc6 ONVIF 客户端 + 虚拟摄像机服务端
onvif-rs Rust v0.6.0 ONVIF 设备侧服务端
gb28181-go Go v0.10.0 国标设备侧 + 平台侧 + 多级级联
gb28181-rs Rust v0.11.0 国标设备侧

onvif-go 是双侧的,既能当客户端去控别人的摄像机,也能起一个虚拟摄像机服务端。配套有预编译好的命令行工具(设备发现、诊断、快速上手、服务端模拟),手上没有硬件要联调的时候很有用。它的 go.mod 里只有模块声明和 Go 版本两行,零第三方依赖,这一点可以直接翻文件核对。设备兼容性覆盖海康、大华、Axis、博世、ESP32 这类常见 ONVIF 设备。v2 是一次破坏性重构,仓库里带 MIGRATION.md。

gb28181-go 是四个里覆盖面最广的:设备侧(UAC)、平台侧(UAS),以及多级级联(平台挂平台)。级联部分做了环路防护,用 OriginDeviceID 标记每个通道的来源,上级平台不会收到自己被下级回传的通道,对应的 INVITE 直接回 404,NVR 挂 NVR 时的信令和媒体回环就被掐掉了。2022 版本的对齐也在这条线上:五组新的信息查询进了 MANSCDP 编解码,golden 用例钉在标准原文附录上;X-GB-Ver 版本协商(附录 I)两侧可选择性开启;对照标准文本发现并修正了一个线格式错误,SVAC 音频的 stream_type 从 0x81 改成 0x9B。

gb28181-rs 是设备侧实现,和 Go 版共享同一套线格式 golden 契约。前面那台 Rust 摄像头的国标能力就是它提供的。

onvif-rs 是设备侧服务端,发布到 crates.io 时用的名字是 onvif-device-rs,因为 onvif-rs 这个名字被 2018 年的一个废弃占位占着。它的响应 XML 逐字节稳定,用 golden 字符串测试钉住,这个做法就是冲着严格解析器去的。

测试数 代码规模
onvif-go 723(693 测试 + 25 基准 + 5 模糊) 177 个 .go 文件,55,284 行
gb28181-go 574(563 + 7 + 4) 161 个 .go 文件,39,695 行
onvif-rs 174 21 个 .rs 文件,8,922 行
gb28181-rs 218 40 个 .rs 文件,16,876 行

四个库加起来 1,689 个测试。Go 侧的行数含测试文件:onvif-go 的 88 个测试文件占 27,156 行,gb28181-go 的 89 个测试文件占 21,114 行。Rust 侧除了 tests/ 目录,源码里还有内联测试模块。两个 Rust 库声明的 MSRV 都是 1.80,开 GB 35114 特性需要 1.85 以上。

这四个库可以直接用在你自己的产品里。 如果你在写 NVR、在写平台侧,或者需要一套设备模拟器做联调,不必先接受一台设备。国标这条线上,Go 库设备侧和平台侧都有,Rust 库只有设备侧。

还没到成熟期的部分也如实写:

  • onvif-go 仍是候选版。 最新 tag 是 v2.0.0-rc6,v1 到 v2 是破坏性重构,升级要看 MIGRATION.md。
  • onvif-rs 的动作集是"配 NVR"范围。 v0.1.0 的说明写明没有 GetSnapshotUri、没有 Events,第三方客户端兼容性除 NVR 类消费方外没有验证。
  • gb28181-rs 只做设备侧,没有平台角色;gb28181-go 的平台侧也不做 2022 版的 HTTP 媒体面(录像 HTTP 检索)。
  • 两个国标库的订阅、告警、位置上报、云台指令接收都还没实现。 这正是前面说的 v0.3.0 范围,按"协议语义先落库、产品仓只做接线"的顺序推进。
  • 四个库都年轻。 onvif-go 于 2026-07 建仓,其余三个 2026-08 建仓,star 数在 0 到 4 之间。维护节奏与响应速度我不做承诺。

国标这条线现在走到哪

设备侧和平台之间的交互链路是这样的六步,目前都能跑通:

mermaid diagram

按 GB/T 28181-2022 的设备侧能力项逐条清点,19 项矩阵的状态如下(拉取日期 2026-09-15,来源是仓库里的 v0.3.0 路线文档):

能力项 当前状态 计划
注册 / 心跳 / 摘要认证(MD5、SHA-256、qop) 都可用
信息查询组(Catalog / DeviceInfo / DeviceStatus / RecordInfo / Keepalive) 都可用
实时 / 回放 / 下载 INVITE + RTP-PS + SIP INFO 回放控制 都可用
平台抓拍指令应答 都可用
GB 35114 A 级(SM2 注册认证 + keyed-SM3) 都可用
X-GB-Ver 2022 版本协商(附录 I) 仅 Go 可用 v0.3.0 补 Rust 侧
2022 信息查询最小应答(PTZPosition / SDCardStatus 等) 仅 Go 可用 v0.3.0 补 Rust 侧
SUBSCRIBE / NOTIFY 订阅框架(目录、告警、位置三类) 未实现 v0.3.0
Alarm 告警订阅与上报(设备向平台 NOTIFY) 未实现 v0.3.0
MobilePosition 位置订阅与上报 未实现 v0.3.0
PTZ 远程控制接收(A.3 PTZCmd 解码) 未实现 v0.3.0
HomePosition / 预置位 / 巡航(A.2.4.x 查询组) 未实现 v0.3.0
语音广播通知 + 语音对讲 各半侧可用 v0.3.0(Go 补对讲接收)
DeviceConfig 最小合规(SetTime / Restart / ConfigDownload) 未实现 v0.3.0
告警与检测事件的 Web 透出(SPEC v1 事件通道) 未实现 v0.3.0(产品侧)
DeviceControl 子命令组(IFrameCmd / RecordCmd / GuardCmd 等) 未实现 v0.3.0
回放 / 下载结束的 MediaStatus INFO(§9.4.2) 未实现 v0.3.0
对讲上行(设备向平台 G.711 发送半边) 未实现 v0.3.0
优雅注销(下线时 REGISTER Expires:0) 未实现 v0.3.0

读表的方式:19 项里两实现都能用的是 5 项,只有 Go 侧能用的是 2 项(2022 版本协商和 2022 信息查询最小应答),剩下 12 项排在 v0.3.0。语音广播与对讲那一行是两边各有一半,Rust 侧能收对讲音频、Go 侧能接广播回调,缺的另一半也在 v0.3.0 里补。

明确不做的是三项,理由写在路线文档里:SVAC 与 GB 35114 B/C 级需要 SVAC 硬件编解码(GB/T 25724),软件方案做不到,硬做等于假合规;SIP over WebSocket 不是设备侧主流需求;平台(UAS)角色不做,这个项目的定位是设备端。另外 2022 版的 HTTP 媒体面(录像 HTTP 检索)实现面太大,评估后顺延到 v0.4.x,不塞进这个版本凑数。

顺带说一句版本节奏:两个实现的 minor 版本同步发布,同版本号、同日打 tag、发布说明互链;patch 版本各自独立。这个约定是为了让选型的用户不纠结"我这台板子该跟哪个版本"。v0.2.0 的两个 tag 都打在 2026-09-13。

国密这块能承诺什么

GB 35114 A 级的实现是:用 SM2 证书替换默认的摘要认证做 REGISTER,用 keyed-SM3 校验信令与数据的完整性。编译期可选,Go 侧加 -tags gb35114,Rust 侧加 --features gb35114

需要说清楚边界。A 级管的是设备和平台之间的身份认证与完整性,满足这个层级的项目做到这里就够了。B 级和 C 级要签视频流,前提是 SVAC 硬件编码,本项目不做,也不打算做。另外这是"提供了一个合规起点",不代表已经通过任何第三方检测。项目仓库里也没有检测报告,我不做这类承诺。

摘要认证本身是完整实现的,MD5 和 SHA-256 都支持,含 qop 参数,这是 GB/T 28181-2022 相对早期版本的一个变化。

存量改造:不动摄像机,动接入方式

Go 版有一个模式值得单独说:camera.mode: rtsp。配置成这个模式后,它从一条已有的 RTSP 流拉数据,然后把这路流重新发布成一台 ONVIF + GB28181 设备。不重新编码,CPU 开销接近于零。

适用的场景挺具体:

  • 摄像机本身不支持国标,或者国标实现有问题(这在国内存量 IPC 里不算少见)。
  • 同一路视频要同时喂给 NVR 和国标平台,摄像机只允许一路连接。
  • 想把 NVR 的某个通道、或者别的软件推出来的流,挂到国标平台上。

这个模式不限制硬件,任何 Linux 主机都行,x86 的机器、常开的 NAS、云主机都可以。它只做协议转换,不碰视频编码。

主板不限

三种采集路径,按板子类型选:

采集模式 适用 编码方式
mtxrpicam / rpicamvid 树莓派系列(CSI 模块) libcamera 前置输出的 H.264
v4l2 任意 Linux 板,含 USB/UVC 摄像头 探测到 M2M 节点走硬编,否则软编
rtsp 任意主机 + 已有 RTSP 源 不重编码,直接转发

v4l2 模式的编码路径是自动决策的。启动时用 VIDIOC_QUERYCAP 探测 camera.encoder_device(默认 /dev/video11)是不是 M2M 编码节点,是就交给硬件编码器(树莓派的 bcm2835-codec、i.MX 的 coda 都在这类),探测不到就退回软件编码。探测逻辑踩过一个坑:M2M 能力的标志位在真实内核 UABI 里是 0x4000,早期写法对不上,现在两个仓库都按真机验证过的值修正了。

软件编码的兜底两边不同。Rust 版用进程内编译进去的 openh264,Go 版用常驻的 ffmpeg 子进程(rawvideo 转 H.264,进程挂了自动重启)。这个差别直接决定了"能不能零子进程部署",选型时值得看一眼。

32 位板要注意:Go 版有 armv7 产物,Rust 版 v0.2.0 还没有。原因是上游 gb28181-rs 在 ILP32 目标上的 tm_gmtoff 类型问题,issue 已经提了、修复合进上游了,v0.3.0 会把 armv7 加进发布矩阵。另外 32 位上没有测试硬件,Rust 版的 armv7 产物会以软件编码为主,带 M2M 编码器的 32 位板没测过。

两个实现怎么选

两套代码实现同一套协议、同一套 SPEC v1 的 Web API,接同一批 NVR,选型只看部署画像:

mermaid diagram

换成表格更直接:

你的情况 建议
想最快跑通,交叉编译别折腾 Go 版,零 CGO,标准交叉编译
板子内存小、闪存小 Rust 版,二进制约 2 MB
需要 OSD 水印烧进所有输出 Rust 版
需要 HLS 或中英文界面 Go 版
32 位老板子 Go 版(有 armv7 产物)
习惯改这门语言的代码 按语言选

两边的实测数据我按口径列出来,不要跨口径直接比:

指标 Go 版 Rust 版
二进制体积 约 15 MB 约 2 MB
内存 15–25 MB(开 AI 再加约 15 MB) 空载管线约 6–12 MB;全功能实测 93.8 MB
子进程 mtxrpicam 或 ffmpeg
CPU(720p@15fps) 约 15% 全功能实测 124–146%(四核约 139%)

Rust 版那组内存和 CPU 数字的口径比较重,得说清楚:2026-09-14 在树莓派 3B 上测的,720p@15fps,硬件 M2M 编码,GB28181 注册、本地录像、OSD 水印全开,逐帧 INFO 级日志开着,挂了一路 RTSP over TCP 客户端,跑 60 秒取了 13 个采样点。RSS 稳定在 93.8 MB。裸管线(不注册国标、不录像、不开水印)的数字要低得多。所以仓库描述里那个"~6 MB RSS"和 README 实测表里的 93.8 MB 不是一回事,前者是空载量级,后者是全功能量级,别混着引用。

Go 版那组 CPU 约 15% 来自 v0.1.0 的发布说明,也是树莓派 3B 上的部署实测。两边口径不同,我不做横向结论。想在自己板子上复现,两个仓库都带了 bench/rpi-bench.sh

工程上的一些具体东西

代码规模和测试密度(2026-09-15 统计):

项目 Go 版 Rust 版
源码文件 93 个 .go 文件 63 个自研 .rs 文件
源码行数 22,022 行 25,387 行
测试 34 个测试文件 8,170 行,287 个测试函数 469 个测试函数(内联测试模块)
设计文档 7 篇 md,917 行 13 篇 md,3,345 行

Rust 版另外有 11 个文件、3,491 行是 vendored 的依赖补丁(shiguredo_v4l2 的 musl ioctl 修复)。

真正让我觉得有价值的是仓库里那份 NVR 互操作踩坑笔记(docs/nvr-integration-notes.md,386 行,10 个问题,每个都有现象、根因、修复位置)。挑三个说:

重复的 Content-Length 让 NVR 拒绝连接。 RTSP 的 DESCRIBE 响应里 Content-Length 被写了两遍,响应构造函数自动加了一次,handler 里又手动加了一次。ffmpeg 能容忍,gortsplib 系的 NVR 直接判为非法 SDP 拒绝建流。严格解析器比宽松解析器更容易暴露这类问题。

半帧比丢帧更糟。 一个访问单元(一帧)通常要拆成多个 RTP 包发。TCP 交错模式下逐个 write,中间超时的话客户端收到的是半截 FU-A 分片,解码器补不回来。现在的做法是把一个 AU 的所有包合并成一次 write_all,要么整帧到,要么整帧丢;超时丢帧后要回退 RTP 序号,否则后续包会出现序号空洞;丢帧后还要把后续 P 帧一并跳过,直到下一个关键帧,因为那些 P 帧引用了已经丢掉的参考帧。

树莓派 3B 欠压降频会产坏帧。 老 3B 的供电老毛病:编码器一忙电压就掉,固件开始降频,切换的那一瞬间编码器吐出损坏的 NALU。vcgencmd get_throttled 返回 0x50005,是欠压加降频同时命中。解决办法是在编码线程里读固件状态,检测到降频就隔帧丢弃,编码负载降下来频率自己恢复,是自愈的。改完之后 60 秒测试零错误。

还有浏览器预览那条:Chrome 120 之后不再渲染 <img> 里的 multipart/x-mixed-replace,MJPEG 预览直接黑屏。改成 H.264 over WebSocket 加 MSE 之后工作正常,顺带把预览延迟从 3–9 秒压到 1 秒以内,靠的是把服务端队列从 64 帧降到 2 帧、客户端按落后程度调播放速率、落后超过 2 秒直接跳到直播边缘。

还没做的,和我知道的短板

这部分写清楚比写优势更有用:

  • 只有 Profile S。 ONVIF 的 Media2、Profile T、Recording、Analytics 都没实现。需要录像检索或双向对讲的 NVR,现在用不上。
  • H.265、WebRTC、多摄像头还没写。 Rust 仓库里有三篇设计文档(共 1,126 行),代码里是空特性,打开只打日志。
  • Rust 版的移动侦测没接上。 帧差检测器在 src/motion/ 里实现了,但没接到 Web API,前端调不到。
  • AI 检测要自己准备运行库。 发布包里不含 libonnxruntime.so 和模型文件,得自己下载放好。模型缺失时能力位降为 ai:false,不造假数据,但也确实意味着开箱不能直接用 AI。
  • Rust 版没有 armv7 产物,32 位也没有测试硬件。
  • 项目很新。 两个仓库都是 2026-09-12 建的,目前只有一名贡献者,star 数是零。维护节奏、issue 响应速度这类东西我不写进文章,也不做承诺。
  • GB 35114 只到 A 级,且没有第三方检测报告。

上手

Go 版:

git clone https://github.com/xiqing85/mibee-eye-go
cd mibee-eye-go
make build

cp configs/config.example.yaml config.yaml
# 改相机参数和 onvif.username / onvif.password,密码别留空

sudo cp deploy/mibee-eye.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now mibee-eye

Rust 版:

git clone https://github.com/xiqing85/mibee-eye-rs
cd mibee-eye-rs
cargo build --release

# 交叉编译到 aarch64(推荐 rust-lld,全静态,不需要外部工具链)
rustup target add aarch64-unknown-linux-musl
make cross-build

./deploy/install.sh pi@192.168.1.100

配置项里几个容易踩的点:camera.fps 默认 15,单板机上别盲目往上调;gb28181.enabled 默认关闭,要注册平台得显式打开并填平台地址、SIP 域、设备编码;摄像机和 ONVIF 密码默认空,公网可达的部署必须设置。环境变量用 MIBEE_EYE_ 前缀可以覆盖任意配置项,适合容器或 systemd 里注入密钥。

项目地址

GitHub 地址:mibee-eye-go · mibee-eye-rs

底下的四个协议库:onvif-go · onvif-rs(crate 名 onvif-device-rs)· gb28181-go · gb28181-rs

posted on 2026-09-15 12:14  MickeyZZC  阅读(2)  评论(0)    收藏  举报

导航