小富婆游戏
441MB · 2025-09-15
在上一章中,我们认识了流水线终点NVENC 硬件编码器封装 (NvEncoderD3D11)。我们了解到,这位剪辑师虽然工作效率极高,但它有一个小小的“偏好”:它最喜欢处理一种叫做 NV12 的特殊图像格式。
然而,我们流水线起点的桌面复制接口 (DDAImpl 捕获到的画面却是常见的 RGBA 格式。这就好比摄影师拍了一张色彩斑斓的数码照片(RGBA),而印刷厂的机器(编码器)只认一种特殊的印刷油墨模式(NV12)。
我们该如何解决这个“格式不兼容”的问题呢?
答案就是我们本章的主角,流水线中承上启下的关键一环:RGBToNV12
——一位专业的“色彩空间转换器”。
RGBToNV12
是什么?RGBToNV12
是我们流水线的第二步:预处理 (Pre-process)。
它的任务非常专一:接收一张 RGBA 格式的图像,并利用显卡内置的视频处理功能,将其快速地转换为 NV12 格式。它就像一位专业的色彩专家,能将一幅色彩丰富的油画(RGB)转换成适合印刷的特定颜色模式(NV12),同时保证视觉效果基本不变。
你可能会问,为什么不直接让编码器处理 RGBA 图像呢?
这背后是视频压缩的核心思想。
通过共享颜色信息,NV12 格式在人眼几乎察觉不到画质损失的情况下,大大减少了数据量。这使得它成为视频编码器的“天选之子”,可以实现更高的压缩率。
真实世界类比 | nvEncDXGI 组件 | 作用 |
---|---|---|
色彩翻译官/调色师 | RGBToNV12 | 将“摄影师”拍摄的 RGBA 原始素材,翻译成“剪辑师”最容易理解和处理的 NV12 格式。 |
这个转换过程完全在 GPU 上进行,速度飞快,确保了它不会成为我们高性能录制流水线的瓶颈。
RGBToNV12
?在我们的项目中,应用程序主控 (DemoApplication) 负责调用这位“调色师”来处理每一帧画面。这个过程发生在 Preproc()
方法中。
// 在 DemoApplication::Preproc() 内部
HRESULT Preproc()
{
// ... 从编码器那里“借”来一个空的 NV12 纹理 pEncBuf ...
// 调用色彩转换器,执行转换!
// 输入: pDupTex2D (包含 RGBA 图像)
// 输入: pEncBuf (空的 NV12 纹理)
hr = pColorConv->Convert(pDupTex2D, pEncBuf);
// ... 释放原始的 RGBA 图像 ...
return hr;
}
使用起来非常直观:
DDAImpl
捕获的、包含 RGBA 图像的纹理 (pDupTex2D
)。NvEncoderD3D11
“借”来的、空的 NV12 格式纹理 (pEncBuf
)。pColorConv->Convert()
方法,它会施展“魔法”,将 pDupTex2D
的内容转换后,填充到 pEncBuf
中。转换完成后,pEncBuf
就包含了编码器所需要的 NV12 格式图像,可以被送入流水线的下一个环节了。
RGBToNV12
是如何工作的?RGBToNV12
的高效并非源于复杂的 CPU 计算,而是巧妙地利用了 DirectX 中一个强大的功能:视频处理器 (Video Processor)。这是现代显卡普遍具备的硬件功能,专门用于视频播放中的各种图像处理,比如缩放、去隔行,以及我们这里用到的色彩空间转换。
让我们用一个时序图来看看,当 Convert
方法被调用时,RGBToNV12
是如何与 DirectX 沟通,让 GPU 来完成实际工作的。
sequenceDiagram
participant App as DemoApplication
participant Converter as RGBToNV12
participant D3D aS DirectX 视频设备
participant GPU
App->>Converter: Convert(pRGB, pYUV)
Note right of Converter: 准备工作
Converter->>D3D: 为 pRGB 创建一个“输入视图”
D3D-->>Converter: 输入视图已创建
Converter->>D3D: 为 pYUV 创建一个“输出视图”
D3D-->>Converter: 输出视图已创建
Note right of Converter: 指挥 GPU 执行
Converter->>D3D: VideoProcessorBlt(输入视图 -> 输出视图)
D3D->>GPU: 执行硬件色彩转换
GPU-->>D3D: 转换完成
D3D-->>Converter: Blt 操作成功
Converter-->>App: 转换成功
这个流程可以简化为三步:
RGBToNV12
首先为输入的 RGBA 纹理和输出的 NV12 纹理分别创建了“输入视图”和“输出视图”。你可以把“视图(View)”想象成让视频处理器能够“看到”并操作这块显存区域的特殊窗口。m_pVP
)已经根据输入输出图像的尺寸等信息配置好了。VideoProcessorBlt
函数。Blt
是“块图像传输 (Block Image Transfer)”的缩写,你可以把它理解为一条指令:“嘿,GPU!请启动视频处理器,从输入视图读取数据,转换成 NV12 格式,然后把结果写入输出视图。”现在,让我们打开 Preproc.cpp
文件,看看 Convert
函数中的关键代码是如何实现上述流程的。
第一步:检查并创建视频处理器 (如果需要)
视频处理器是与特定视频尺寸相关的。如果屏幕分辨率变了,就需要重新创建一个。
// 文件: Preproc.cpp (Convert)
// 检查输入/输出图像的尺寸是否发生变化
if (m_pVP)
{
if (m_inDesc.Width != inDesc.Width || /*...尺寸不同...*/)
{
// 如果尺寸变了,就释放旧的处理器
SAFE_RELEASE(m_pVPEnum);
SAFE_RELEASE(m_pVP);
}
}
if (!m_pVP)
{
// 如果处理器不存在,就创建一个新的
// ... 创建 D3D11_VIDEO_PROCESSOR_CONTENT_DESC ...
hr = m_pVid->CreateVideoProcessor(m_pVPEnum, 0, &m_pVP);
// ...
}
这段代码确保了我们总是有个配置正确的视频处理器可供使用。
第二步:为输入和输出纹理创建视图
GPU 不能直接操作纹理本身,它需要通过“视图”来访问。
// 文件: Preproc.cpp (Convert)
// 为输入的 RGBA 纹理创建一个“输入视图”
D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputVD = { ... };
hr = m_pVid->CreateVideoProcessorInputView(pRGB, m_pVPEnum, &inputVD, &pVPIn);
// 为输出的 NV12 纹理创建一个“输出视图”
ID3D11VideoProcessorOutputView* pVPOV = nullptr;
// (这里有一个优化:代码会检查 viewMap 中是否已为该纹理创建过视图)
D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC ovD = { ... };
hr = m_pVid->CreateVideoProcessorOutputView(pYUV, m_pVPEnum, &ovD, &pVPOV);
第三步:执行转换
一切准备就绪,现在是时候让 GPU 开始工作了。
// 文件: Preproc.cpp (Convert)
// 准备一个“流”结构体,将输入视图放进去
D3D11_VIDEO_PROCESSOR_STREAM stream = { TRUE, 0, 0, 0, 0, nullptr, pVPIn, nullptr };
// 执行 Blt 操作!这是真正发生转换的地方
hr = m_pVidCtx->VideoProcessorBlt(m_pVP, pVPOV, 0, 1, &stream);
// ... 释放本次调用创建的临时资源 ...
SAFE_RELEASE(pVPIn);
当 VideoProcessorBlt
函数返回时,转换操作就已经在 GPU 上完成了。输出纹理 pYUV
(也就是 pEncBuf
)现在已经充满了新鲜出炉的 NV12 格式的图像数据。
在本章中,我们深入了解了流水线中至关重要的“翻译官”——RGBToNV12
。
Convert()
方法,接收一个输入纹理和一个输出纹理,即可完成转换。VideoProcessorBlt
命令,将繁重的转换工作完全交给了 GPU 硬件。《艾尔登法环》海摩大槌获得方法 海摩大槌怎么获得
OPPO Find X9 Pro 手机搭载行业唯一满血版 LYT-828 传感器,推出首个可实现连续运作「瞬时三曝光」技术