在上一章中,我们认识了流水线终点NVENC 硬件编码器封装 (NvEncoderD3D11)。我们了解到,这位剪辑师虽然工作效率极高,但它有一个小小的“偏好”:它最喜欢处理一种叫做 NV12 的特殊图像格式。

然而,我们流水线起点的桌面复制接口 (DDAImpl 捕获到的画面却是常见的 RGBA 格式。这就好比摄影师拍了一张色彩斑斓的数码照片(RGBA),而印刷厂的机器(编码器)只认一种特殊的印刷油墨模式(NV12)。

我们该如何解决这个“格式不兼容”的问题呢?

答案就是我们本章的主角,流水线中承上启下的关键一环:RGBToNV12——一位专业的“色彩空间转换器”。

RGBToNV12 是什么?

RGBToNV12 是我们流水线的第二步:预处理 (Pre-process)

它的任务非常专一:接收一张 RGBA 格式的图像,并利用显卡内置的视频处理功能,将其快速地转换为 NV12 格式。它就像一位专业的色彩专家,能将一幅色彩丰富的油画(RGB)转换成适合印刷的特定颜色模式(NV12),同时保证视觉效果基本不变。

为什么需要这种转换?

你可能会问,为什么不直接让编码器处理 RGBA 图像呢?

这背后是视频压缩的核心思想。

  • RGBA:存储了每个像素完整的红(R)、绿(G)、蓝(B)和透明度(A)信息。这种格式信息完整,但数据量巨大。
  • NV12:是一种 YUV 颜色格式的变体。它将图像信息分为两部分:
    1. 亮度 (Luma, Y):代表图像的明暗信息,每个像素都有一个独立的亮度值。
    2. 色度 (Chroma, UV):代表图像的颜色信息。它利用了人眼对亮度比对颜色更敏感的特点,让相邻的几个像素共享一套颜色信息。

通过共享颜色信息,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;
}

使用起来非常直观:

  1. 我们准备好输入:一张从 DDAImpl 捕获的、包含 RGBA 图像的纹理 (pDupTex2D)。
  2. 我们准备好输出目标:一张从 NvEncoderD3D11 “借”来的、空的 NV12 格式纹理 (pEncBuf)。
  3. 调用 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: 转换成功

这个流程可以简化为三步:

  1. 创建“窗口”RGBToNV12 首先为输入的 RGBA 纹理和输出的 NV12 纹理分别创建了“输入视图”和“输出视图”。你可以把“视图(View)”想象成让视频处理器能够“看到”并操作这块显存区域的特殊窗口。
  2. 配置处理器:它会确保内部的“视频处理器”(m_pVP)已经根据输入输出图像的尺寸等信息配置好了。
  3. 下达指令:最后,它调用 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

  • 我们知道了它的核心任务是将捕获到的 RGBA 图像转换为编码器偏爱的 NV12 格式,这是为了实现更高效的视频压缩。
  • 它的角色是流水线中的“色彩专家”,利用 GPU 硬件加速能力完成这一转换,保证了整个流程的高性能。
  • 我们学习了它的核心用法:通过 Convert() 方法,接收一个输入纹理和一个输出纹理,即可完成转换。
  • 我们还探究了其内部原理,了解到它通过 DirectX 的视频处理器 (Video Processor)VideoProcessorBlt 命令,将繁重的转换工作完全交给了 GPU 硬件。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]