☰
从零搭建低成本视频监控系统:推流、播放与录像实践
2026/10/15 15:32:29 网站建设 项目流程

视频监控这个需求,听上去是非常成熟的领域,商用方案一抓一大把。但真到了自己动手做一套“够用、可控、不贵”的小监控系统时,会发现坑并不少:摄像头怎么选、推流用什么协议、浏览器里怎么播放、录像怎么存才不至于把硬盘撑爆。这篇是这个系列的第一篇,不搞花活,先把一条能跑通的最小链路搭出来:从摄像头采集、推流到流媒体服务,再到网页实时观看和本地循环录像,最后加一个最简单的移动侦测告警。适合想入门流媒体开发的朋友,也适合需要低成本监控方案的小工作室或极客玩家。

1. 项目需求拆解与总体架构

1.1 一个“简单”的视频监控系统,到底需要哪些部件

在动手之前,先把需求量化成四个刚需:实时画面、历史回放、告警提醒、稳定运行。

实时画面是第一优先级,打开网页或者手机就能看到现场,而不是叫我去调试播放器;历史回放解决“刚才发生了什么”,至少能翻最近几天的录像;告警提醒负责“有情况叫我”,比如检测到人形或者大范围画面变化;稳定运行则要求 7×24 小时不三天两头断流。

顺着这四个刚需,整个系统自然被拆成五个部分:

  • 视频源:摄像头,负责采集画面
  • 推流端:把摄像头原始画面编码并送出去
  • 流媒体服务:负责接入、协议转换和多路分发
  • 播放端:浏览器或手机 App,负责把画面播出来
  • 存储与告警:录像循环覆盖,以及画面变化时的通知

用一句话串起来就是:摄像头画面通过推流工具送入流媒体服务,服务再分发给播放器,同时录像模块持续把流写入磁盘,告警模块盯着画面找变化。

这个架构和商用监控的原理一致,只是把厂家私有协议换成了开放标准协议。优点是每个环节都可以替换,某个模块挂了不至于影响全局。这也是我个人比较建议的起点:先接受这套“不酷但稳”的架构,后面再加 AI 识别、云存储都方便。

1.2 为什么选“推流 + 流媒体服务 + 播放器”而不是其他

有些人会问:能不能让摄像头直接连浏览器,省掉中间那台服务器?答案是能,但会有三个明显问题。

一是浏览器原生不支持 RTSP 协议,想在网页里直接看,要么装插件(早已被淘汰),要么自己做协议解析与封装,工程量不小。二是多路视频并发时,摄像头自己扛不住,通常一路摄像头只能稳定服务一两个播放端。三是权限和安全没法统一管理,摄像头一旦暴露到公网,风险直线上升。

WebRTC 是另一个常被提起的方案。它的延迟确实低,几百毫秒级别,做实时互动很香,但信令服务、NAT 穿透、STUN/TURN 中继,这些组件单独拎出来都是一篇长文。如果只是让几个人看监控,WebRTC 的复杂度明显偏高,属于杀鸡用牛刀。

所以这个项目选的是一条中间路线:摄像头用 RTSP 或 RTMP 推流到流媒体服务,服务统一转成 HLS 和 HTTP-FLV 两种格式。播放端按场景选择用哪个协议,既保证了兼容性,又把延迟控制在可接受范围。内网实时预览用 HTTP-FLV 大约 1 到 3 秒,手机和跨网络回放用 HLS 大约 5 到 8 秒。监控场景下这个延迟完全可以接受,换来的是开发量和稳定性的大幅改善。

2. 采集端设计与实现

2.1 采集设备怎么选:USB摄像头、网络摄像头还是开发板

采集端是整个系统的“眼睛”,选错会浪费大量排查时间。按照实际项目里最常见的三类做对比:

USB 摄像头(UVC 协议)最便宜也最好上手。插上开发板或者小主机,系统直接识别为 /dev/video0,用现成工具就能抓流,适合先把链路跑通。缺点是不方便远距离布线,USB 线超过 3 米信号就不太稳,而且本身不带编码,需要推流端用 CPU 或硬件编码器实时编码。

网络摄像头是监控场景的主力。自带 RTSP 服务,支持 POE 供电,网线一插几十米也没问题,很多还带红外夜视。选的时候注意 ONVIF 兼容性,这个标准协议可以让同一套软件管理不同品牌的摄像头。买回来第一件事必须改默认密码,这个环节别偷懒。

开发板摄像头(MIPI/CSI 接口)画质好、体积小、功耗低,适合做嵌入式外壳。但驱动配置比较折腾,不同内核版本的摄像头驱动可能不一样,新手容易被坑。我的建议是:第一套系统先用 USB 摄像头跑通全部链路,再考虑换成网络摄像头或开发板方案。这能帮你把“硬件问题”和“配置问题”分开排查,不至于摄像头没图像时到处瞎猜。

顺便提一句选型参数。做监控,分辨率 1080P 是起步,帧率 15 到 25 帧足够;如果夜晚环境没有补光,优先选带红外或者支持低照度的型号;有运动目标检测需求时,尽量选感光度好的传感器,太便宜的摄像头噪点会严重影响后面的移动侦测准确度。

2.2 推流参数怎么调:编码器、码率、关键帧间隔一把说清

采集画面只有编码压缩之后才适合网络传输。编码这一步是视频监控最核心的参数调节点,几个关键概念要理解透。

编码格式选 H.264 还是 H.265。H.265 同等画质下码率更低,但编码更慢、兼容性稍差,入门阶段建议直接用 H.264,省下的带宽以后再考虑。

编码速度与延迟的取舍。推流工具里有一组常见的“预设”参数,选非常快模式能明显减少编码耗时,对延迟很敏感的场景再加零延迟调优选项。这两个参数组合起来,局域网延迟可以从两三秒降到一秒左右。

关键帧间隔(GOP)决定播放器从哪里能开始解码。监控直播建议 2 秒一个关键帧,也就是帧率 25 时设成 50。录像切片时会让切片时长与关键帧间隔对齐,否则播放器切换分片时容易出黑屏或者花屏。

码率直接影响画质和存储。1080P 25 帧的室内画面,静止场景 2 Mbps 够用,有持续人移动或者环境复杂的场景建议拉到 4 Mbps 以上。码率设太低,画面会出现大量马赛克,动态场景尤其明显。

一段典型的推流命令长这样:

ffmpeg -f v4l2 -framerate 25 -video_size 1920x1080 -i /dev/video0 \ -c:v libx264 -preset veryfast -tune zerolatency \ -g 50 -b:v 2M -maxrate 2M -bufsize 4M \ -f rtsp rtsp://流媒体服务器地址:554/live/camera1

参数不是死的,按场景微调。摄像头如果本来就能输出 H.264 编码流,推流端不需要再做二次编码,直接拉 RTSP 再转发,CPU 占用会非常低;如果摄像头输出的是裸流,就要靠推流端编码。

ARM 开发板上做软编码 1080P 会比较吃力,实测 CPU 会跑满,发热明显。建议优先用支持硬编码的方案,或者在摄像头端直接输出 H.264 流,这样系统负荷会小很多。

2.3 采集端稳定运行与安全

监控系统最怕跑两天自己断了,而且断流发生在半夜。采集端稳定性的核心是“自动重连”和“开机自启”。

用 systemd 管理推流进程,配置自动重启很顺手:

[Unit] Description=Camera Stream Service After=network.target [Service] ExecStart=/usr/bin/ffmpeg -f v4l2 -i /dev/video0 ... Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target

设备权限问题也常见。某些 USB 摄像头插上后系统只有一个用户能访问 /dev/video0,建议加一条 udev 规则把设备权限打开,避免服务以 root 身份运行的尴尬。

网络摄像头方面,服务器主动拉流比摄像头推流更稳。拉流断了之后,流媒体服务一般会按固定间隔自动重连,相当于把异常恢复的活交给了服务端。另外补一个定时探活脚本,检测到长时间没有流数据时重启相关服务,几个月跑下来会很省心。

安全方面说三点:一是所有摄像头密码改成强密码,别用出厂默认密码;二是摄像头所在网络尽量和办公网隔离,避免监控流直接暴露;三是一切访问都走流媒体服务统一入口,不要直接把摄像头端口映射到公网。本地监控项目尤其要重视隐私,能本地存储就尽量别上云。

3. 流媒体服务与播放链路搭建

3.1 流媒体服务的作用与开源选型

流媒体服务就像一个“翻译官+分发器”。它把摄像头多样的输入协议统一收编,再转换成播放端想要的输出格式,顺便处理权限和多路并发的负载问题。

开源自建和云直播服务的取舍是很多人的纠结点。我的建议很直接:内网监控优先自建,也就是在局域网里跑一个轻量开源流媒体服务;如果摄像头点位跨越多个城市,再考虑云直播或自建边缘节点。自建的好处是数据不出内网、隐私可控、没有按流量计费的压力;缺点是公网访问时需要自己做端口映射和鉴权。

我习惯用轻量级开源流媒体服务,Docker 一键部署很省事:

docker run -d --name streaming-server \ -p 1935:1935 \ -p 554:554 \ -p 8080:8080 \ 开源流媒体服务镜像

跑起来以后,主要关注三件事:是否支持 RTSP 拉流/推流,是否自动转换 HLS 和 HTTP-FLV,以及管理界面里能不能看到实时流的在线状态。

3.2 接入拉流与转封装配置

把摄像头接入流媒体服务,有推流和拉流两种方式。

USB 摄像头的场景走推流:用前面那段 FFmpeg 命令,把编码后的视频送到流媒体服务器的 RTMP 或 RTSP 端口。网络摄像头则建议走服务端主动拉流,在流媒体服务的配置里填上摄像头的 RTSP 地址,类似:

rtsp://用户名:密码@摄像头IP:554/stream1

如果摄像头本身支持 RTSP,服务器每隔几秒检查一次连接,断线后自动重连,整个过程不需要额外脚本。

转封装参数决定了播放体验。HLS 切片时长建议和关键帧间隔对齐,比如 2 秒一个切片。切片越小延迟越低,但会产生更多文件碎片;切片太大延迟明显变高。播放列表长度建议保留 10 到 20 个切片,保证页面刷新后能快速起播。

HTTP-FLV 是低延迟场景的首选。它本质上是一个不断追加数据的 HTTP 流,播放器边下载边播放,局域网内实测延迟可以做到 1 到 3 秒。要注意开启服务端的 CORS 跨域配置,否则浏览器里播放器会报跨域错误。

3.3 Web端播放的实现与延迟实测

浏览器不能直接播 RTSP,所以前端需要依赖一个能解析 HTTP-FLV 或 HLS 的开源播放器库。

以 HTTP-FLV 为例,页面里引入播放器 JS 后,写一个最简单的播放器:

<video id="player" controls autoplay muted></video> <script src="播放器库地址"></script> <script> if (flvjs.isSupported()) { var video = document.getElementById('player'); var flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'http://流媒体服务器地址:8080/live/camera1.flv' }); flvPlayer.attachMediaElement(video); flvPlayer.load(); flvPlayer.play(); } </script>

HLS 的接入类似,换成对应播放器库,把视频源地址指向 .m3u8 文件即可。iOS 的 Safari 对 HLS 支持较好,移动端页面优先用 HLS;桌面浏览器用 HTTP-FLV 看实时画面,延迟体验更好。

我做过的实际延迟测试,同一台局域网服务器、同一路摄像头:HLS 从画面变化到屏幕上出现大约 5 到 8 秒,HTTP-FLV 大约 1 到 3 秒。如果你硬追求毫秒级,那只能上 WebRTC,但对监控场景意义不大,反而增加不少运维负担。

播放端调优还有一个容易被忽略的点:播放器内部的缓冲时长设置。缓冲越长越不容易卡顿,但延迟也越大。监控页面建议把缓冲设置得短一些,优先保证实时性;如果网络抖动严重,再做折中。

4. 录像存储与告警联动

4.1 循环录像与存储容量估算

录像存储的核心是算清楚“能存几天”,别等硬盘满了才发现。

录像文件大小取决于码率。换算关系是:1 Mbps 等于每秒 0.125 MB,一个小时约 450 MB,一天 24 小时约 10.8 GB。单路 1080P 如果按 2 Mbps 码率录 7 天,大约需要 76 GB;按 4 Mbps 录 30 天,大约需要 1.3 TB。

下面这张表可以按实际码率快速估算:

码率每小时容量每天容量7天容量
1 Mbps450 MB10.8 GB76 GB
2 Mbps900 MB21.6 GB151 GB
4 Mbps1.8 GB43.2 GB302 GB
8 Mbps3.6 GB86.4 GB605 GB

录像建议按片段切分,比如 5 分钟一个文件,好处是删除旧文件时不用动整个大文件,播放器定位到某段也更容易。目录结构按日期分目录很实用,例如record/2024/05/18/18-25.mp4,调试时一眼就能找到对应时间点。

循环覆盖可以用一个简单的定时清理脚本:找到超过保留天数的文件直接删除。同时监控一下磁盘使用率,超过阈值时在管理页面标红提醒,避免“满了就静默停录”的情况。

4.2 移动侦测与告警通知的实现思路

移动侦测是给监控系统加“主动性”的第一步。商用方案里那些花哨功能,本质上都是在检查画面有没有变化。

最简单的实现方法是帧差法:每隔一定帧数截取一帧灰度图,和上一帧做差,统计像素变化的比例,超过阈值就认为有动态。代码量不大,却能处理大部分室内场景。

用开源视觉库做一个轻量侦测,核心逻辑类似这样:

import cv2 cap = cv2.VideoCapture("rtsp://摄像头地址") ret, prev = cap.read() prev_gray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY) while cap.isOpened(): ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) diff = cv2.absdiff(prev_gray, gray) change = (diff > 25).sum() total = diff.shape[0] * diff.shape[1] ratio = change / total if ratio > 0.01: # 触发告警,顺便保存当前帧 cv2.imwrite("./alarm/alarm.jpg", frame) prev_gray = gray

直接对每一帧判断会产生大量误报和重复告警。实用的做法是“连续 N 帧变化率超阈值才触发”,比如 3 帧,并且触发后进入 30 秒冷却期,期间不再重复告警。这能把灯光闪烁、窗帘飘动这类瞬态变化过滤掉。

告警通知建议优先走 Webhook 方式:侦测程序往内部接口发一个 POST 请求,内部服务负责推送通知到手机 App 或聊天工具。邮件通知也可以保留,作为备用通道。告警截图单独存到一个固定目录,和录像文件分开,便于快速翻看。

4.3 录像文件的管理与时间检索

录像存下来之后,如果没有办法回放,那它只是占用硬盘的数据。回放页面的体验,很大程度上取决于文件的组织方式。

我的做法是录像文件和播放索引分离:文件系统负责按照“日期 + 小时 + 分钟”的规则存储,Web 后端提供一个目录接口,返回某一天所有分片文件的列表。前端拿到列表后,就能按 5 分钟一段的方式拼装播放列表,用户拖动时间轴到任意位置,自动加载对应分片。

侦测事件和录像的关联也很重要。移动侦测触发时记录一个 Unix 时间戳,前端点击告警记录后,用户可以直接跳到录像中对应的时间点,比如事件发生在 18:25:12,就定位到18-25.mp4这个分片播放。这一条链路打通后,监控系统从“能看”变成了“好查”,体验好不少。

告警截图与录像时间戳之间的同步也很关键:事件发生时,可以顺便把当前系统的 UTC 时间写入事件表,避免服务端与前端所在时区不同导致的时间错位。

5. 常见问题与排查技巧实录

5.1 画面卡顿、延迟膨胀、花屏怎么定位

视频监控项目里最常见的三张故障脸,我按实际踩坑频率排一下。

画面卡顿且持续一卡一卡,大概率是源端问题。先用开源播放器(比如 VLC)直接打开推流地址,如果卡顿依旧,说明问题在推流端或编码参数;如果播放器里流畅但网页卡,那就是流媒体服务分发或浏览器端解码的问题。

延迟持续增长,几分钟后越来越慢,通常是缓冲策略问题。推流端的零延迟调优没开、播放器缓冲过大、或者流媒体服务的队列积压,都会导致延迟像滚雪球一样变大。按“源头推流工具 -> 流媒体服务 -> 播放器”的顺序逐段查看延迟,定位很快。

花屏和音画不同步,多半是帧丢失。摄像头推流默认用 UDP 传 RTSP,丢包就会花屏,改成 TCP 传输通常能明显改善。另外关键帧间隔太大时,播放器长时间收不到关键帧也会花屏,把间隔调小一些试试。

5.2 多路访问与外网访问的坑

内网多人同时观看时,流量计算很容易被忽略。假设有 5 个人同时看一路 4 Mbps 的流,服务器下行带宽至少得保障 20 Mbps。如果画面同时卡顿,先看服务器网卡流量,再看客户端各自带宽,通常能定位到是不是有人在用流量下载。

外网访问是容易踩大坑的地方。直接把流媒体服务端口映射到公网,意味着任何人都能拿到流地址,即使是不公开的也容易被扫描。建议至少加一层访问令牌或鉴权接口,前端带上有效令牌才能拉流;有条件的话限制海外 IP 和白名单,安全系数会高不少。

NAT 环境下,摄像头的摆放位置很关键。如果摄像头在办公楼内网,流媒体服务器也在同一内网,那一切简单;如果服务器在云端,摄像头在本地,推荐让云端服务器主动拉本地的 RTSP,这样摄像头只需对内网开放端口,不直接暴露公网。

5.3 长时间运行的稳定性记录

我遇到过两次诡异现象,都是长时间运行后出现的。

第一次是摄像头跑了两周后频繁掉线,后来发现是电源适配器发热严重,电压瓦数不够,换成足额电源后问题消失。第二次是录像写满后画面卡死,排查后发现是循环脚本没有把服务重载,旧文件虽然删了但进程没释放句柄,增加清理后的服务重载就解决了。

长时间运行的另一个隐患是存储介质寿命。普通 SD 卡连续写入会很快损坏,建议用支持循环写入的监控级存储卡,或者直接把录像写到 NAS 或机械硬盘。机械硬盘长时间连续写入也建议选 7×24 小时工作类型,监控级硬盘并非营销概念。

日志文件也要限制大小。推流工具如果每秒输出一行日志,几天能写几个 G。配置输出到文件,并配合系统的日志轮转工具定期清理,或者直接把日志配置成按小时滚动,超过保留数量自动删除。

5.4 排查速查表

现象可能原因快速检查解决办法
浏览器画面黑屏跨域未配置、播放器库没加载打开控制台看报错开启 CORS,确认 JS 库 URL 可用
画面一直卡顿推流端 CPU 占满看推流工具 CPU 占用换硬件编码或降低分辨率
延迟越来越大缓冲策略不匹配对比直播流和录像流延迟调整播放器缓冲、推流端启用零延迟
花屏UDP 丢包关闭其他流量测试改用 RTSP over TCP
录像文件缺失时间没校准或磁盘已满看服务器时间和磁盘剩余配置 NTP,清理旧录像
告警频繁误报阈值过低、光线变化查看告警截图内容连续帧判定、调高阈值、开冷却期

我个人在实际操作中的体会是:这类项目最怕一上来就追低延迟和高画质,结果被各种协议兼容性和网络穿透问题拖死。先用一条 RTSP 链路把内网跑通,再逐步加上录像、告警、外网访问,每走一步确认一步,系统就会非常可控。这个系列的第二篇,我打算把移动侦测升级成人形识别,让告警和录像回放真正闭环,到那一步就需要引入模型推理了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询