ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

屏幕录像程序源码解析:DXGI采集与FFmpeg编码实战

屏幕录像程序源码解析:DXGI采集与FFmpeg编码实战 简介屏幕录像程序源代码面向需要学习桌面录屏原理的开发者可用于教程制作、游戏录制、软件演示等场景从主窗体设计、录像/截图设置到播放与管理操作均给出可运行实现。压缩包共58个文件约5.61MB以C源码.cpp/.h、对话框与资源文件.rc/.res/.ico为主另有程序使用说明文档、编译生成的exe及调试工程文件便于对照学习与二次开发。资源已获得594人学习代码结构清晰按界面模块划分包含名称设置、区域截取、画面截取等对话框逻辑并覆盖视频编码、文件管理、时间查看、错误处理与性能优化等关键点。通过阅读源码可掌握屏幕捕获、编码与文件操作的完整链路借助配套使用说明和分模块源码读者能快速定位主窗体、录像设置、截图及文件管理相关实现适合具备基础C知识、希望上手录像工具开发的开发者。 最近在整理手头一个屏幕录像程序的源代码顺便把录制引擎的核心模块又重新过了一遍。屏幕录像程序说穿了就是实时抓取屏幕上显示的画面经过编码压缩以后写入文件或者推流出去这个需求在教学演示、游戏录制、远程协助、安防监控这些场景里都会碰到。市面上现成的录屏工具很多但如果你想把它集成进自己的产品或者单纯想搞明白录屏软件背后的实现原理那源代码这一层是绕不过去的。这篇文章就围绕屏幕录像程序的源代码展开既讲画面采集和音视频编码的核心原理也会给出一套可以直接上手的工程骨架适合正在做桌面工具开发、流媒体方向的朋友参考。1. 项目概述与整体设计思路1.1 屏幕录像程序到底在录什么很多人觉得录屏就是把屏幕截图一帧帧存起来其实这个理解会把你带偏。一个真正可用的录屏程序至少要处理三路数据屏幕画面是一路不断更新的图像序列系统播放的声音是一路音频流麦克风采集是另一路音频流如果还要叠加摄像头画面那就是第四路。源代码的核心任务就是把这几路数据源并行采集在时间轴上对齐然后交给编码器转成压缩后的视频文件。这里有个关键认知录屏不是简单的定时截图。屏幕内容随时在变如果每一帧都做一次完整截屏帧率上不去文件体积也爆炸而且录出来的画面经常出现撕裂感。成熟的屏幕录像程序本质上是直接对接操作系统提供的图形接口主动去拉取帧缓冲而不是被动地一次次截屏。把这个逻辑想通后面看代码就会顺很多。1.2 模块划分与技术选型录屏程序的源代码常规工程会拆成下面几个核心模块画面采集模块负责抓取屏幕图像的原始帧音频采集模块负责捕获系统声音和麦克风输入编码模块把原始图像和音频压缩成H.264、AAC等格式封装写入模块把编码后的数据流封装成MP4、FLV或者MKV文件预览与控制模块负责界面显示、开始/停止、参数设置模块划分是第一步更关键的是技术选型。以Windows平台为例画面采集有GDI、DXGI Desktop Duplication、Windows Graphics Capture三种主流方案音频采集主要走WASAPI。选哪个方案直接决定代码的复杂度和录制性能。我先说结论如果开发Windows 10以上系统使用的录屏程序画面采集优先选DXGI Desktop Duplication次选Windows Graphics Capture最后才考虑GDI。原因后面具体讲。编码这边追求省事就用FFmpeg想减少依赖就用系统自带的Media Foundation各有取舍。2. 核心功能模块的实现拆解2.1 屏幕画面采集的原理与代码骨架画面采集是整个录屏程序最核心的部分。三种方案的底层逻辑完全不同。GDI方式是最老派的思路核心就一个BitBlt函数把屏幕设备上下文里的像素拷到内存位图里。这种方式实现最简单兼容性也好但性能很低。因为BitBlt是同步拷贝而且没有利用GPU加速在高分辨率下做全屏录制帧率很难超过20帧CPU占用却高得吓人。它比较适合低帧率截图场景比如做间隔抓拍。DXGI Desktop Duplication是Windows 8开始引入的方案原理是让录屏程序复制桌面副本。它有两个核心优势一是可以只在画面有变化时才拿到新帧静态桌面几乎不占用资源二是能够拿到GPU上的纹理数据整体性能比GDI强一个量级。缺点是需要创建后台线程持续调用AcquireNextFrame逻辑相对复杂一点。核心代码骨架大概是这个逻辑// 初始化阶段 IDXGIOutputDuplication* pDeskDupl nullptr; pOutput-DuplicateOutput(pDevice, pDeskDupl); // 采集循环 while (bRecording) { DXGI_OUTDUPL_FRAME_INFO frameInfo; IDXGIResource* pDesktopResource nullptr; HRESULT hr pDeskDupl-AcquireNextFrame( 500, frameInfo, pDesktopResource); if (hr DXGI_ERROR_WAIT_TIMEOUT) { continue; // 画面没变化继续等 } // 拿到纹理后转成可读格式 ID3D11Texture2D* pAcquiredDesktopImage; pDesktopResource-QueryInterface( __uuidof(ID3D11Texture2D), (void**)pAcquiredDesktopImage); // 这里做格式转换然后送编码器 // ... pDeskDupl-ReleaseFrame(); }Windows Graphics Capture是Windows 10 1803之后推出的新接口它比DXGI更现代支持窗口级捕获、HDR、多显示器。但它依赖系统组件Windows.UI.Composition在纯桌面应用里接入会绕一些。个人做工具类软件没有特殊需求时DXGI Desktop Duplication完全够用。注意DXGI Desktop Duplication不支持抓取本身受保护的内容比如DRM视频播放画面。如果产品有这个需求得用Windows Graphics Capture配合特定权限设置。2.2 音频采集与音视频同步音频采集大多数开发者容易忽略但录出来没有声音的录屏工具基本等于废了。Windows平台录系统声音正路是走WASAPIWindows Audio Session API的Loopback模式。Loopback模式的核心思想是拦截系统混音器发给扬声器的数据。用WASAPI打开默认渲染设备的音频端点设置成AUDCLNT_STREAMFLAGS_LOOPBACK标志就能拿到系统正在播放的音频数据。这里有个细节要注意Loopback模式下采集到的音频格式完全由系统混音器决定通常是32位浮点格式采样率可能是44100也可能是48000你得用IAudioClient::GetMixFormat查询实际格式不能写死。音视频同步是另一个容易翻车的地方。屏幕采集是独立的循环音频采集是另一个线程两边节奏不同步录出来的视频就会出现画面和声音对不上。业界常用的方案是用时间戳做对齐。每个视频帧和音频包都记录它对应的系统时间用QPC高精度计时器封装成MP4时封装器会根据时间戳自动做交错排列。代码层面要注意的是视频帧率如果设置成30那采集线程的循环不能死板地每秒只跑30次而是要尽量快然后根据时间戳决定这一帧该不该进编码器。音频那边也是一样收到音频包后先打时间戳再入队列。伪代码大概是这样// 音频采集回调 void OnAudioData(byte[] data, long timestampMs) { // 先入队列编码线程从队列取 audioQueue.Enqueue(new AudioPacket(data, timestampMs)); } // 视频采集循环 while (isRecording) { frame textureToBitmap(acquireFrame()); long now GetTimestampMs(); if (now - lastVideoTimestamp frameIntervalMs) { videoQueue.Enqueue(new VideoFrame(frame, now)); lastVideoTimestamp now; } }2.3 编码封装与参数选择拿到原始帧和音频数据只是第一步真正决定文件体积和播放兼容性的是编码器。主流的录屏软件用的都是H.264视频编码加AAC音频编码这个组合兼容性最好从手机到电脑都能播。编码方式的选型会直接影响CPU占用率。软件编码x264画质最稳但高分辨率下CPU占用很高硬件编码NVENC、Intel QuickSync、AMD AMF能大幅降低CPU压力8代以上Intel CPU和GTX 10系以上显卡都支持。我实际测试过在4K分辨率下x264的CPU占用能到60%以上而NVENC只有10%左右画质差异肉眼几乎看不出来。所以做录屏工具优先走硬件编码软编码作为兜底方案。码率的设置也有讲究。录屏幕画面不同于录现实视频屏幕内容有大面积静止区域和锐利的文字边缘码率给太低文字会糊给太高文件又大得离谱。我习惯用固定码率加一个约束条件1080p分辨率的录屏码率设置在8到12 Mbps之间比较合适如果录的是游戏动态画面多建议上到16 Mbps以上纯PPT演示或代码讲解6 Mbps就够了。封装这块推荐用MP4。MP4在浏览器、播放器里的兼容性无解但要注意写入时必须处理moov atom的位置。实时录屏时moov要写在文件末尾否则录制中断会导致整个文件损坏。如果你用FFmpeg可以直接用muxer的movflags参数设置为faststart这样封装完会把moov挪到文件头部方便网络播放。3. 实操搭建一个可用的录屏程序3.1 环境准备与最小工程结构如果你不想从零开始写采集和编码建议直接用FFmpeg库它会省掉你至少两周的开发时间。FFmpeg把采集、编码、封装全套都集成了甚至可以直接调用Windows底层的dshow和gdigrab设备。但对于Windows平台录屏我更推荐用FFmpeg的设备抽象层配合自定义采集因为gdigrab只走GDI性能不够看。这里给一个C工程的最小目录结构只包含最核心的文件ScreenRecorder/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp // 入口命令行参数解析 │ ├── recorder.h // 录制器类声明 │ ├── recorder.cpp // 录制器实现协调整个流程 │ ├── screen_capture.h // 画面采集封装 │ ├── screen_capture.cpp // DXGI采集实现 │ ├── audio_capture.h // 音频采集封装 │ ├── audio_capture.cpp // WASAPI采集实现 │ └── encoder.h // 编码器抽象接口 └── third_party/ ├── ffmpeg/ // FFmpeg库头文件和导入库 └── dxgidump/ // 相关依赖3.2 核心代码骨架与实现步骤下面用一个简化的recorder类展示整个录制流程是怎么串起来的。class Recorder { public: bool init(int width, int height, int fps, int bitrate); void start(); void stop(); private: ScreenCapture screenCap; AudioCapture audioCap; // ... };初始化阶段要做这几件事初始化DXGI设备创建Desktop Duplication对象初始化WASAPI音频采集获取混音格式初始化FFmpeg编码器配置输出格式和码率创建编码线程和采集线程启动录制的流程void Recorder::start() { // 启动音频采集线程 audioThread std::thread([this]() { audioCap.startCapture(audioQueue); }); // 启动画面采集循环 videoThread std::thread([this]() { while (recording.load()) { auto frame screenCap.acquireFrame(); if (frame ! nullptr) { encodeVideoFrame(frame); } } }); recording.store(true); }其中encodeVideoFrame就是把DXGI抓到的纹理转成AVFrame送进FFmpeg编码器然后写入输出文件。这一步的格式转换要用DXGI的CopyResource配合Shader或者D3D11纹理拷贝不能直接拿原始纹理进编码器因为编码器通常只认RGBA或NV12格式。音频编码线程类似从audioQueue取数据转成AVFrame送进AAC编码器再写入同一个输出文件。FFmpeg的AVFormatContext会同时管理视频流和音频流写入的时候会自动根据时间戳做交错不需要你手动去合并。3.3 优化调整与效果验证完成主体逻辑以后有几处优化值得投入时间。第一鼠标光标的处理。DXGI Desktop Duplication本身会隐藏硬件光标你得手动用IDXGIOutputDuplication::GetFramePointerShape获取光标形状然后在视频帧上叠加绘制。不做这一步录出来的视频看不到鼠标很多录屏工具都死在这个细节上。第二丢帧策略。当GPU负载高导致采集速度跟不上编码速度时要主动丢弃一部分旧帧而不是无限积压。如果队列里积压了几十帧录出来的视频会一直是慢动作状态很影响体验。我的做法是视频队列超过5帧就清空重来把最新的那帧送进去。第三启动和停止的时序。停止录制时一定要先停采集线程然后调用编码器的flush操作把编码器缓存里的帧全部刷出来最后再关闭文件。否则最后几秒的画面会丢失。验证效果的方法很简单拿手机或者另一块屏幕播放一段带有音频的视频然后用你的录屏程序录下来最后对比播放和原视频是否同步。重点看两处画面是否卡顿音频是否丢字。4. 常见问题排查与工程化经验4.1 高频问题排查速查表我自己开发和测试过程中记录了一些高频问题整理成表供参考现象常见原因排查方向录出来是黑屏DXGI初始化失败或Desktop Duplication被占用确认是否在桌面会话运行是否有其他录屏软件抢占帧率很低采集线程和编码线程互相阻塞检查是否忘了释放AcquireNextFrame后的资源声音和画面不同步音频帧和视频帧时间戳基准不一致统一用QPC高精度计时器音频视频都用同一个时间源录到一半程序崩溃没有处理DXGI_ERROR_ACCESS_LOST异常监听设备变化检测到访问丢失要重新初始化文件体积异常大码率设置过高或帧率失控检查编码器参数是否真正生效必要时用CRF模式压测画面正常但没有声音WASAPI Loopback模式打开失败检查音频端点是否被独占尝试用共享模式打开DXGI_ERROR_ACCESS_LOST是个特别坑的问题它通常发生在GPU驱动更新、屏幕分辨率切换、锁屏或切换到远程桌面的时候。不做处理的话程序会直接崩掉。正确处理方式是捕获这个错误重新调用DuplicateOutput创建采集对象。4.2 源码工程管理与加密保护录屏工具类的工程代码量虽然不大但涉及多线程、图形API和编码库维护起来门槛不低。我的建议是源码管理用Git加清晰的提交规范模块接口尽量用抽象类隔离这样换采集方案或编码器的时候不用动上层逻辑。关于源代码本身的安全如果你做的是商业工具需要考虑防反编译和防篡改。C编译出来的原生代码反编译难度相对高但也要注意几点不能明文硬壳密钥和服务器地址调试信息要关闭关键算法可以拆到独立模块做混淆。C#写的录屏工具就麻烦一点.NET程序集可以直接用工具反编译成近乎源码的代码如果介意这点要么用NetReactor这类混淆工具要么把核心采集和编码用C写成原生库C#只做UI层。写在最后屏幕录像程序的源代码说难不难说简单也不简单。上手的时候别贪多先把一个最小可运行的Demo跑通再逐步加功能。我最开始做的时候贪心一上来就想着搞多显示器、区域录制、摄像头画中画全一把梭结果一个月时间全耗在调试崩溃上了。后来收敛思路先把单屏加系统声音录明白后面那些功能只是在这个骨架上做加法而已。如果你也打算动手写一个录屏工具我建议从DXGI加WASAPI这条线路入手拿到一版可用的核心代码以后再回头去研究FFmpeg的封装细节。这个路径踩的坑最少出成果最快。本文还有配套的精品资源点击获取
返回列表