在我们深入研究代码的细节之前,最重要的事情是理解整个应用程序的核心工作流程。
想象一下,你想实现一个屏幕录制软件。你面临的第一个问题是:"我如何将屏幕上看到的动态画面,变成一个可以播放的 .mp4 或 .h264 视频文件呢?"
这个过程可以分为三个主要步骤,就像一个工厂里的流水线:
- 捕获 (Capture):首先,你需要一种方法来"抓住"屏幕上显示的图像。
- 预处理 (Pre-process):抓到的原始图像数据可能不是最适合视频压缩的格式。你需要对其进行一些处理,比如转换颜色格式,使其"原料"更优质。
- 编码 (Encode):最后,你需要将处理好的图像一帧一帧地压缩,并按照特定的视频格式(如 H.264)打包,最终生成视频文件。
这三个步骤------"捕获-预处理-编码"------构成了我们这个项目的核心 流水线。它不是一个具体的 C++ 类,而是贯穿整个项目的核心思想。数据就像在传送带上一样,从一个环节流向下个环节,直到最终产品(视频文件)被生产出来。
我们可以用一个更生动的比喻来理解它:瓶装饮料生产线。
生产线步骤 | nvEncDXGI 流水线 |
作用 |
---|---|---|
抓取空瓶子 | 捕获 | 使用 桌面复制接口 (DDAImpl) 从屏幕上获取原始图像帧。 |
灌装饮料 | 预处理 | 使用 色彩空间转换器 (RGBToNV12)将图像从 RGBA 格式转换为 NV12 格式,这是编码器最高效的格式。 |
封盖贴标 | 编码 | 使用 NVENC 硬件编码器封装 (NvEncoderD3D11) 将 NV12 图像帧压缩成 H.264 视频流。 |
理解这条流水线,你就掌握了理解 nvEncDXGI
项目运作方式的钥匙。
流水线概览
让我们用一张图来清晰地展示数据是如何在这条流水线中流动的:
代码中的流水线
理论讲完了,现在让我们看看这条流水线在实际代码中是如何体现的。打开 main.cpp
文件,找到 Grab60FPS
函数。这个函数内部有一个 do-while
循环,这个循环的每一次迭代,都完整地执行了一遍我们的流水线操作。
这个循环的核心逻辑可以简化为以下三个步骤:
1. 捕获一帧
cpp
// 从 DDA 获取一帧画面
hr = Demo.Capture(wait);
这行代码启动了流水线的第一步。Demo.Capture()
会调用内部的 DDAImpl
对象,从你的显示器上捕获当前时刻的图像。如果成功,图像数据会存放在一个临时的显存区域。
2. 预处理该帧
cpp
// 为编码做预处理
hr = Demo.Preproc();
紧接着,流水线进入第二步。Demo.Preproc()
会调用 RGBToNV12
对象,将上一步捕获到的 RGBA 格式图像转换为 NV12 格式。这一步是性能优化的关键,因为 NV12 格式更受硬件视频编码器的青睐。
3. 编码该帧
cpp
// 编码处理后的帧
hr = Demo.Encode();
最后,流水线进入收尾阶段。Demo.Encode()
会将预处理好的 NV12 图像帧交给 NvEncoderD3D11
对象。后者利用 NVIDIA GPU 的硬件编码单元(NVENC)将其高效地压缩成 H.264 视频数据,并写入到最终的文件中。
就是这样!Capture
-> Preproc
-> Encode
。这个简单的三步曲在循环中不断重复,直到录制到指定数量的帧,最终就构成了一个完整的视频。
内部工作流程
为了更深入地理解,我们来看一下当录制一帧画面时,各个组件之间是如何相互协作的。
一张 RGBA 格式的图像 DDA-->>App: 返回 RGBA 图像 App->>Preproc: Preproc() Note right of Preproc: 在 GPU 上执行
颜色空间转换 Preproc-->>App: 返回 NV12 格式的图像 App->>Encoder: Encode() Note right of Encoder: 调用 NVIDIA 硬件编码器
压缩 NV12 图像 Encoder-->>App: 返回编码后的 H.264 数据包 App->>App: 将数据包写入文件
这个图表清晰地展示了 DemoApplication
类(我们将在下一章详细介绍)作为总指挥,依次调用 DDAImpl
、RGBToNV12
和 NvEncoderD3D11
这三个专业组件,共同完成了一帧画面的录制任务。
总结
在本章中,我们学习了 nvEncDXGI
项目最核心的概念:捕获-预处理-编码流水线。
- 我们知道了屏幕录制可以分解为捕获 、预处理 和编码三个阶段。
- 我们了解了每个阶段分别由哪个核心组件负责:
DDAImpl
、RGBToNV12
和NvEncoderD3D11
。 - 我们通过分析
main.cpp
中的核心循环,看到了这个流水线在代码中的实际体现。