MiBee Eye Notebook:把闲置笔记本变成一台带麦克风的网络摄像头

MiBee Eye 系列此前有两个实现,跑在树莓派这类 Linux 小板上;这次新增的一支面向笔记本、台式机与迷你主机,采集本机的摄像头和麦克风,把手上闲置的电脑变成一台能进监控系统的设备。

场景

办公室里换下来的笔记本,抽屉里放着的迷你主机,性能跑不动什么正经活了,但摄像头、麦克风、网口、硬盘都是齐的。想让它们进监控系统,缺的是一层把本机音视频变成标准设备信号的软件。

已有的做法要么太轻,只能推一路 RTSP 出去;要么太重,直接装一整套 NVR。中间那一层很少见:能自动被 NVR 发现,能注册进国标平台,能录像,还能被平台语音喊话。

MiBee Eye Notebook 做的是这一层。它在 PC 上跑,采集物理插在这台机器上的摄像头和麦克风,把音视频按标准协议送出去,日常操作全在浏览器里完成。

它是什么

一句话:部署在 PC 上的本地音视频采集代理。笔记本、台式机、迷你主机、自助终端都能装,作为系统服务运行。

几点边界值得先说清楚,因为它决定了这个工具适合什么场景:

  • 只采本机设备。 USB 摄像头、笔记本内置摄像头、USB 麦克风、内置麦克风都在范围内;网络摄像头的 RTSP/ONVIF 拉流被明确定义为产品红线,永远不做。摄像头没插在这台机器上,就不在范围内。
  • 不是 NVR。 它不管第三方摄像头,只录本机设备。
  • Web UI 是主控制面。 实时预览、截图、录像开关、协议配置都在浏览器里;配置文件只是给高级用户的逃生口。
  • 出站协议默认全关。 不主动暴露任何外部接口,要开哪个由用户在界面上显式打开。

本机内部的数据流是这样的:
#mermaid-svg-A6zVMLRTQvsQekVg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-A6zVMLRTQvsQekVg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-A6zVMLRTQvsQekVg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-A6zVMLRTQvsQekVg .error-icon{fill:#552222;}#mermaid-svg-A6zVMLRTQvsQekVg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-A6zVMLRTQvsQekVg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-A6zVMLRTQvsQekVg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-A6zVMLRTQvsQekVg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-A6zVMLRTQvsQekVg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-A6zVMLRTQvsQekVg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-A6zVMLRTQvsQekVg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-A6zVMLRTQvsQekVg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-A6zVMLRTQvsQekVg .marker.cross{stroke:#333333;}#mermaid-svg-A6zVMLRTQvsQekVg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-A6zVMLRTQvsQekVg p{margin:0;}#mermaid-svg-A6zVMLRTQvsQekVg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-A6zVMLRTQvsQekVg .cluster-label text{fill:#333;}#mermaid-svg-A6zVMLRTQvsQekVg .cluster-label span{color:#333;}#mermaid-svg-A6zVMLRTQvsQekVg .cluster-label span p{background-color:transparent;}#mermaid-svg-A6zVMLRTQvsQekVg .label text,#mermaid-svg-A6zVMLRTQvsQekVg span{fill:#333;color:#333;}#mermaid-svg-A6zVMLRTQvsQekVg .node rect,#mermaid-svg-A6zVMLRTQvsQekVg .node circle,#mermaid-svg-A6zVMLRTQvsQekVg .node ellipse,#mermaid-svg-A6zVMLRTQvsQekVg .node polygon,#mermaid-svg-A6zVMLRTQvsQekVg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-A6zVMLRTQvsQekVg .rough-node .label text,#mermaid-svg-A6zVMLRTQvsQekVg .node .label text,#mermaid-svg-A6zVMLRTQvsQekVg .image-shape .label,#mermaid-svg-A6zVMLRTQvsQekVg .icon-shape .label{text-anchor:middle;}#mermaid-svg-A6zVMLRTQvsQekVg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-A6zVMLRTQvsQekVg .rough-node .label,#mermaid-svg-A6zVMLRTQvsQekVg .node .label,#mermaid-svg-A6zVMLRTQvsQekVg .image-shape .label,#mermaid-svg-A6zVMLRTQvsQekVg .icon-shape .label{text-align:center;}#mermaid-svg-A6zVMLRTQvsQekVg .node.clickable{cursor:pointer;}#mermaid-svg-A6zVMLRTQvsQekVg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-A6zVMLRTQvsQekVg .arrowheadPath{fill:#333333;}#mermaid-svg-A6zVMLRTQvsQekVg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-A6zVMLRTQvsQekVg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-A6zVMLRTQvsQekVg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A6zVMLRTQvsQekVg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-A6zVMLRTQvsQekVg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A6zVMLRTQvsQekVg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-A6zVMLRTQvsQekVg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-A6zVMLRTQvsQekVg .cluster text{fill:#333;}#mermaid-svg-A6zVMLRTQvsQekVg .cluster span{color:#333;}#mermaid-svg-A6zVMLRTQvsQekVg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-A6zVMLRTQvsQekVg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-A6zVMLRTQvsQekVg rect.text{fill:none;stroke-width:0;}#mermaid-svg-A6zVMLRTQvsQekVg .icon-shape,#mermaid-svg-A6zVMLRTQvsQekVg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A6zVMLRTQvsQekVg .icon-shape p,#mermaid-svg-A6zVMLRTQvsQekVg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-A6zVMLRTQvsQekVg .icon-shape .label rect,#mermaid-svg-A6zVMLRTQvsQekVg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A6zVMLRTQvsQekVg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-A6zVMLRTQvsQekVg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-A6zVMLRTQvsQekVg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} USB 或内置摄像头
采集层

V4L2 读帧
H.264 编码

openh264
USB 或内置麦克风
音频编码

G.711 或 AAC
编码帧分发
本机输出
浏览器预览
MP4 分段录像
对外拉流
RTSP 服务
ONVIF 设备端
对外推流
RTMP 推流
国标设备端

默认端口:Web UI 8443(强制 TLS),RTSP 8554,RTMP 1935,SIP 5060

和树莓派那条线什么关系

同属 MiBee Eye 家族,但针对的机器和场景不一样。三条线都实现同一套协议、同一套 SPEC v1 Web API,接同一批 NVR:

mibee-eye-go / mibee-eye-rs 本篇(notebook)
目标机器 树莓派等 Linux 小板 台式机、笔记本、迷你主机
摄像头 CSI 排线模块或 USB 摄像头 USB 摄像头或笔记本内置
音频 无麦克风采集管线 麦克风采集 + 国标语音对讲
主控制面 配置文件为主,Web UI 辅助 强制 TLS 的 Web UI
编码 V4L2 硬件编码或进程内软件编码 进程内 openh264
平台 Linux(含 armv7) 目前只有 Linux

选择很直接:手上有小板就装那两个,手上有闲置 PC 又希望日常用浏览器管,就装这个。
#mermaid-svg-8AZDe381EYaBplro{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8AZDe381EYaBplro .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8AZDe381EYaBplro .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8AZDe381EYaBplro .error-icon{fill:#552222;}#mermaid-svg-8AZDe381EYaBplro .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8AZDe381EYaBplro .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8AZDe381EYaBplro .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8AZDe381EYaBplro .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8AZDe381EYaBplro .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8AZDe381EYaBplro .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8AZDe381EYaBplro .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8AZDe381EYaBplro .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8AZDe381EYaBplro .marker.cross{stroke:#333333;}#mermaid-svg-8AZDe381EYaBplro svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8AZDe381EYaBplro p{margin:0;}#mermaid-svg-8AZDe381EYaBplro .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8AZDe381EYaBplro .cluster-label text{fill:#333;}#mermaid-svg-8AZDe381EYaBplro .cluster-label span{color:#333;}#mermaid-svg-8AZDe381EYaBplro .cluster-label span p{background-color:transparent;}#mermaid-svg-8AZDe381EYaBplro .label text,#mermaid-svg-8AZDe381EYaBplro span{fill:#333;color:#333;}#mermaid-svg-8AZDe381EYaBplro .node rect,#mermaid-svg-8AZDe381EYaBplro .node circle,#mermaid-svg-8AZDe381EYaBplro .node ellipse,#mermaid-svg-8AZDe381EYaBplro .node polygon,#mermaid-svg-8AZDe381EYaBplro .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8AZDe381EYaBplro .rough-node .label text,#mermaid-svg-8AZDe381EYaBplro .node .label text,#mermaid-svg-8AZDe381EYaBplro .image-shape .label,#mermaid-svg-8AZDe381EYaBplro .icon-shape .label{text-anchor:middle;}#mermaid-svg-8AZDe381EYaBplro .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8AZDe381EYaBplro .rough-node .label,#mermaid-svg-8AZDe381EYaBplro .node .label,#mermaid-svg-8AZDe381EYaBplro .image-shape .label,#mermaid-svg-8AZDe381EYaBplro .icon-shape .label{text-align:center;}#mermaid-svg-8AZDe381EYaBplro .node.clickable{cursor:pointer;}#mermaid-svg-8AZDe381EYaBplro .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8AZDe381EYaBplro .arrowheadPath{fill:#333333;}#mermaid-svg-8AZDe381EYaBplro .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8AZDe381EYaBplro .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8AZDe381EYaBplro .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8AZDe381EYaBplro .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8AZDe381EYaBplro .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8AZDe381EYaBplro .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8AZDe381EYaBplro .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8AZDe381EYaBplro .cluster text{fill:#333;}#mermaid-svg-8AZDe381EYaBplro .cluster span{color:#333;}#mermaid-svg-8AZDe381EYaBplro div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8AZDe381EYaBplro .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8AZDe381EYaBplro rect.text{fill:none;stroke-width:0;}#mermaid-svg-8AZDe381EYaBplro .icon-shape,#mermaid-svg-8AZDe381EYaBplro .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8AZDe381EYaBplro .icon-shape p,#mermaid-svg-8AZDe381EYaBplro .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8AZDe381EYaBplro .icon-shape .label rect,#mermaid-svg-8AZDe381EYaBplro .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8AZDe381EYaBplro .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8AZDe381EYaBplro .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8AZDe381EYaBplro :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 要接的是哪种机器
主板加 CSI

或 USB 摄像头
PC 或笔记本

自带摄像头麦克风
mibee-eye-go

或 mibee-eye-rs
mibee-eye-notebook

国标这条线,外加语音对讲

国标设备端的能力来自协议库 gb28181-rs:SIP 注册(摘要认证)、目录与设备信息查询、实时流与回放的 RTP-PS 推送、平台抓拍指令、GB 35114 A 级(SM2 证书注册认证 + keyed-SM3)。实时流是收到平台 INVITE 时动态挂到分发总线上,收到 BYE 再摘掉,不占空转资源。

这条线里有个树莓派那两个实现没有的东西:语音对讲

平台侧发起一路 audio-only 的 INVITE,设备把它解成 G.711,直接输出到本机扬声器。也就是说,值班的人可以在平台上对着这台 PC 讲话,声音从 PC 的音箱出来。这一项默认开启;如果配置里关掉了,或者机器上根本没有可用的输出声卡,设备对这类 INVITE 回 488,而不是先回 200 再把音频丢掉。

最后那一句是这个项目的取舍风格:宁可明确拒绝,也不假装接通。

音频是怎么走的

麦克风这条链路上有两条编码路径,用途不同:
#mermaid-svg-Exq43a9ydRfeKzmL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Exq43a9ydRfeKzmL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Exq43a9ydRfeKzmL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Exq43a9ydRfeKzmL .error-icon{fill:#552222;}#mermaid-svg-Exq43a9ydRfeKzmL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Exq43a9ydRfeKzmL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Exq43a9ydRfeKzmL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Exq43a9ydRfeKzmL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Exq43a9ydRfeKzmL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Exq43a9ydRfeKzmL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Exq43a9ydRfeKzmL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Exq43a9ydRfeKzmL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Exq43a9ydRfeKzmL .marker.cross{stroke:#333333;}#mermaid-svg-Exq43a9ydRfeKzmL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Exq43a9ydRfeKzmL p{margin:0;}#mermaid-svg-Exq43a9ydRfeKzmL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Exq43a9ydRfeKzmL .cluster-label text{fill:#333;}#mermaid-svg-Exq43a9ydRfeKzmL .cluster-label span{color:#333;}#mermaid-svg-Exq43a9ydRfeKzmL .cluster-label span p{background-color:transparent;}#mermaid-svg-Exq43a9ydRfeKzmL .label text,#mermaid-svg-Exq43a9ydRfeKzmL span{fill:#333;color:#333;}#mermaid-svg-Exq43a9ydRfeKzmL .node rect,#mermaid-svg-Exq43a9ydRfeKzmL .node circle,#mermaid-svg-Exq43a9ydRfeKzmL .node ellipse,#mermaid-svg-Exq43a9ydRfeKzmL .node polygon,#mermaid-svg-Exq43a9ydRfeKzmL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Exq43a9ydRfeKzmL .rough-node .label text,#mermaid-svg-Exq43a9ydRfeKzmL .node .label text,#mermaid-svg-Exq43a9ydRfeKzmL .image-shape .label,#mermaid-svg-Exq43a9ydRfeKzmL .icon-shape .label{text-anchor:middle;}#mermaid-svg-Exq43a9ydRfeKzmL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Exq43a9ydRfeKzmL .rough-node .label,#mermaid-svg-Exq43a9ydRfeKzmL .node .label,#mermaid-svg-Exq43a9ydRfeKzmL .image-shape .label,#mermaid-svg-Exq43a9ydRfeKzmL .icon-shape .label{text-align:center;}#mermaid-svg-Exq43a9ydRfeKzmL .node.clickable{cursor:pointer;}#mermaid-svg-Exq43a9ydRfeKzmL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Exq43a9ydRfeKzmL .arrowheadPath{fill:#333333;}#mermaid-svg-Exq43a9ydRfeKzmL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Exq43a9ydRfeKzmL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Exq43a9ydRfeKzmL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Exq43a9ydRfeKzmL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Exq43a9ydRfeKzmL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Exq43a9ydRfeKzmL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Exq43a9ydRfeKzmL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Exq43a9ydRfeKzmL .cluster text{fill:#333;}#mermaid-svg-Exq43a9ydRfeKzmL .cluster span{color:#333;}#mermaid-svg-Exq43a9ydRfeKzmL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Exq43a9ydRfeKzmL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Exq43a9ydRfeKzmL rect.text{fill:none;stroke-width:0;}#mermaid-svg-Exq43a9ydRfeKzmL .icon-shape,#mermaid-svg-Exq43a9ydRfeKzmL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Exq43a9ydRfeKzmL .icon-shape p,#mermaid-svg-Exq43a9ydRfeKzmL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Exq43a9ydRfeKzmL .icon-shape .label rect,#mermaid-svg-Exq43a9ydRfeKzmL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Exq43a9ydRfeKzmL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Exq43a9ydRfeKzmL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Exq43a9ydRfeKzmL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 麦克风
PCM 采样
G.711 编码
AAC 编码

可选特性
国标实时流
MP4 录像
RTMP 推流
平台语音对讲
G.711 解码
本机扬声器

  • G.711 μ-law 是默认路径,纯 Rust 实现,走国标实时流,也是平台对讲接收时用的编码。
  • AAC 走 MP4 录像和 RTMP 推流,但它挂在一个默认关闭的编译特性后面(aac),需要显式打开才会编进二进制。原因是它依赖一个外部 AAC 编码库,而项目整体的取向是尽量少引入重量级组件。

录像这边:MP4 分段落盘,默认 900 秒一段,总容量上限默认 10,240 MB,到顶了按先进先出删最旧的,且只删自己写的文件。切段发生在关键帧边界上,不会出现半截片子。

安全默认值:出站协议一律先关着

这个项目的安全取向比较少见,值得单独说:所有对外协议默认全关。RTSP 服务、RTMP 推流、ONVIF 设备端、国标设备端,装完都是关的,要哪个自己在界面上打开,状态存在 SQLite 里,重启后保留。

其余几条:

  • Web UI 强制 TLS,只走 rustls,没有 HTTP 回退,在 localhost 上也不回退。首次运行自动生成自签名证书。
  • 管理员口令用 bcrypt 存,会话 cookie 24 小时,带 HttpOnlySecureSameSite=Strict
  • 认证接口按 IP 限流,默认 20 次 / 60 秒;登录成功重置计数;连续 5 次失败按用户锁定并指数退避。
  • 状态变更请求校验双提交 CSRF,响应带严格 CSP 头。
  • 匿名可访问的只有 /health/metrics 两个端点,其余全部要登录。
  • 单管理员模型,没有多租户和按用户权限;双因素与双向 TLS 明确不做,v1 的认证就只有强口令这一层。

最后一条是明显的短板,公网直连的部署只能靠强口令加 TLS。定位上它是内网设备,不是给公网暴露用的。

没有 ffmpeg 的编解码栈

早期版本依赖 ffmpeg 子进程做转码,现在这五处调用全部换成了进程内实现:

原来的活 换成了什么
H.264 编码 openh264(Cisco,BSD-2,源码内置编译)
MJPEG 解码成像素 jpeg-decoder
YUYV 转 JPEG 预览 jpeg-encoder
音频编码 G.711 μ-law,或可选 FDK-AAC
MP4 分片复用 muxide,纯 Rust 的 fMP4 复用器

换掉之后有两个直接变化:运行时不再需要装 ffmpeg(容器镜像、systemd 单元、裸机安装步骤里都不用),崩溃恢复也不用再靠进程守护。项目自己在变更记录里给的数字是,这一改动消除了每路约 64 MB 的子进程开销。这是项目自述口径,我没有做过第三方对比测试。

浏览器预览也不是靠转码实现的。取流顺序是按设备能力协商的:先试 MSE(分块 HTTP 传分片 MP4),不行退回 MJPEG,再不行退回快照轮询。抓帧是采集循环里维护的一路 JPEG 旁路,MJPG 摄像头直接把原始 JPEG 字节转出去,YUYV 摄像头按帧间隔重新编码一张 JPEG,快照和预览都消费这个旁路。

运维面

这些东西对单机部署其实挺关键:

  • 协议热开关:在界面上开关 ONVIF、国标、RTMP 不用重启服务,状态落 SQLite。
  • 摄像头热插拔:udev 监听增删,插上的摄像头自动出现,拔掉的标记离线并优雅关闭它的录像输出。
  • 实时事件:SSE 通道把摄像头上下线事件推到浏览器,不用手动刷新。
  • 可观测:Prometheus 指标 14 个以上;OpenTelemetry 追踪管线接了 132 个 span 标注;日志是结构化 JSON;可选把日志推到 Loki 兼容后端,后端不在也不影响运行。

平台现实与已知短板

  • 还没有正式发布。 没有 tag、没有 release,变更记录全部挂在 Unreleased 下,版本号是 0.1.0。没有可对齐的预编译产物和镜像版本,现在只能自己编译。
  • 只有 Linux。 Windows 和 macOS 都在计划里,但当前编译不过 :Windows 那边卡在 libc::getifaddrs 只有 POSIX 实现、采集代码缺 Windows 分支、路径写死了 /dev/videoN;macOS 就算编过,/dev/videoN 也不存在,还需要 AVFoundation 那套设备枚举。而这个产品的主要用户画像恰恰是手上有闲置 Windows 笔记本的人,这条缺口不小。
  • README 和实现已经对不上。 README 的 crate 行数表是旧值;预览那一节仍写成 ffmpeg 转码,而 ffmpeg 已经移除、现在的主路径是 MSE。
  • 资源数字是目标值。 空闲 CPU 低于 5%、内存低于 200 MB、首帧低于 500 ms、并发不超过 16 路,这些都是设计目标,仓库里没有对外可引用的实测数据。这一点和树莓派那两个实现不同,它们的 README 里有带条件的实测记录,不要把两边的数字放一起比。
  • 认证只有一层。 单管理员、强口令,没有双因素和双向 TLS。
  • 没有云、没有 App、没有推送。 定位就是内网自托管,别指望移动端体验。
  • 项目很新。 2026-09-16 建仓开源,1 个 star。维护节奏和响应速度我不写进文章,也不做承诺。
  • Rust 工具链要求 1.88、edition 2024,偏新。

上手

Linux 上编译运行:

bash 复制代码
# 系统依赖
sudo apt install libv4l-dev libasound2-dev libclang-dev
sudo usermod -aG video $USER      # 摄像头访问权限,需重新登录生效

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

cp config.toml config.local.toml  # config.local.toml 已在 gitignore 里
# 编辑录制路径、协议参数、平台地址等

cargo run --release -- --config config.local.toml

几个容易踩的点:首次运行会走一个初始化流程创建管理员账号并生成自签名证书,浏览器会提示证书不可信,这是预期行为;[web] advertised_host 留空会通过 UDP 探测本机局域网 IP,做 NAT 或机器有多个网卡时建议手动指定,否则返回给客户端的拉流地址可能是错的;[capture] video_device 默认 /dev/video0,多摄像头的机器要先确认设备号;忘掉口令可以用 --reset-password 重置。

项目地址

mibee-eye-notebook:https://github.com/xiqing85/mibee-eye-notebook

同族的另外两个实现(面向树莓派等 Linux 小板):

mibee-eye-go:https://github.com/xiqing85/mibee-eye-go

mibee-eye-rs:https://github.com/xiqing85/mibee-eye-rs

协议层来自这两个库:

onvif-rs:https://github.com/mickeyzzc/onvif-rs(crateonvif-device-rs

gb28181-rs:https://github.com/mickeyzzc/gb28181-rs

许可:Apache-2.0,可商用。协议库 onvif-rs 与 gb28181-rs 均为 MIT。

相关推荐
小灰灰搞电子2 小时前
Rust Once 、OnceLock、LazyLock 一次性初始化详解
开发语言·后端·rust
qq_452396232 小时前
第八篇:《智能指针与内部可变性:Rust 的进阶内存管理》
rust
Amos_Web4 小时前
Rspack 源码解析(七):Module、Chunk 与加载树优化
前端·rust·源码阅读
柯南46681 天前
【AI开发之Rust】第 8 课:泛型、trait 与 trait 对象
rust·编程语言
yume_sibai1 天前
12-Rust 性能优化指南(性能分析 + 内存优化 + SIMD + 并发优化 + 编译器优化 + 基准测试)
开发语言·性能优化·rust
geovindu1 天前
rust:Builder Pattern
开发语言·设计模式·rust·建造者模式
传奇开心果编程1 天前
【Rust入门知识点学与练】第33课:异步编程入门(async/await)强化
开发语言·学习·rust
传奇开心果编程1 天前
【Rust入门知识点学与练】第34课:所有权与借用
开发语言·学习·rust