半夜两点翻回放发现硬盘里只有最近三天的画面而三天前那次快递被顺手牵羊的片段刚好被覆盖掉了——这是我第一次自己搭监控系统时踩的最疼的一脚。那套东西当时是七拼八凑起来的一台吃灰的旧笔记本、两个淘来的网络摄像机、一堆从各种教程里抄来的命令行推流能看、录像也有但没人告诉我磁盘会在第 97 个小时写满也没人提醒我断电重启之后 FFmpeg 进程不会自己爬起来。后来我把这套链路推倒重来砍掉了一半的组件四路摄像机稳定跑了两年多硬件成本控制在千元出头日常维护基本就是偶尔看一眼手机。这篇东西讲的就是这套低成本视频监控系统的完整搭建过程推流怎么从摄像机把画面接进来、播放怎么在不同设备上都能看、录像怎么存得下又找得到。适合手上有几台网络摄像机想自己接管的人也适合拿旧设备想折腾点实用东西的玩家。我不打算写成产品说明书重点放在每一步为什么这么选、参数为什么是这个数、以及那些只有跑上几个月才会暴露出来的坑。1. 把低成本这三个字算清楚再动手很多人上来就问用什么软件、买什么设备这个问题本身顺序就反了。低成本监控系统里真正决定总成本的从来不是软件——软件几乎都是开源免费的——而是你要存多久的录像、要接几路画面、以及你打算让哪台机器 7×24 小时不关机。这三件事想不清楚后面所有的选型都是在赌博。1.1 需求三问看什么、存多久、给谁看我习惯让每个想搭这套东西的人先回答三个问题。第一个是看什么也就是监控目的是看门口有没有人经过还是看清车牌和面部这两个需求对应的分辨率和码率差了三四倍。第二个是存多久七天、三十天还是三个月这个数字直接乘出你的硬盘容量是整套系统里唯一一块硬成本。第三个是给谁看只有自己在家里的局域网看和要让家里老人在外地用手机看架构复杂度完全不是一个量级。这三个问题里最容易被低估的是存多久。大部分人凭感觉说一个月吧但当他看到一个月四路 1080p 需要接近 2.6TB 可用空间、而一块能用的监控盘要几百块的时候通常会改口说七天。我的建议是先按七天做把架构留出扩容余地等真正出过一次事、知道自己最常回看的是哪几天之后再决定加不加盘。绝大多数家庭和小场所的真实回看需求集中在最近 72 小时内。至于给谁看如果只有局域网内访问整条链路可以做得极简连鉴权都可以省一旦要跨网络访问就要认真考虑认证、加密和访问路径的收敛这部分后面单独讲。把这三个问题的答案写在纸上你的方案轮廓其实已经出来一半了。1.2 摄像机取流三种来源三种隐性成本摄像机这一端能拿到 RTSP 流的设备是最省事的。市面上主流的网络摄像机基本都支持取流地址各家路径不同常见形如rtsp://用户名:密码IP:554/...主码流和子码流通常是两个不同的路径。买之前一定要确认设备支持标准取流协议最好还能支持关闭云服务——很多便宜机器默认会往厂商云端推一份既占上行带宽也不是你想要的架构。第二种来源是旧手机改造。装一个能提供标准取流的 App把闲置手机变成摄像机成本几乎为零画质还不错缺点是长时间录像会发热、电池鼓包风险、以及系统会杀后台。我的做法是把电池拆掉、改成直接供电并且关闭所有省电策略跑个一年半载问题不大。第三种是模拟摄像机加编码器或者干脆用一块带摄像头模块的单板机自己采集。前者的好处是老设备能复用坏处是多一层转码后者灵活但需要自己写采集和编码逻辑。对大多数人来说第一种和第二种就够了第三种属于折腾范畴除非你有特殊角度需求。隐性成本这块要提醒一句便宜摄像机最常见的两个坑是码率不可控和时间不同步。前者导致你的存储容量估算全盘作废后者导致回放的时候四路画面对不上时间轴。买之前查一下能不能手动设置码率上限和帧率能不能配置时间同步服务器这两项比标称像素重要得多。1.3 服务端跑在哪四种硬件平台的真实代价服务端这台机器是整套系统的心脏。我试过四种平台各自的代价摊开说一下。平台类型典型功耗能扛几路不转码优点实际痛点旧笔记本/旧台式机15-45W8-16 路零成本、有硬盘位、性能余量大体积大、风扇积灰、意外断电伤盘百元级单板计算机3-7W2-4 路静音、省电、可塞进弱电箱需要外接存储、SD 卡易损、散热要处理迷你主机/低功耗小主机6-15W6-10 路有 M.2 或 2.5 寸盘位、体积小单网口居多、部分型号散热一般软路由/NAS 复用视原有设备4-8 路不增加设备、集中管理和原有服务抢资源、磁盘寿命被摊薄关键结论是只要不做视频转码几乎所有平台都能扛住 4 路 1080p。因为推流链路里最省 CPU 的做法是转封装-c copy数据进来什么样出去就什么样只是换个容器CPU 占用接近于零。真正把 CPU 打满的是解码再编码比如把 H.265 转成 H.264 给不支持的浏览器看或者把 4K 降到 1080p。这类操作一路就能吃掉一个四核小主机的全部算力。所以我的选型逻辑很简单先想办法让全链路都不转码实在绕不过去的那一路单独拎出来处理。这个原则定下来之后硬件预算能砍掉一大半。存储盘单独说一句不要用叠瓦盘SMR7×24 连续写入的场景下这类盘的写入性能会断崖式下跌宁可买小一点容量的垂直盘CMR。2. 推流链路把画面从摄像机接进来的第一公里链路的第一段是从摄像机把流拉出来再送到服务端。这段路看起来只有一条命令但十个自己搭监控的人里有八个在这里翻过车问题集中在两个地方分不清拉流和推流的方向以及不知道该不该转码。把这两个概念捋直了后面的配置就都是照着抄的事。2.1 拉流和推流不是一回事别被名字绕进去站在服务端的角度从摄像机取画面这个动作叫拉流pull服务端是客户端摄像机是服务端把画面分发给播放器这个动作有两种模式服务端主动推给某个地址叫推流push播放器来服务端要叫拉流。同一个词在不同视角下含义相反这是新手最容易混淆的地方。我习惯用快递打比方摄像机是仓库服务端是快递站。快递站派人去仓库取货这是拉快递站把货送到你家这是推你自己去快递站拿这也是拉。仓库摄像机通常不会主动送货因为它不知道快递站在哪也没这个能力。所以绝大多数情况下链路是服务端去摄像机拉而不是摄像机往服务端推。搞清这一点你就明白为什么很多教程里写的让摄像机推流到服务器其实是在摄像机的高级设置里填一个推流地址——那是摄像机厂商做的主动推送功能不是所有设备都有。绝大多数情况下我们用的是服务端拉流配置写在服务端摄像机什么都不用改这也更符合设备坏了换一台就行的运维思路。2.2 FFmpeg 转封装每个参数都有它存在的理由最通用的做法是用 FFmpeg 把 RTSP 流拉下来转封装成其他协议。最小可用命令长这样ffmpeg -hide_banner -loglevel warning \ -rtsp_transport tcp \ -stimeout 5000000 \ -use_wallclock_as_timestamps 1 \ -i rtsp://user:pass192.168.1.64:554/stream1 \ -c copy \ -an \ -f flv \ -flvflags no_duration_filesize \ rtmp://127.0.0.1:1935/live/cam1逐个说这些参数在干嘛以及为什么不能省。-rtsp_transport tcp是最重要的一个。RTSP 协议默认走 UDPUDP 不保证送达网络稍有波动就会丢包画面上表现为绿色马赛克或者整块糊掉。改成 TCP 之后数据可靠传输代价是延迟增加大概 100 到 300 毫秒以及偶尔会因为重传导致卡顿。局域网环境下这个代价完全可以接受我从来没后悔过改成 TCP。-stimeout 5000000设的是 socket 超时单位微秒五秒。新版 FFmpeg 里这个参数名可能变成-timeout具体看你装的版本两个都试一下。它的作用是摄像机掉线之后不要傻等超时退出交给外层守护进程重启。没有这个参数的话摄像机关机后 FFmpeg 会一直挂着等看着进程还在其实什么都没在录。-use_wallclock_as_timestamps 1处理的是时间戳问题。有些摄像机的 RTP 时间戳不连续或者跳变不修正的话转封装出来的流在播放器里会卡住或者快进。这个参数让 FFmpeg 用系统时间来打时间戳牺牲一点点精确性换取稳定性。配合-fflags genpts一起用效果更稳。-c copy是全链路不转码的核心直接复制音视频流不重新编码。这一条让你用一块百元级单板机就能扛住四路 1080pCPU 占用可能只有个位数。代价是分辨率、码率、编码格式都不能改摄像机给什么就是什么。-an是去掉音频。绝大多数监控场景不需要声音而且摄像机的音频编码五花八门经常是转封装失败的原因。去掉音频能省掉一大堆麻烦也少一点隐私上的顾虑。-f flv和-flvflags no_duration_filesize是为了输出 RTMP 流后一个参数让 FFmpeg 不去写文件头和文件尾的时长信息因为直播流没有总时长这个概念写了反而增加断流风险。2.3 服务端选型我为什么最后没用纯 RTMP最早我用的是 RTMP 方案服务端是一个配置了 RTMP 模块的 Nginx。能跑但有三个问题让我最终换掉了它。第一是 RTMP 在浏览器端已经不可用了Flash 退场之后 RTMP 只能靠桌面客户端或者转成 HTTP-FLV 给网页播等于多一层。第二是它只管推流分发录像要靠额外的 FFmpeg 进程再去拉一次流写文件链路变成摄像机 → FFmpeg → RTMP → FFmpeg → 文件中间任何一个环节挂了录像就断了。第三是配置分散在多个文件里加一路摄像机要改好几处运维成本随路数线性上升。后来换成 MediaMTX情况简单多了。它是一个单可执行文件配置就一个 YAML拉流、分发、录像、协议转换全在内部完成。核心配置大概是这样logLevel: info rtsp: yes rtmp: no hls: yes webrtc: yes pathDefaults: sourceOnDemand: no record: yes recordPath: /data/rec/%path/%Y-%m-%d_%H-%M-%S-%f recordFormat: fmp4 recordSegmentDuration: 1h recordDeleteAfter: 168h paths: cam1: source: rtsp://user:pass192.168.1.64:554/stream1 cam2: source: rtsp://user:pass192.168.1.65:554/stream1几行配置就把一路摄像机定义完了从哪拉、录不录、录到哪、切多长、留多久。同一路流可以同时以 RTSP、HLS、WebRTC 三种方式对外提供播放器想用哪个用哪个不用为每种协议单独起进程。sourceOnDemand这个开关值得单独说一下。设成yes就是按需拉流没人看的时候不去摄像机取数据。省带宽、省摄像机资源看起来很美好但一旦开了录像就千万别开按需因为没人看的时候没数据流录像就是断的。我第一次配的时候两个都打开了结果只有我在看的那几个小时有录像其他时间全是空的。这个坑值得单独记一笔。2.4 断流、花屏和重连把不稳定因素一个个按住真实环境里链路一定会断。断的原因五花八门摄像机重启、交换机抽风、网线接触不良、服务端负载飙高、甚至是隔壁装修把线挖断了。我们能做的是让断了之后能自己恢复并且恢复得足够快。进程级的守护交给 systemd 就行关键是重启策略要配对[Unit] DescriptionMediaMTX Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple ExecStart/opt/mediamtx/mediamtx /opt/mediamtx/mediamtx.yml Restartalways RestartSec5 LimitNOFILE65536 [Install] WantedBymulti-user.targetRestartalways保证进程挂了五秒后自动起来Afternetwork-online.target保证网络就绪之后再启动LimitNOFILE是因为多路流加上多个播放器很容易把文件句柄数打满默认的 1024 不够用。花屏这个问题要分两种情况看。如果改成 TCP 传输之后还花那多半是摄像机端编码参数有问题比如 GOP 太长、码率波动太大去摄像机设置里把 GOP 调到帧率的两倍左右25fps 就设 50码率上限设成固定值而不是可变码率能解决大部分情况。如果是播放端花那可能是服务端的缓冲区不够或者播放器解码能力跟不上。还有一个容易被忽略的点是网线。我遇到过一路摄像机隔三差五断流查了半个月最后发现是那根网线的水晶头压得不好稍微有点震动就丢包。换根线之后两年没再出过问题。所以排查链路问题的时候永远从物理层开始往上查别一上来就怀疑软件。3. 播放端延迟、兼容性、带宽三者的拉扯流能从摄像机接到服务端只是完成了前半程。真正决定使用体验的是播放这一端——你在手机上点开要等几秒才出画面、画面和现实差了几秒、四个人同时看会不会卡这些才是每天都会碰到的问题。这一段没有完美解只有针对场景的取舍。3.1 四种播放协议的延迟实测与适用场景延迟是播放端最重要的指标也是取舍的核心。我把常见几种协议在局域网环境下实测的结果整理成表协议端到端延迟浏览器原生支持服务端开销适合场景WebRTC0.2-0.8 秒需要信令交互中实时对讲、查看当前画面HTTP-FLV1-3 秒需要 JS 播放库低桌面端长时间观看LL-HLS2-5 秒需要较新播放器高移动端近实时查看标准 HLS6-30 秒几乎全支持最低回看、兼容性优先这些数字会随网络状况和切片参数浮动但大致的量级关系是稳定的。选哪种取决于你打开监控的目的是什么。如果是为了看看现在门口有没有人那需要秒级以内的延迟WebRTC 是唯一选择。它的实现原理是建立点对点或者端到端的实时传输通道不做缓冲攒数据代价是需要一套信令交互服务端配置比其他协议复杂。如果是为了挂在屏幕上一直看HTTP-FLV 是最舒服的延迟两三秒完全能接受播放器实现简单长时间运行稳定CPU 占用也低。缺点是浏览器端要引入一个 FLV 播放库不能直接丢个video标签完事。如果是为了在外面用手机瞄一眼HLS 的兼容性优势无法替代iOS 上直接就能播不用装任何东西。延迟高一点就高一点反正只是确认一下状态。我自己的方案是同时开三路日常在电脑上挂 FLV出门用手机看 HLS需要确认实时情况时切到 WebRTC。同一路流不用重复拉服务端内部就是一份数据分发出去的。3.2 网页播放绕不开的编解码问题浏览器对视频编码的支持是个大坑而且这个坑和你用什么协议无关是编码格式本身决定的。H.264 基本通吃H.265 在 Chrome 上要看硬件和系统支持很多情况下直接播不了AV1 更新一些支持面更窄。现实情况是现在很多摄像机默认用 H.265因为同样画质下码率能省三到四成对存储成本敏感的人来说很有吸引力。但这个选择会把你逼到一个岔路口要么在服务端实时转码成 H.264 给浏览器看要么在播放端想办法。服务端转码的代价我实测过一路 1080p25 的 H.265 转 H.264在四核小主机上会吃掉 60% 到 80% 的 CPU四路直接跑不动。如果非要转必须上带硬件编码的设备或者用显卡加速成本和功耗立刻上去了和低成本三个字背道而驰。所以我的建议是录像用摄像机原始编码H.265 存得省实时播放优先走支持 H.265 的通路。WebRTC 在很多浏览器上对 H.265 的支持取决于具体版本和平台HLS 在 Safari 上支持 H.265 比较完整。如果实在绕不过去就在摄像机设置里把主码流保持 H.265、子码流设成 H.264 低分辨率实时查看用子码流回放用主码流。这样既有画质又有兼容性CPU 也不用受罪。3.3 多画面布局与大屏常显的性能账四路画面拼在一个页面上常显是很多人的实际用法——挂在客厅的旧显示器上或者放在店里当监视墙。这种场景下性能问题会放大因为要同时解码四路。网页端做多画面最容易踩的坑是给每个video元素都设成高分辨率。四路 1080p 同时解码一台老旧笔记本的风扇立刻开始嚎叫。正确的做法是用子码流做多画面比如 640×360 或者 704×576单路码率几百 Kbps四路加起来还不到 2 Mbps解码压力小一个数量级。需要看清细节的时候再点开单画面切主码流这个交互设计比硬扛四路 1080p 聪明得多。布局方面CSS Grid 是最省事的几行就能做出自适应.wall { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; background: #000; height: 100vh; } .wall video { width: 100%; height: 100%; object-fit: contain; }object-fit: contain保证画面按比例缩放不变形多出来的部分留黑边比cover更符合监控的观看习惯——裁掉边缘可能正好裁掉了你想看的东西。大屏常显还有两个实际问题。一是浏览器内存泄漏长时间播放 FLV 流有些播放库会缓慢吃掉内存几天之后页面崩掉。解决办法是加一个定时刷新比如每 12 小时重载一次页面简单粗暴但极其有效。二是屏幕烧屏长时间显示固定画面LCD 还好OLED 会留下残影尽量选 LCD 屏做监视墙。3.4 出门在外怎么看把访问路径收窄跨网络访问是个必须认真对待的环节因为它直接关系到安全。我的原则是能不给外网开端口就不开必须开的话只开一个并且加认证。比较稳妥的几种做法按推荐程度排第一是组网方案把手机和服务端放进同一个虚拟局域网服务端的监控端口根本不暴露在公网上安全性最好代价是需要额外的组网工具。第二是反向代理加身份认证用一台有公网地址的机器做入口所有请求先过认证再转发配置稍复杂但可控。第三是服务端自带的多用户鉴权MediaMTX 这类工具都支持配置用户名密码和权限至少把默认的空密码改掉。不管用哪种方式有几条底线要守住不要用默认端口不要用弱密码不要给只读用户开写权限不要把所有摄像头的路径设成一样。还有一条容易被忘记的——把云服务关掉。很多摄像机默认会往厂商云推流你在自己这套系统里做得再严实那个后门还开着等于白搭。4. 录像与存储算容量、定切片、做兜底录像这块是整套系统里最没有炫技空间、但最容易出事的环节。推流断了你能立刻发现播放卡了你能立刻感知唯独录像出问题的时候你毫不知情——直到你真的需要那段画面打开一看是空的。所以这一章的重点不是怎么录而是怎么保证一直在录、怎么知道它在录、以及录满了怎么办。4.1 一路一天多少 GB把公式摊开自己算容量的计算其实是个很简单的换算但很多人懒得算结果买回来的盘要么大到浪费要么小到三天就满。公式是每天容量(GB) 码率(Mbps) ÷ 8 × 86400 ÷ 1024化简一下就是码率(Mbps) × 10.8。也就是说 1 Mbps 的码率一天大约 10.8 GB。把这个乘上路数和天数就是你的实际需求。分辨率/编码典型码率单路每天4 路 7 天4 路 30 天720p / H.2641.0 Mbps10.8 GB302 GB1296 GB1080p / H.2642.0 Mbps21.6 GB605 GB2592 GB1080p / H.2651.2 Mbps13.0 GB363 GB1555 GB4MP / H.2652.5 Mbps27.0 GB756 GB3240 GB算出来的是理论值实际规划还要乘两个系数。一个是硬盘的可用比例标称 1TB 的盘实际可用大概 0.9TB 多一点再考虑文件系统开销按 0.9 算比较稳。另一个是余量监控盘写满之后如果没有任何清理策略新录像就写不进去了所以至少要留 15% 到 20% 的空闲空间。综合下来实际需要的标称容量大概是理论值除以 0.7。举个例子四路 1080p H.265存 30 天理论 1555 GB除以 0.7 是 2221 GB也就是说一块 3TB 的盘刚好够用且有余量。如果改成存 7 天1TB 的盘绰绰有余。所以存多久这个问题的答案直接决定你要不要多花几百块买盘。还有一个影响因素是码率是不是真的可控。很多摄像机标称 2Mbps实际因为场景复杂比如树影晃动、雨雪天气码率会飙到 4Mbps 甚至更高你的容量估算直接翻倍。解决办法是在摄像机设置里把码率控制模式设成 CBR固定码率而不是 VBR可变码率虽然画质在复杂场景下会稍微牺牲一点但容量可预测对长期运行来说这个交换是值得的。4.2 切片长度、容器格式与断电损坏录像文件不能一直往一个文件里写因为一旦进程被杀或者断电整个文件就废了前面几小时全白录。所以必须切片按时间或者按大小切成一个个独立文件。切片长度是个需要权衡的参数。切得太短文件数量爆炸一个小时间隔里几十个文件回放检索的时候列表能翻半天文件系统的元数据压力也大。切得太长单个文件太大出问题的时候损失的时间跨度也大而且拖动进度条时的定位精度差。我试过 1 分钟、5 分钟、10 分钟、1 小时几种最后定在10 分钟理由是出问题时最多丢 10 分钟一天 144 个文件检索列表长度可接受单个文件大概 1.5GB拷贝和转存也方便。容器格式这个细节值得单独提醒。MP4 的文件索引moov box默认写在文件末尾这意味着如果录制过程中断电文件没有正常收尾索引没写进去这个 MP4 就是打不开的。有两种解法一是录制成 MPEG-TS 或者 Matroska这两种格式本身就是流式的断电也能播二是用 FFmpeg 的-movflags faststart或者分段录制时的-reset_timestamps 1配合正确的收尾。用 FFmpeg 直接切片录制的命令大概是这样ffmpeg -hide_banner -loglevel warning \ -rtsp_transport tcp \ -i rtsp://user:pass192.168.1.64:554/stream1 \ -c copy -an \ -f segment \ -segment_time 600 \ -segment_format mp4 \ -segment_format_options movflagsfaststart \ -reset_timestamps 1 \ -strftime 1 \ /data/rec/cam1/%Y%m%d_%H%M%S.mp4-segment_time 600是十分钟一片-reset_timestamps 1让每个文件的时间戳从零开始不然播放器打开第二个文件会发现时间戳是接着上一个的进度条行为会很怪。-strftime 1让文件名里的时间占位符生效这样文件名本身就是时间索引后面检索会很方便。如果更在意断电安全把-segment_format mp4换成-segment_format mpegts文件名后缀改成.ts代价是部分桌面播放器打开 TS 文件时会稍慢一点但换来的是任何情况下都能播。我现在的方案是 MP4 为主同时给关键的一路大门额外存一份 TS重要位置不省这点空间。4.3 磁盘写满了怎么办三道兜底录像系统最经典的故障就是磁盘写满。写满之后新录像写不进去有的程序会直接崩溃有的会静默失败——后者更可怕因为进程还在跑你以为在录其实早就没写了。第一道兜底是录制工具自带的清理。MediaMTX 的recordDeleteAfter参数就是干这个的设成168h就是保留七天超期的自动删。这个最省心优先用它。第二道兜底是定时清理脚本适合用 FFmpeg 自己切片录制的场景。写个简单的 find 命令挂到 cron 上每天跑一次#!/bin/bash # 删除 7 天前的录像 find /data/rec -type f -name *.mp4 -mtime 7 -delete find /data/rec -type d -empty -delete # 记录当前磁盘使用率 df -h /data | tail -1 /var/log/rec_disk.log-mtime 7是按修改时间算超过七天的删掉。注意后面那条删空目录不然目录会越积越多。第二行把磁盘使用率写日志方便回查。第三道兜底是监控告警。这一道最容易被忽略但恰恰是最重要的。我现在的做法是让服务端每分钟检查一次最新录像文件的时间戳如果超过 15 分钟没有新文件产生就发一条通知给自己。这个检查能同时覆盖两种情况磁盘满了没写进去以及推流断了没在录。实现上就是一个脚本取目录下最新的文件名和当前时间做差超过阈值就报警。#!/bin/bash latest$(ls -t /data/rec/cam1/*.mp4 2/dev/null | head -1) if [ -z $latest ]; then echo cam1 无录像文件 exit 1 fi age$(( ($(date %s) - $(stat -c %Y $latest)) / 60 )) if [ $age -gt 15 ]; then echo cam1 最新录像距今 ${age} 分钟 exit 1 fi这类脚本的价值在于它能在你发现问题之前发现问题。我第一次磁盘写满的时候是三天后想回放才发现的那三天的画面全没了。有了这个检查之后最坏情况是延迟一分钟知道还能补救。4.4 回放检索文件名就是最好的索引回放这件事用户真正需要的不是一个播放器而是快速定位到某个时间点。如果用文件名带时间戳的方式存储检索逻辑会变得极其简单——根本不需要数据库扫一遍目录就能生成时间轴。目录结构建议按设备/日期分层文件名用日期_时间/data/rec/ ├── cam1/ │ ├── 20240315_080000.mp4 │ ├── 20240315_081000.mp4 │ └── 20240315_082000.mp4 └── cam2/ └── ...这样一天的文件在一个列表里排好序缺失的时段一眼就能看出来——比如 08:00、08:10、08:30中间的 08:20 没了说明那十分钟断过流。这种缺口可见的特性比什么监控面板都直观。如果要做网页端的回放界面思路是把目录扫出来的文件列表转成 JSON前端按时间生成一个可以选择的时间轴点哪段播哪段。切片长度统一在十分钟的话一天 144 段前端渲染毫无压力。播放用原生video标签就够了不需要任何流媒体协议因为文件已经在服务端上了直接走 HTTP 静态文件就行。5. 让它连续跑下去那些跑三个月才会暴露的问题把系统装起来跑通可能只需要一个周末。让它连续跑两年不出事需要的是另一套东西。这一章讲的全是我在实际运行中撞上的问题共同点是它们在搭建当天完全不会显现都是跑到第二周、第二个月才冒出来。5.1 重启之后谁先起来启动顺序与依赖断电重启是最常见的故障场景也是最容易暴露架构问题的场景。我遇到过一次整栋楼跳闸恢复供电之后监控系统有三路没起来过了两天才发现。原因很简单服务端启动的时候网络还没就绪拉流失败退出systemd 虽然配了重启但重启几次之后放弃了。或者反过来服务端起来了但摄像机那边还没启动完成服务端拉不到流超时退出。解决办法是三层。第一层是 systemd 的Afternetwork-online.target和Wantsnetwork-online.target保证网络就绪再启动服务。第二层是把Restartalways和RestartSec设好即使失败也一直重试别设重启次数上限。第三层是拉流端自己要有重连逻辑别指望一次成功。如果摄像机本身启动缓慢有些设备上电到能出流要一两分钟可以在启动脚本里加一个等待探测循环检测 RTSP 端口通不通通了再拉起录制进程。这块多花十分钟写个脚本能省掉以后每次停电后的手工排查。until nc -z -w 2 192.168.1.64 554; do echo 等待摄像机就绪... sleep 5 done这个循环会一直等到端口可达才继续配合 systemd 的启动顺序基本能解决启动期的竞态问题。注意别写成死循环没有超时加个最大重试次数更稳妥。5.2 时间戳漂移会让回放彻底变成灾难摄像机的时钟走时不准是个普遍问题。便宜设备用的晶振精度有限一个月漂几分钟很正常。单看一路没什么影响但如果你有四路每一路都往不同方向漂回放的时候想把四个画面按同一时刻对齐就会发现它们差了十几秒甚至几分钟。更麻烦的是文件名。如果摄像机的时间戳被写进了文件名而摄像机时间又是错的你按时间检索的时候就会找不到东西——明明记得是下午三点实际文件名是两点五十。解决办法是强制的让所有摄像机和服务端都同步到同一个时间源。服务端好办装个时间同步服务指向内网或者公网的时间服务器。摄像机方面主流设备都支持在设置里填时间服务器地址把它们全部指向服务端的内网地址同步间隔设成最短的选项通常是每小时或者每 6 小时。同步做完之后还要检查时区设置。这一条我踩过坑摄像机默认是 UTC服务端是本地时区两边差了八小时录像文件名全是错的。把所有设备的时区统一成同一个值这一项比同步本身还容易漏。如果摄像机不支持配置时间服务器那就退一步用服务端时间做文件命名而不是用流里的时间戳。FFmpeg 用-strftime 1的时候默认用的是本地系统时间这一点反而是好事至少服务端的时钟是准的。5.3 供电、散热和网线不技术但最致命跑久了才知道监控系统最大的敌人不是软件 bug是物理世界。供电小功率设备用原装适配器没问题但如果服务端接了机械硬盘启动瞬间的电流冲击可能让劣质适配器掉压硬盘反复重启很快就坏了。我现在的做法是给服务端单独配一个质量靠谱的电源功率留足 50% 余量。如果所在环境经常停电加一个小容量 UPS能撑十分钟就够——足够撑到正常关机避免文件系统损坏。散热单板计算机塞进弱电箱是常见做法但弱电箱通常密不透风夏天内部温度能到五六十度SD 卡和电源模块是最先扛不住的。我的处理是把设备贴在箱体外侧或者至少留出通风口加一个小风扇。硬盘位的温度也要盯机械盘长期超过 50 度寿命会明显缩短用smartctl定期看一眼温度曲线。网线前面提过一次这里再强调一下。监控点位通常布线距离长劣质网线在长距离下丢包率会明显上升表现就是画面时不时花一下、断一下但设备 ping 得通很容易误判成软件问题。我的经验是只要出现规律性的短时断流先换线换头比在软件里查半天快得多。防潮和防尘户外点位要考虑防水室内点位要注意积灰。摄像机镜头前面积灰会影响画质一年擦一次是必要的维护。这一条没什么技术含量但确实是长期运行的一部分。5.4 上线前的一份自查清单最后整理一份我自己在每次新增点位或者迁移环境时都会过一遍的清单都是前面这些内容的具体落点。检查项具体要求为什么重要取流协议已确认走 TCP超时参数已设UDP 丢包导致花屏超时缺失导致进程僵死码率控制已设为固定码率记录实际值容量估算的前提实际值才准时间同步所有设备指向同一时间源时区一致回放对齐和文件检索的基础录像切片10 分钟一段带时间戳文件名断电损失可控检索方便磁盘余量剩余空间不低于 15%满了之后写入会静默失败清理策略自动清理已生效并验证过配置了不等于跑起来了失联告警15 分钟无新文件触发通知唯一能主动发现故障的手段断电恢复模拟断电后全链路自动恢复启动顺序和重连逻辑的最终验证存储介质已确认是垂直记录盘叠瓦盘在连续写入下会严重掉速隐私边界拍摄范围不涉及他人私密空间这是底线不是可选项最后一条我想多说两句。自己搭监控系统很容易因为反正只有我自己看就放松对拍摄范围的把控。但摄像头的视角一旦装上去覆盖范围往往比预想的大可能照到邻居的窗户、照到公共楼道、照到别人家门口。所以我给自己定的规矩是装之前先站到摄像机位置看一眼视野里不该有的区域要么调整角度要么在摄像机设置里用隐私遮蔽功能涂掉。这件事花不了十分钟但省掉的是以后的麻烦。这套系统我从零搭到稳定运行前后折腾了小半年砍掉的组件比留下的多。真正让它跑得住的不是哪一个高级工具而是把每个环节的失败模式想清楚然后给它留一条兜底的路——推流断了能重连录像断了能告警磁盘满了能清理断电了能自己爬起来。技术上没什么高深的东西耐心比技巧重要得多。