资讯详情

C++视频监控源码拆解:MFC框架下解码、渲染与网络模块实战

📅 2026/10/18 12:57:15 | 华诺云谱 👁 阅读
C++视频监控源码拆解:MFC框架下解码、渲染与网络模块实战
简介这是一套面向C初学者与Windows桌面开发者的视频监控系统完整源码基于MFC框架与C语言编写在VC6.0环境下开发适合想通过真实项目理解GUI编程、视频采集与多线程处理的进阶学习者。压缩包共85个文件约2.78MB以cpp与h源码文件为核心辅以dsp、dsw等VC工程文件、bmp与ico界面资源、mdb数据库及avi演示视频工程结构完整可直接编译研究。源码覆盖视频采集、DirectShow或OpenCV视频处理、MFC界面显示、录像存储、多路监控、RTSP网络传输、报警事件处理与数据库记录等模块并包含登录、回放、系统设置等对话框实现。目前已有621人学习下载读者可借此跳过零散概念直接进入实战掌握MFC框架、视频流处理、多线程与网络通信等综合技能。1. 从一份 C 视频监控源码说起MFC 老框架还能怎么打前阵子帮一个做安防集成的朋友看项目他手里有一套跑了七八年的 C 视频监控系统源码基于 MFC 开发界面是老式对话框风格但功能一点不含糊——多路摄像头预览、录像回放、云台控制、报警联动全都有。他问我这套东西还能不能改能不能接新的网络摄像机我花了两天把代码翻了一遍结论是能改而且比想象中好改。这套源码的价值不在于界面多现代而在于它把视频监控系统最核心的几条链路——设备接入、码流解码、画面渲染、录像存储——用 C 和 MFC 老老实实实现了一遍。对于想入门安防开发、或者需要一套可运行的视频监控系统源码做二次开发的开发者来说这是一个很实在的起点。它适合两类人一是刚接触 Windows 桌面端音视频开发、想找一个完整项目练手的二是手里有老系统维护需求、需要快速理解 MFC 视频监控架构的。下面我就按「这项目怎么跑起来 → 核心模块怎么实现的 → 怎么改」的顺序把这套源码拆开讲。2. 环境搭建与首次运行从零把 MFC 视频监控系统跑起来2.1 开发环境与依赖清单这套源码是典型的 Windows 桌面端 C 项目开发环境用 Visual Studio 最稳妥。我实测用的是 VS2019 社区版VS2022 也能编译但需要注意 MFC 组件要单独勾选安装。很多新手第一次打开项目就报「找不到 afxwin.h」就是因为装 VS 的时候没勾「使用 C 的桌面开发」里的 MFC 支持。依赖方面这套视频监控系统源码主要用到三类库依赖类型具体组件作用系统库MFC、Win32 API界面框架、窗口消息、线程网络库Winsock2与网络摄像机通信解码库FFmpeg常见做法H.264/H.265 码流解码渲染库GDI / DirectDraw视频画面绘制这里要说明一下原文没有明确写解码库用的是哪个版本但按视频监控系统的常规做法FFmpeg 是最常见的选择。我一般会去 FFmpeg 官网下载 Windows 预编译包把 include 和 lib 目录配到项目属性里。如果你拿到的源码里已经带了 third_party 目录那就省事了直接检查库文件是否完整即可。配置步骤大致是右键项目 → 属性 → VC 目录 → 包含目录加上 FFmpeg 的 include 路径库目录加上 lib 路径然后在链接器 → 输入 → 附加依赖项里加上 avcodec.lib、avformat.lib、avutil.lib、swscale.lib。这几个库分别负责解码、封装格式解析、工具函数和图像缩放是视频监控系统里绕不开的。提示如果你用的是 64 位 FFmpeg 库项目平台也要切成 x64否则链接时会报「模块计算机类型 x86 与目标计算机类型 x64 冲突」这个坑我踩过不止一次。2.2 编译与运行第一次看到画面环境配好之后直接 F5 编译运行。第一次跑起来大概率会遇到两个问题一是没有摄像头设备预览窗口全黑二是如果源码里写死了设备 IP连不上就会一直弹重连提示。我的建议是先把程序跑起来看到主界面框架再去改设备配置。主界面通常长这样左边是设备树右边是 2x2 或 3x3 的视频预览宫格底部是录像、抓拍、云台控制按钮。这套 MFC 视频监控系统的界面逻辑集中在主对话框类里比如 CMainDlg 或者 CMonitorDlg你可以在解决方案资源管理器里找到对应的 .cpp 文件。运行起来之后如果要接真实摄像机需要改设备配置文件。常见做法是源码里有一个 config.ini 或者 devices.xml里面存了设备 IP、端口、用户名、密码、通道号。格式大概是这样[Device1] IP192.168.1.64 Port8000 Useradmin Passwordadmin123 Channel1 ProtocolRTSP改完之后重启程序如果网络通、账号密码对预览窗口就应该出画面了。如果不出画面先别急着改代码用 VLC 或者 ffplay 测一下 RTSP 地址能不能拉流这一步能排除掉一大半「到底是网络问题还是代码问题」的玄学。2.3 项目目录结构速览这套源码的目录结构比较典型我整理了一下核心部分VideoMonitor/ ├── VideoMonitor.sln # 解决方案文件 ├── VideoMonitor/ # 主工程目录 │ ├── MainDlg.cpp/.h # 主界面对话框 │ ├── DeviceTree.cpp/.h # 设备树控件 │ ├── VideoWindow.cpp/.h # 视频渲染窗口 │ ├── Decoder.cpp/.h # 解码线程封装 │ ├── NetClient.cpp/.h # 网络通信模块 │ ├── RecordMgr.cpp/.h # 录像管理 │ ├── PtzCtrl.cpp/.h # 云台控制 │ └── Config.cpp/.h # 配置读写 ├── third_party/ # 第三方库 │ ├── ffmpeg/ │ └── ... └── res/ # 资源文件这个结构清晰地把界面、解码、网络、录像分开了二次开发的时候定位问题很方便。比如画面卡顿先看 Decoder 和 VideoWindow连不上设备先看 NetClient 和 Config。3. 核心模块拆解解码、渲染、网络三条链路怎么串3.1 解码模块FFmpeg 拉流与帧回调视频监控系统的核心是解码。这套源码里解码逻辑封装在 Decoder 类里每个视频通道对应一个解码线程。线程里做的事情就是从网络模块拿到码流数据 → 送进 FFmpeg 的 AVCodecContext → 拿到 AVFrame → 转成 RGB → 回调给渲染窗口。关键代码大概长这样// Decoder.cpp 解码线程主循环简化示意 void CDecoder::DecodeThread() { AVPacket packet; AVFrame* frame av_frame_alloc(); AVFrame* rgbFrame av_frame_alloc(); while (m_bRunning) { // 1. 从网络缓冲区取一帧码流数据 if (!m_netClient-GetPacket(packet)) { Sleep(10); // 没数据就歇一下避免空转吃满 CPU continue; } // 2. 送进解码器 int ret avcodec_send_packet(m_codecCtx, packet); if (ret 0) { av_packet_unref(packet); continue; } // 3. 取解码后的帧 ret avcodec_receive_frame(m_codecCtx, frame); if (ret 0) { // 4. 转 RGB方便 GDI 绘制 sws_scale(m_swsCtx, frame-data, frame-linesize, 0, frame-height, rgbFrame-data, rgbFrame-linesize); // 5. 回调给界面线程渲染 if (m_pFrameCallback) m_pFrameCallback(rgbFrame-data[0], frame-width, frame-height); } av_packet_unref(packet); } av_frame_free(frame); av_frame_free(rgbFrame); }这段代码有几个关键点。第一avcodec_send_packet和avcodec_receive_frame是 FFmpeg 新版 API 的用法老版本用的是avcodec_decode_video2如果你拿到的源码用的是老 API编译时会有弃用警告但一般还能跑。第二sws_scale做的是 YUV 到 RGB 的转换视频监控里摄像头出来的基本都是 YUV420P而 GDI 绘制需要 RGB这一步不能省。第三回调函数m_pFrameCallback是解码线程和界面线程的桥梁注意这里不能直接操作 MFC 控件必须用PostMessage把数据传到界面线程再绘制否则会出各种莫名其妙的崩溃。参数方面m_codecCtx是解码器上下文初始化的时候要指定编码格式常见做法是从 RTSP 的 SDP 信息里解析出来或者直接暴力尝试 H.264 和 H.265。m_swsCtx是缩放上下文初始化时指定源宽高和目标宽高如果预览窗口大小变了记得重建这个上下文。3.2 渲染模块MFC 窗口里画视频画面渲染这块这套源码用的是 GDI 的StretchDIBits把 RGB 数据直接画到窗口 DC 上。虽然性能比不上 DirectDraw 或者 D3D但对于 4 路 1080P 预览来说够用了。渲染的关键在于双缓冲不然画面会闪。常见做法是在内存里建一个兼容 DC 和位图先画到内存位图再一次性 BitBlt 到窗口。代码示意// VideoWindow.cpp 绘制一帧画面简化示意 void CVideoWindow::DrawFrame(BYTE* pRgbData, int nWidth, int nHeight) { CDC* pDC GetDC(); if (!pDC) return; // 内存 DC 双缓冲防闪烁 CDC memDC; memDC.CreateCompatibleDC(pDC); CBitmap memBmp; memBmp.CreateCompatibleBitmap(pDC, m_nWndWidth, m_nWndHeight); memDC.SelectObject(memBmp); // 构造 BITMAPINFO指定 24 位 RGB BITMAPINFO bmi { 0 }; bmi.bmiHeader.biSize sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth nWidth; bmi.bmiHeader.biHeight -nHeight; // 负号表示自上而下 bmi.bmiHeader.biPlanes 1; bmi.bmiHeader.biBitCount 24; bmi.bmiHeader.biCompression BI_RGB; // 画到内存 DC StretchDIBits(memDC.GetSafeHdc(), 0, 0, m_nWndWidth, m_nWndHeight, 0, 0, nWidth, nHeight, pRgbData, bmi, DIB_RGB_COLORS, SRCCOPY); // 一次性贴到窗口 pDC-BitBlt(0, 0, m_nWndWidth, m_nWndHeight, memDC, 0, 0, SRCCOPY); ReleaseDC(pDC); }这里biHeight用负数是个小技巧表示图像数据是自上而下的跟 FFmpeg 输出的 RGB 数据顺序一致不然画面会上下颠倒。StretchDIBits负责缩放如果窗口比例和视频比例不一致画面会拉伸变形想要保持比例就得自己算黑边填充区域。注意GetDC和ReleaseDC必须成对出现而且不要在解码线程里调用否则 GDI 资源泄漏是迟早的事。我见过一个项目跑了一天之后界面卡死最后查出来就是渲染线程里 DC 没释放。3.3 网络模块RTSP 交互与码流接收网络模块负责和摄像机通信这套源码里用的是 Winsock2 直接发 RTSP 请求。RTSP 协议本身是文本协议交互过程大概是OPTIONS → DESCRIBE → SETUP → PLAY然后摄像机开始通过 RTP 推流。核心代码在 NetClient 类里简化后的连接流程// NetClient.cpp RTSP 连接流程简化示意 bool CNetClient::Connect(LPCTSTR lpszUrl) { // 1. 解析 URL提取 IP 和端口 ParseUrl(lpszUrl, m_strIP, m_nPort); // 2. 创建 socket 并连接 m_sock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addr { 0 }; addr.sin_family AF_INET; addr.sin_port htons(m_nPort); inet_pton(AF_INET, m_strIP, addr.sin_addr); if (connect(m_sock, (sockaddr*)addr, sizeof(addr)) ! 0) return false; // 3. 依次发送 RTSP 命令 SendRequest(OPTIONS, lpszUrl); RecvResponse(); SendRequest(DESCRIBE, lpszUrl); std::string sdp RecvResponse(); ParseSdp(sdp); // 从 SDP 里拿编码格式、RTP 端口 SendRequest(SETUP, lpszUrl); RecvResponse(); SendRequest(PLAY, lpszUrl); RecvResponse(); // 4. 启动接收线程收 RTP 包 m_bRunning true; m_recvThread std::thread(CNetClient::RecvThread, this); return true; }这段代码里ParseSdp是关键SDP 信息里包含了视频编码是 H.264 还是 H.265、分辨率、帧率以及 RTP 包的 payload type。拿到这些信息之后解码器才能正确初始化。RTP 收包线程里要做的事情是收 UDP 包 → 按序列号排序 → 去掉 RTP 头 → 拼成完整的 NAL 单元 → 送给解码器。参数方面RTSP 默认端口是 554但很多摄像机改成了 8554 或者其他端口配置文件里要写对。另外 RTP 传输模式有 TCP 和 UDP 两种UDP 延迟低但容易丢包TCP 稳定但延迟稍高常见做法是优先 UDP丢包严重时自动切 TCP。4. 避坑与排查MFC 视频监控开发里最容易翻车的几个点4.1 画面卡顿或延迟越来越大现象预览刚开始正常跑了几分钟之后画面越来越卡延迟从几百毫秒涨到几秒。原因解码线程和渲染线程速度不匹配帧数据在缓冲区里越堆越多。常见于解码比渲染快的情况比如 1080P 解码出来是 25 帧但 GDI 绘制一帧要 60 毫秒每秒只能画 16 帧剩下的帧就积压了。解决在渲染回调里加丢帧逻辑如果上一帧还没画完就直接丢掉当前帧。或者把渲染频率和解码频率对齐解码线程每解出一帧就等渲染线程的信号不要无限制往队列里塞。4.2 程序运行一段时间后崩溃现象跑几个小时甚至一天之后程序突然闪退没有明显规律。原因GDI 对象泄漏或者内存泄漏。MFC 里GetDC、CreateCompatibleDC、CreateCompatibleBitmap这些如果不释放GDI 对象数会一直涨到一万个左右系统就不给分配了。另外 FFmpeg 的AVPacket和AVFrame如果忘记av_packet_unref和av_frame_free内存也会持续增长。解决用任务管理器看 GDI 对象数如果持续上涨就是泄漏。在渲染函数里确保每个 DC 和 Bitmap 都配对释放FFmpeg 的 packet 和 frame 用 RAII 封装或者确保每条路径都释放。4.3 多路预览时 CPU 占用过高现象4 路 1080P 预览CPU 直接跑到 80% 以上风扇狂转。原因每路解码都用独立线程而且 YUV 转 RGB 用的是 CPU 软转4 路同时转很吃资源。另外如果渲染用的是StretchDIBits且窗口很大缩放也是 CPU 在扛。解决常见做法是开启 FFmpeg 的硬件加速比如 D3D11VA 或者 DXVA2把解码放到 GPU 上。YUV 转 RGB 也可以用 shader 做但 MFC 里集成 D3D 渲染稍微麻烦一点。如果不想动架构至少把预览窗口缩小减少缩放开销。4.4 网络断线后无法自动重连现象摄像机网络波动一下画面就黑了程序不会自己恢复必须重启。原因网络接收线程在recv返回错误后直接退出了没有重连机制。而且解码器的状态也没有重置即使重新连上解码器里还有旧数据解出来的画面是花的。解决在接收线程里加循环重连逻辑检测到断开后先关闭 socket等几秒再重新走一遍 RTSP 流程。重连成功后要 flush 解码器把avcodec_flush_buffers调一下清掉残留数据。4.5 录像文件无法播放或时间不对现象录出来的 MP4 文件用播放器打不开或者能打开但进度条时间不对。原因录像封装的时候没有正确写 moov box或者时间戳用的是系统时间而不是流时间。MP4 的 moov box 默认在文件末尾如果录像过程中程序崩溃moov 没写进去文件就废了。解决录像时用av_write_frame而不是av_interleaved_write_frame并且在停止录像时确保av_write_trailer被调用。时间戳方面用av_rescale_q把流时间戳转成 MP4 的时间基不要直接塞系统时间。5. 二次开发改造从能跑到好用几个实用的改造方向这套源码跑通之后二次开发的空间其实挺大的。我按优先级说几个改造方向都是实际项目里验证过有用的。第一个方向是解码硬件加速。前面提到软解 4 路 1080P 很吃力改成 D3D11VA 硬解之后CPU 占用能降到 20% 以下。改造点在 Decoder 初始化的时候把avcodec_find_decoder换成avcodec_find_decoder_by_name(h264_d3d11va)然后给AVCodecContext设置hw_device_ctx。渲染那边也要改硬解出来的帧在 GPU 显存里不能直接sws_scale要么下载到内存再画要么直接用 D3D 渲染。如果只是想省 CPU下载到内存再走 GDI 也能接受性能还是比软解好。第二个方向是录像策略优化。现在的录像可能是全天候录硬盘很快就满了。可以改成移动侦测录像只有画面有变化才录。移动侦测的实现思路是解码出来的 YUV 帧取 Y 分量做帧差差异超过阈值就触发录像。这个逻辑加在解码回调里就行不需要改架构。第三个方向是设备接入协议扩展。现在源码里可能只支持 RTSP但市面上还有很多 ONVIF 设备和私有协议的摄像机。ONVIF 的接入可以用 gSOAP 生成代码把设备发现、能力获取、媒体配置这几个接口实现一下。私有协议就得看厂商文档了一般也是基于 RTP 传输改改 NetClient 里的握手逻辑就行。第四个方向是界面现代化。MFC 的界面确实老气如果客户对界面有要求可以考虑用 Qt 重写界面层解码和网络模块保持不变。Qt 的QOpenGLWidget渲染视频比 GDI 流畅得多而且跨平台。改造的时候把 Decoder 和 NetClient 编译成静态库Qt 工程直接链接就行。验证改造效果的方法很简单改完之后跑 24 小时压力测试看 CPU、内存、GDI 对象数是否稳定画面是否流畅断线重连是否正常。我一般会写一个简单的日志每 10 秒记录一次这些指标跑完之后看曲线比凭感觉靠谱得多。从那以后我每次拿到一套视频监控源码都强制先跑一遍 24 小时稳定性测试再动代码不然改出来的东西自己心里没底。希望这套拆解能帮到你源码跑起来之后剩下的就是按自己的需求慢慢磨了。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑