简介Moonfire NVR 是一款开源的安全摄像机网络视频录像机面向需要自建监控存储方案的开发者与运维人员。它把 IP 摄像机输出的 H.264-over-RTSP 流直接落盘视频帧以旋转目录形式保存其余元数据存入 SQLite3并能按任意时间范围动态生成 .mp4 文件由于不做解码、分析与重编码CPU 占用极低可在普通机器上同时处理六路 1080p/30fps 流。资源包共 116 个文件约 5.22MB以 48 个 Rust 源码文件为核心配合 24 个 JavaScript 前端脚本、11 个 Markdown 文档、5 个 SQL 建表脚本以及 Dockerfile、TOML 配置、proto 定义与示例视频等覆盖后端、前端、数据库与容器化部署。目前已有 1409 人学习下载。借助这套代码读者可理解 NVR 的存储分层设计、RTSP 接入流程与 Web 片段检索实现并据此搭建或二次开发自己的录像系统。1. 从一台总是掉线的摄像头说起Moonfire NVR 到底解决什么问题手里有几台 RTSP 网络摄像机的人大概率都经历过这种场景厂商自带的客户端只能在一台手机上看换台电脑就得重新装一遍想存 7×24 小时录像要么买厂商的云存储按月付费要么忍受 NAS 上那套笨重的套件。更麻烦的是一旦摄像头数量上去码流、磁盘、时间轴回放这三件事就开始互相打架。Moonfire NVR 就是冲着这个痛点来的——它是一个用 Rust 写的网络视频录像机核心职责很纯粹把 RTSP 摄像机的流拉进来切成一段段可检索的录像落到本地磁盘再通过一个轻量 Web 界面回放和导出。它适合谁适合手里有 IP 摄像头、想自己掌控录像数据、又不想被厂商生态绑死的从业者。Rust 技术栈意味着它在长时间运行下的内存占用和稳定性有先天优势不像某些脚本拼起来的方案跑几天就内存泄漏。前端是 JavaScript浏览器直接看不需要装插件。这篇笔记按「它是什么 → 怎么装怎么配 → 坑在哪 → 怎么用得更顺」的顺序拆一遍新手能照着复现熟手能直接看参数边界。2. 拆开 Moonfire NVR 的架构Rust 后端、RTSP 拉流与分段存储2.1 为什么是 Rust 分段录像这套组合先讲清楚它的工作模型不然后面配参数全是玄学。Moonfire NVR 的进程模型很简单一个常驻的 Rust 二进制内部维护若干「流stream」每个流对应一路摄像机的 RTSP 地址。它不像传统 NVR 那样把视频拼成一个大文件而是按时间切成固定时长的「录像段recording」每段独立成文件同时在 SQLite 里记一条元数据起始时间、时长、文件路径、码流参数。这种设计的好处是回放和清理都变成数据库查询。你要看某个时间点的录像先查 SQLite 找到覆盖该时间点的段再定位文件要删旧录像按时间条件删记录和文件即可不用去动一个大文件。代价是文件数量会很多磁盘的 inode 和目录遍历性能要留意后面避坑章节会讲。Rust 在这里的价值主要是两点一是内存安全长时间跑不会因为缓冲区越界崩掉二是它用了一套基于 tokio 的异步 IO多路 RTSP 并发拉流时线程开销可控。前端 JavaScript 只负责渲染时间轴和播放真正的解码交给浏览器服务端基本只做转发和存储CPU 压力比想象中小。2.2 目录结构与关键文件装完之后你会看到一个典型的数据目录理解它才能排查问题。常见布局如下路径作用备注db/SQLite 数据库存流配置、录像段元数据db/db主数据库文件备份时优先拷这个sample/或录像根目录录像段文件按流和时间组织config.toml主配置定义流、端口、存储路径二进制本体moonfire-nvr用子命令操作配置里最核心的是[[streams]]段每一路摄像机一个块。下面是一个最小可用的配置骨架字段名按常见版本习惯写具体以你拿到的版本为准# 全局设置Web 监听端口和数据库位置 bind_addresses [0.0.0.0:8080] db_dir /var/lib/moonfire-nvr/db # 每一路摄像机一个 [[streams]] [[streams]] id 1 # RTSP 地址用户名密码按实际填 url rtsp://user:pass192.168.1.10:554/stream1 # 录像保留天数超期自动清理 retain_days 14 # 单段录像时长秒太短文件多太长回放粒度粗 sample_file_duration 300逻辑说明bind_addresses决定 Web 界面从哪访问db_dir是数据库落盘位置retain_days是自动清理策略sample_file_duration控制分段粒度。参数上sample_file_duration我一般设 300 秒也就是 5 分钟一段——设成 60 秒会让文件数暴涨设成 3600 秒则回放时定位精度差拖动时间轴要等整段加载。2.3 初始化数据库与首次启动配置写完不能直接跑得先初始化数据库否则进程会因为找不到表结构退出。常见做法是分两步# 第一步初始化数据库创建表结构 moonfire-nvr init --db-dir /var/lib/moonfire-nvr/db # 第二步前台启动方便看日志 moonfire-nvr run --db-dir /var/lib/moonfire-nvr/db --config /etc/moonfire-nvr/config.toml逻辑说明init只做一次重复执行一般会报错或跳过run是常驻进程。第一次启动时重点看日志里有没有connected或stream started之类的字样如果某路流一直重连八成是 RTSP 地址或鉴权有问题。参数上--db-dir必须和配置里的db_dir一致否则会出现「配置读到了但数据库是空的」这种翻车现场。启动成功后浏览器打开http://主机IP:8080应该能看到时间轴界面。如果页面能开但时间轴空白说明 Web 服务正常、拉流没成功问题在 RTSP 那一侧不在前端。3. 把 RTSP 摄像机接进来地址、鉴权与码流选择3.1 RTSP 地址的构成与常见写法RTSP 地址是接入的第一道坎。标准格式是rtsp://用户名:密码IP:端口/路径但不同厂商的路径差异很大这是最容易踩坑的地方。常见几类路径写法主码流/stream1、/h264/ch1/main/av_stream、/cam/realmonitor?channel1subtype0子码流/stream2、/h264/ch1/sub/av_stream、/cam/realmonitor?channel1subtype1主码流清晰但码率高子码流省带宽但糊。Moonfire NVR 本身不做转码拉什么存什么所以选哪路直接决定磁盘消耗。我的习惯是录像存主码流如果带宽或磁盘吃紧再考虑子码流。3.2 用命令行先验证流能不能拉在把地址填进配置之前强烈建议先用工具验证一遍别让 NVR 去当小白鼠。常见做法是用 ffmpeg 或 ffprobe 探一下# 探测流信息不实际录像几秒后自动退出 ffprobe -rtsp_transport tcp -i rtsp://user:pass192.168.1.10:554/stream1 -show_streams # 实际拉 10 秒存成文件验证能否稳定解码 ffmpeg -rtsp_transport tcp -i rtsp://user:pass192.168.1.10:554/stream1 -t 10 -c copy test.mp4逻辑说明-rtsp_transport tcp强制走 TCP避免 UDP 丢包导致的画面花屏这是 IP 摄像头场景下的血泪经验。-show_streams会打印编码格式、分辨率、帧率确认是 H.264 还是 H.265——有些浏览器对 H.265 支持有限回放可能黑屏。-c copy表示不重新编码直接封装速度快。如果 ffprobe 都拉不到流Moonfire NVR 一定也拉不到先解决网络和鉴权别去改 NVR 配置。3.3 多路流与带宽估算接多路之前先算笔账。假设一路主码流 4 Mbps14 天保留单路每天4 Mbps ÷ 8 0.5 MB/s 0.5 MB/s × 86400 s ≈ 43 GB/天 14 天 ≈ 600 GB/路四路就是 2.4 TB 左右。这个数字决定了你磁盘要留多少余量。Moonfire NVR 不做智能编码码率是摄像机给的所以估算只能按摄像机标称值来。实际中运动画面多的场景码率会更高建议按标称值的 1.2 倍留余量。配置多路时每路一个[[streams]]块id不能重复。启动后如果某几路正常、某几路一直重连优先怀疑那几路的地址或该摄像机的并发连接数限制——有些摄像头同时只允许 12 个 RTSP 客户端你 NVR 占了一个手机 App 再连就挤掉了。4. 避坑与排查那些让录像悄悄断掉的问题4.1 时间轴有段但点开黑屏现象Web 界面时间轴上明明有录像段点进去播放却黑屏或一直转圈。原因多半是编码格式问题。摄像机用了 H.265而当前浏览器不支持或者录像段封装格式浏览器解不了。Moonfire NVR 服务端不转码浏览器拿到什么就解什么。解决把摄像机切到 H.264 主码流或者确认浏览器版本支持 H.265。用 ffprobe 看录像段文件的编码确认和预期一致。如果必须用 H.265回放时换支持它的浏览器或者接受导出后用本地播放器看。4.2 磁盘写满后录像静默停止现象某天发现最近几天的录像全没了进程还在跑日志也没明显报错。原因保留策略和磁盘容量没对上。retain_days设了 14 天但磁盘只够 7 天写满后新段写不进去旧段又没到清理时间就卡住了。有些版本在磁盘满时的日志级别不高容易被忽略。解决先按 3.3 的公式算实际容量把retain_days调到磁盘能承受的值留 15% 余量。同时监控磁盘使用率超过 85% 就告警。别指望 NVR 自己处理磁盘满这类工具普遍是「写不进去就停」不会帮你删。4.3 文件数暴涨拖慢回放现象跑了一段时间后时间轴加载变慢磁盘ls一个目录要好几秒。原因sample_file_duration设得太小比如 60 秒一天就是 1440 个文件一个月四万多个。文件系统在单目录文件数过多时性能下降明显。解决把sample_file_duration调到 300 秒甚至 600 秒减少文件数。已经产生的碎文件不用手动合并等保留期过了自动清理。如果目录已经很大清理时用按时间批量删别用通配符一次删几万个。4.4 RTSP 断流后不自动重连现象摄像头重启或网络抖动后那路流再也没恢复时间轴出现一段空白。原因部分版本的重连逻辑有退避策略如果摄像头短时间内反复断退避时间会拉长或者 TCP 连接处于半开状态进程以为还连着。解决先确认进程日志里有没有重连记录。如果长时间不恢复重启 NVR 进程是最直接的办法。长期方案是给摄像机配固定 IP、稳定的交换机端口减少物理层抖动。也可以写个外部脚本定期检查时间轴最新段的时间超过阈值就重启对应流。4.5 数据库和录像文件不同步现象手动删了录像文件但时间轴上还能看到那些段点开报 404。原因Moonfire NVR 的元数据在 SQLite 里文件系统是另一套。手动删文件绕过了数据库记录还在。解决永远通过 NVR 自己的清理机制删录像别手动rm。如果已经删了需要进数据库把对应记录清掉或者重建数据库会丢失所有元数据慎用。备份时数据库和录像文件要一起备只备一个没意义。5. 进阶用法用 SQLite 直接查录像元数据与自动化清理跑顺之后最有价值的进阶技巧是直接查它的 SQLite 数据库。Web 界面能做的事有限但数据库里什么都有写脚本做自动化清理、统计、告警都靠它。先看表结构常见的有stream、recording、sample_file几张表。查最近一小时有哪些录像段-- 查指定流在某个时间范围内的录像段 SELECT r.id, r.start_time_90k, -- 起始时间单位 90kHz r.duration_90k, -- 时长单位 90kHz sf.path -- 对应的文件路径 FROM recording r JOIN sample_file sf ON sf.recording_id r.id WHERE r.stream_id 1 AND r.start_time_90k (strftime(%s,now) - 3600) * 90000 ORDER BY r.start_time_90k DESC;逻辑说明Moonfire NVR 内部用 90kHz 时钟计数所以时间字段要乘 90000 换算成秒。strftime(%s,now)取当前 Unix 时间戳减 3600 秒就是最近一小时。这条查询能帮你确认某路流到底有没有在正常写录像——如果结果为空说明那路流断了。参数上stream_id对应配置里的id改这个值就能查不同摄像机。duration_90k除以 90000 就是这段录像的秒数可以用来统计实际录像覆盖率把一天所有段的时长加起来除以 86400就知道有没有漏录。基于这个查询可以写个巡检脚本每小时跑一次发现某路流最近 10 分钟没有新段就发告警。这比等用户发现录像没了要主动得多。我一般还会顺手统计磁盘增长速率反推retain_days设得合不合理。导出录像也是类似思路查到目标时间段的文件路径直接拷文件即可Moonfire NVR 的段文件本身就是可播放的封装格式不需要额外转码。导出后如果要合并成一个大文件用 ffmpeg 的 concat 协议按顺序拼就行。从那以后我每次接新摄像机都强制先用 ffprobe 验证流、再算磁盘容量、最后才填配置这三步少一步后面都要返工。希望帮到你。本文还有配套的精品资源点击获取