拓冰建站拓冰建站
首页 / 资讯中心 / 正文

C#家庭视频监控系统源码实战:从采集编码到TCP多客户端分发

简介这份资源是面向C#开发者与智能家居爱好者的家庭视频监控系统完整源代码适合希望从零理解监控系统架构、提升综合开发能力的中级学习者。项目以C#为主语言覆盖视频流处理、网络通信、数据库管理、用户界面设计、多线程异步编程、安全认证与报警通知等模块可帮助读者掌握从摄像头采集到远程查看的完整链路。压缩包为zip格式大小约5.34MB文件总数暂未提供具体类型明细亦未给出但结合描述可推断包含C#源码、界面资源与数据库脚本等。目前已有168人学习下载热度适中。通过研读源码读者能理解OpenCV或AForge.NET视频处理、RTSP/WebRTC传输、SQL Server或SQLite存储、WPF或WinForms界面以及移动端集成思路并借鉴多线程与安全加密的工程实践适合作为课程设计、毕业设计或自研监控项目的参考蓝本。1. 从一份 C# 家庭视频监控系统源码说起它到底能跑出什么很多人第一次拿到「c#源代码家庭视频监控系统.zip」这类压缩包第一反应是解压、双击 sln、F5 运行然后发现摄像头画面出不来于是判定「这源码是假的」。实际上一套能落地的 C# 家庭视频监控系统核心不是界面有多花哨而是三件事能不能闭环视频采集、编码传输、存储回放。它解决的是把家里几路 USB 摄像头或网络摄像头的画面在局域网内集中预览、录像、按时间回查适合想自己搭一套私有监控、又不想把画面上传到第三方云端的开发者。热词里频繁出现的 c#上位机、c# TCPListener 多客户端、c# 委托恰好对应这类系统的三个技术底座上位机负责界面与设备管理TCP 负责多路视频流分发委托负责解码线程回调到 UI 线程。这一章先把边界讲清楚后面几章再动手。2. 拆解家庭视频监控系统的技术栈采集、编码、传输、存储怎么选2.1 采集层USB 摄像头与 RTSP 网络摄像头两条路家庭场景里摄像头无非两类。一类是 USB 摄像头插上电脑就能用采集走 DirectShow 或 MediaFoundationC# 里常见做法是封装avicap32.dll或者用 AForge.NET、EmguCV 这类库抓帧。另一类是网络摄像头走 RTSP 协议用 FFmpeg 拉流最稳。选型上我的建议很直接如果你只是两三路 USB 摄像头用 AForge 抓帧足够代码量小如果要接海康、大华这类 IPC别自己写 RTSP 解析直接调 FFmpeg 命令行或 FFmpeg.AutoGen 绑定。采集层最容易翻车的地方是帧率不稳定。USB 摄像头在 640x480 下能跑 30fps一旦切到 1920x1080很多廉价设备直接掉到 10fps 以下而且GetCurrentImage这类调用是阻塞的。所以采集一定要放在独立线程用生产者消费者队列把帧丢给编码线程主线程只负责显示。// 采集线程把帧写入阻塞队列避免 UI 线程被拖死 private BlockingCollectionBitmap _frameQueue new BlockingCollectionBitmap(boundedCapacity: 5); private void CaptureLoop() { var device new VideoCaptureDevice(_monikerString); device.NewFrame (s, e) { // 队列满时直接丢弃旧帧保证实时性优先于完整性 if (!_frameQueue.TryAdd((Bitmap)e.Frame.Clone())) { // 丢弃策略监控场景宁可丢帧也不要堆积延迟 } }; device.Start(); }这段代码的关键参数是boundedCapacity。设成 5 意味着最多缓存 5 帧超过就丢。监控场景里延迟比丢帧更致命缓存 100 帧只会让你看到几分钟前的画面。NewFrame事件本身在采集线程触发所以Clone是必须的否则 Bitmap 被复用后画面会撕裂。2.2 编码层为什么监控系统绕不开 H.264原始 RGB 帧数据量极大1080p 一帧约 6MB30fps 就是 180MB/s硬盘和网络都扛不住。所以必须编码家庭监控事实标准就是 H.264。C# 里做 H.264 编码有两条路一是调 FFmpeg 的libx264通过管道传帧二是用硬件编码Intel 核显走 QSVNVIDIA 走 NVENC。我一般会优先用 FFmpeg 管道方案因为兼容性最好参数也好调。核心参数是码率和 GOP。家庭监控 1080p 建议码率 2~4MbpsGOP 设成帧率的 2 倍比如 30fps 就设 60。GOP 太大拖动回放时关键帧间隔远定位慢GOP 太小码率浪费在 I 帧上。# 从标准输入读 rawvideo编码成 H.264 存 flv便于后续切片 ffmpeg -f rawvideo -pix_fmt bgr24 -s 1920x1080 -r 30 -i - \ -c:v libx264 -preset veryfast -tune zerolatency \ -b:v 3M -g 60 -f flv rtmp://127.0.0.1/live/cam1-tune zerolatency是监控场景必加项它关闭 B 帧、减小缓冲把编码延迟压到最低。-preset veryfast是速度与压缩率的折中家庭机器 CPU 一般用medium会吃满核。-pix_fmt bgr24要和 C# 里 Bitmap 的像素格式对上否则颜色会偏。2.3 传输层TCPListener 多客户端分发的正确姿势热词里「c# TCPListener 多客户端」出现频率很高因为监控系统天然是一对多一路摄像头画面要同时给手机、平板、客厅电视。用 TCPListener 做分发核心是每个客户端一个独立发送线程并且要有背压控制。常见错误是主线程AcceptTcpClient后直接在里面NetworkStream.Write一个慢客户端就把整个分发卡死。正确做法是每个客户端维护一个发送队列队列满就断开该客户端保护整体。// 每个客户端独立发送循环队列满则主动断开慢客户端 private async Task ServeClient(TcpClient client, BlockingCollectionbyte[] sendQueue) { using var stream client.GetStream(); try { foreach (var packet in sendQueue.GetConsumingEnumerable()) { await stream.WriteAsync(packet, 0, packet.Length); } } catch (IOException) { // 客户端异常断开直接清理不影响其他连接 } finally { client.Close(); } }GetConsumingEnumerable会在队列空时阻塞队列有界时Add会阻塞或返回 false这就是背压。发送队列长度建议设 30 左右按每帧大小估算超过 2 秒的积压就说明客户端网络不行断开比拖着强。2.4 存储层按时间切片与索引设计录像存储不要写成一个巨大的 mp4否则断电就全废。常见做法是按分钟切片文件名带时间戳比如cam1_20250101_1430.mp4。数据库里建一张索引表记录摄像头 ID、开始时间、结束时间、文件路径。回放时先查索引再定位文件。CREATE TABLE recording_index ( id INTEGER PRIMARY KEY AUTOINCREMENT, camera_id TEXT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, file_path TEXT NOT NULL ); CREATE INDEX idx_cam_time ON recording_index(camera_id, start_time);索引必须建在(camera_id, start_time)上因为回放查询永远是「某摄像头某时间段」。切片时长 1 分钟是经验值太短文件数量爆炸太长断电损失大。磁盘满的处理策略也要提前想好一般是按时间淘汰最旧的文件同时删索引。3. 用 C# 把最小可运行版本跑起来从窗体到录像落盘3.1 项目结构与依赖准备最小版本我建议用 WinForms别一上来就 WPFWinForms 在视频渲染上更直接PictureBox 或 Panel 自绘都行。项目分四个模块Capture采集、Encode编码、Transport传输、Storage存储。NuGet 依赖只需要三个AForge.Video.DirectShow抓 USB 摄像头FFmpeg.AutoGen或直接调 ffmpeg.exeSystem.Data.SQLite做索引。目录结构建议这样别把所有代码堆在 Form1.cs 里MonitorSystem/ Capture/CameraCapture.cs Encode/H264Encoder.cs Transport/StreamServer.cs Storage/RecordingIndex.cs UI/MainForm.cs3.2 主窗体初始化与摄像头枚举// 枚举本机 USB 摄像头填充下拉框 private void LoadCameras() { var devices new FilterInfoCollection(FilterCategory.VideoInputDevice); foreach (FilterInfo device in devices) { cmbCameras.Items.Add(device.Name); } if (cmbCameras.Items.Count 0) cmbCameras.SelectedIndex 0; }FilterInfoCollection来自 AForgeFilterCategory.VideoInputDevice只列视频输入设备不会把音频设备混进来。枚举结果顺序不保证稳定所以配置里最好存设备名而不是索引否则换台机器索引就错位。3.3 启动采集与编码管道// 启动采集帧进入队列后由编码线程消费 private void StartCapture(string deviceName) { _capture new CameraCapture(deviceName); _capture.FrameReady (bmp) { // 交给编码线程UI 线程只做显示 _encodeQueue.Add(bmp); }; _capture.Start(); _encodeTask Task.Run(() EncodeLoop()); } private void EncodeLoop() { foreach (var bmp in _encodeQueue.GetConsumingEnumerable()) { byte[] raw BitmapToBgr24(bmp); _encoder.WriteFrame(raw); // 内部走 ffmpeg stdin bmp.Dispose(); // 及时释放否则内存暴涨 } }BitmapToBgr24要自己写用LockBits比GetPixel快几十倍。bmp.Dispose()这行千万别漏监控跑一晚上漏一个 Dispose 就是几个 G 的内存泄漏这是血泪经验。3.4 录像切片与索引写入// 每分钟切一个文件同时写索引 private void OnSegmentFinished(string cameraId, DateTime start, DateTime end, string path) { using var conn new SQLiteConnection(_connStr); conn.Open(); var cmd conn.CreateCommand(); cmd.CommandText INSERT INTO recording_index(camera_id,start_time,end_time,file_path) VALUES(c,s,e,p); cmd.Parameters.AddWithValue(c, cameraId); cmd.Parameters.AddWithValue(s, start.ToString(s)); cmd.Parameters.AddWithValue(e, end.ToString(s)); cmd.Parameters.AddWithValue(p, path); cmd.ExecuteNonQuery(); }时间统一用 ISO 8601 格式s避免本地化格式在 SQLite 里排序出错。切片完成事件由编码器回调触发不要在 UI 线程做数据库写入放到后台线程池。3.5 回放查询与文件定位// 按摄像头和时间段查录像文件 public Liststring QueryRecordings(string cameraId, DateTime from, DateTime to) { var result new Liststring(); using var conn new SQLiteConnection(_connStr); conn.Open(); var cmd conn.CreateCommand(); cmd.CommandText SELECT file_path FROM recording_index WHERE camera_idc AND start_timef AND end_timet ORDER BY start_time; cmd.Parameters.AddWithValue(c, cameraId); cmd.Parameters.AddWithValue(f, from.ToString(s)); cmd.Parameters.AddWithValue(t, to.ToString(s)); using var reader cmd.ExecuteReader(); while (reader.Read()) result.Add(reader.GetString(0)); return result; }查询条件用start_timefrom AND end_timeto能命中索引别写成BETWEEN再对 end_time 做函数运算那样索引失效。回放时按文件顺序依次播放跨文件处会有轻微跳帧属于正常现象。4. 避坑与排查家庭监控系统最容易翻车的五个点4.1 画面卡顿但 CPU 不高现象预览画面每隔几秒卡一下任务管理器 CPU 占用只有 20%。原因多半是 UI 线程在做 Bitmap 转换或绘制GDI 的DrawImage在高分辨率下会阻塞。另一个可能是采集队列满了在丢帧但丢帧策略写成了阻塞式Add。解决把 Bitmap 转 BGR 和缩放都放到后台线程UI 只接收已经处理好的Bitmap并Invalidate重绘。队列用TryAdd加丢弃策略别用阻塞Add。4.2 录像文件能播但时间对不上现象回放时画面内容和索引时间差了几分钟。原因索引写入用的是切片开始时间但编码器实际写入第一帧的时间受缓冲影响-tune zerolatency没加或者 FFmpeg 输出缓冲没刷新。解决编码参数加-tune zerolatency和-flush_packets 1切片结束时主动向 ffmpeg stdin 写q或关闭管道确保数据落盘后再写索引。4.3 多客户端连接后画面越来越慢现象第一个客户端正常连到第三个后所有客户端都变卡。原因发送队列无界慢客户端积压导致内存上涨同时分发线程被慢客户端阻塞。解决每个客户端队列设上界比如 30 帧TryAdd失败就断开该客户端。发送用独立 Task互不影响。4.4 磁盘写满后程序崩溃现象跑几天后程序无响应日志显示磁盘写入异常。原因没有磁盘配额检查和淘汰逻辑SQLite 写入也失败。解决启动时检查剩余空间低于阈值就删最旧的切片和对应索引。所有文件写入加 try-catch失败时降级为只预览不录像别让整个程序挂掉。4.5 摄像头拔插后无法恢复现象USB 摄像头拔掉再插上程序里设备列表不刷新采集线程报异常退出。原因AForge 的VideoCaptureDevice在设备移除后不会自动重连NewFrame事件停止触发。解决监听采集线程异常捕获后释放旧设备重新枚举设备列表并重启采集。加一个看门狗定时器超过 3 秒没有新帧就触发重连。5. 进阶技巧用委托与心跳包把系统做稳5.1 委托在解码回调里的正确用法热词里「c# 委托」反复出现在监控系统里它主要解决跨线程回调。采集线程、编码线程、UI 线程是三个不同的线程帧数据从采集传到 UI 必须跨线程。WinForms 里用Control.Invoke或BeginInvoke但直接写会让代码很乱。我一般定义一个委托类型把「帧到达」这件事抽象出来。// 定义帧到达委托采集层不关心谁消费 public delegate void FrameArrivedHandler(Bitmap frame); public class CameraCapture { public event FrameArrivedHandler FrameArrived; private void OnNewFrame(Bitmap frame) { // 触发委托订阅方自行决定在哪个线程处理 FrameArrived?.Invoke(frame); } }订阅方在 UI 层这样接_capture.FrameArrived (frame) { if (picPreview.InvokeRequired) picPreview.BeginInvoke(new Action(() picPreview.Image frame)); else picPreview.Image frame; };InvokeRequired判断当前线程BeginInvoke异步投递避免 UI 线程等待。注意frame的所有权要明确谁负责 Dispose 要约定好否则要么泄漏要么访问已释放对象。5.2 心跳包与断线重连网络摄像头和 TCP 客户端都需要心跳。服务端每 5 秒向客户端发一个空包或特定字节客户端 15 秒没收到就重连。心跳包不要用业务通道单独开一个短连接或者用固定长度的控制帧。// 服务端心跳每 5 秒向所有客户端发一个字节 0x00 private async Task HeartbeatLoop(CancellationToken token) { while (!token.IsCancellationRequested) { foreach (var client in _clients) { try { await client.Stream.WriteAsync(new byte[] { 0x00 }, 0, 1); } catch { RemoveClient(client); } } await Task.Delay(5000, token); } }心跳间隔 5 秒、超时 15 秒是经验值局域网内可以更激进公网环境要放宽。重连要有退避第一次 1 秒第二次 2 秒最多 30 秒避免网络抖动时疯狂重连把服务端打挂。5.3 验证系统稳定性的三个指标搭完之后别急着交付先跑三个指标。第一连续运行 72 小时内存增长不超过 10%超过说明有泄漏。第二模拟客户端断网重连 100 次服务端不崩溃、不泄漏连接。第三磁盘写满后程序仍能预览只是停止录像并告警。这三个过了家庭场景基本够用。我自己踩过最深的坑是内存泄漏跑一晚上从 200MB 涨到 3GB最后定位到是NewFrame里Clone出来的 Bitmap 在异常分支没释放。从那以后我养成习惯凡是new出来的 Bitmap后面必跟using或显式Dispose异常路径也要覆盖。监控系统是长期运行的东西任何小泄漏都会被时间放大。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门