在媒体基础中支持 Direct3D 11 视频解码

本主题介绍如何在 Microsoft 媒体基础解码器中支持 Microsoft Direct3D 11。 具体来说,它描述了解码器和视频呈现器之间的通信。 本主题不描述如何实现解码操作。

概述

在本主题的剩余部分中,使用了以下术语:

  • 软件解码器。 软件解码器是媒体基础转换 (MFT),它接收压缩的视频样本并输出解压缩的视频帧。
  • 解码器设备。 解码器设备是图形引擎,并且由图形驱动程序来实现。 解码器设备执行加速解码操作。
  • 管道。 管道托管软件解码器,并向软件解码器传递缓冲区以及从软件解码器传递缓冲器。 根据应用的不同,管道可能是媒体会话源读取器或直接调用 MFT 的应用代码。

要使用 Direct3D 11 执行解码,软件解码器必须具有指向 Direct3D 11 设备的指针。 Direct3D 11 设备是在软件解码器的外部创建的。 在媒体会话方案中,视频呈现器创建 Direct3D 11 设备。 在源读取器方案中,通常应用程序会创建 Direct3D 11 设备。

DXGI 设备管理器用于在组件之间共享 Direct3D 11。 DXGI 设备管理器公开 IMFDXGIDeviceManager 接口。 管道通过发送 MFT_MESSAGE_SET_D3D_MANAGER 消息在软件解码器上设置 IMFDXGIDeviceManager 指针。

下图显示了软件解码器、Direct3D 11 和管道之间的关系。

a diagram that shows the software decoder and the dxgi device manager.

以下是软件解码器必须执行的基本步骤,以支持媒体基础中的 Direct3D 11:

  1. 打开 Direct3D 11设 备的图柄。
  2. 查找解码器配置。
  3. 分配解压缩的缓冲区。
  4. 解码帧。

这些步骤将在本主题的剩余部分中进行更详细的描述。

打开设备图柄

解码器 MFT 使用 DXGI 设备管理器来获得 Direct3D 11 设备的图柄。 要打开设备图柄,请执行以下步骤:

  1. 解码器 MFT 必须公开值为 TRUEMF_SA_D3D11_AWARE 属性。
  2. 拓扑加载器通过调用 IMFTransform::GetAttributes 来查询此属性。 TRUE 值表示 MFT 支持 Direct3D 11。
  3. 拓扑加载器使用MFT_MESSAGE_SET_D3D_MANAGER 消息调用 IMFTransform::ProcessMessageulParam 参数是指向 DXGI 设备管理器的 IUnknown 指针。 查询此指针以获取 IMFDXGIDeviceManager 接口。
  4. 调用 IMFDXGIDeviceManager::OpenDeviceHandle 以获取 Direct3D 11 设备的图柄。
  5. 要获取指向 Direct3D 11 设备的指针,请调用 IMFDXGIDeviceManager::GetVideoService。 传入设备图柄和值 IID_ID3D11Device。 方法返回指向 ID3D11Device 接口的指针。
  6. 要获取指向图形引擎的指针,请再次调用 IMFDXGIDeviceManager::GetVideoService。 这一次,传入设备图柄和值 IID_ID3D11VideoDevice。 方法返回指向 ID3D11VideoDevice 接口的指针。
  7. 调用 ID3D11Device::GetImmediateContext 以获取 ID3D11DeviceContext 指针。
  8. ID3D11DeviceContext 上调用 QueryInterface 以获取 ID3D11VideoContext 指针。
  9. 建议对设备上下文使用多线程保护,以防止在调用 ID3D11VideoContext::GetDecoderBufferID3D11VideoContext::ReleaseDecoderBuffer 时有时会发生死锁问题。 要设置多线程保护,请首先调用 ID3D11Device 上的 QueryInterface 以获取 ID3D10Multithread 指针。 然后调用 ID3D10Multithread::SetMultithreadProtected,为 bMTProtect 传递 true

查找解码器配置

要执行解码,软件解码器必须找到解码器设备支持的兼容配置,包括呈现器目标格式。 此步骤发生在 IMFTransform::SetInputType 方法内部,如下所示。

  1. 验证输入的媒体类型。 如果类型遭拒绝,请跳过其余步骤并返回错误代码。
  2. 调用 ID3D11VideoDevice::GetVideoDecoderProfileCount 以获取支持的配置文件数。
  3. 调用 ID3D11VideoDevice::GetVideoDecoderProfile 以枚举配置文件并获取配置文件 GUID。
  4. 查找与视频格式和软件解码器功能匹配的配置文件 GUID。 例如,MPEG-2 解码器将查找 D3D11_DECODER_PROFILE_MPEG2_MOCOMPD3D11_DECODER_PROFILE_MPEG2_IDCTD3D11_DECODER_PROFILE_MPEG2_VLD
  5. 如果找到合适的解码器 GUID,请通过调用 ID3D11VideoDevice::CheckVideoDecoderFormat 方法检查输出格式。 传入解码器 GUID 和指定呈现器目标格式的 DXGI_FORMAT 值。
  6. 接下来,为解码器找到合适的配置。
    1. 调用 ID3D11VideoDevice::GetVideoDecoderConfigCount 以获取解码器配置的数量。 传入相同的解码器设备 GUID,以及描述所建议的呈现器目标格式的 D3D11_VIDEO_DECODER_DESC 结构。
    2. 调用 ID3D11VideoDevice::GetVideoDecoderConfig 以枚举解码器配置。

IMFTransform::GetOutputAvailableType 方法中,返回基于所建议呈现器目标格式的解压缩视频格式。

IMFTransform::SetOutputType 方法中,根据呈现器目标格式检查媒体类型。

回退到软件解码

MFT 可能找不到配置。 例如,图形驱动程序可能不支持正确的功能。 在这种情况下,MFT 必须回退到软件解码,如下所示。

  1. SetInputTypeSetOutputType 方法都应该返回 MF_E_UNSUPPORTED_D3D_TYPE
  2. 作为响应,拓扑加载器将发送 ulParam 参数值为 NULLMFT_MESSAGE_SET_D3D_MANAGER 消息。
  3. MFT 释放指向 IMFDXGIDeviceManager 接口的指针。
  4. 拓扑加载器重新协商媒体类型。

在这一点上,MFT 可以使用软件解码。

分配解压缩的缓冲区

解码器负责分配 Direct3D 11 纹理以用作解压缩视频缓冲区。 输出流属性中的 MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT 属性(请参阅 IMFTransform::GetOutputStreamAttributes)用于确定解码器应为视频呈现器分配多少图面以用于隔行扫描。 解码器应该将此值用于一些合理的上限和下限,例如 3-32。 有关渐进式内容,请参阅 MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT_PROGRESSIVE

IMFTransform::GetOutputStreamInfo 方法中,在 MFT_OUTPUT_STREAM_INFO 结构中设置 MFT_OUTPUT_STREAM_PROVIDES_SAMPLES 标志。 此标志通知媒体会话 MFT 分配其自己的输出样本。 为了分配输出样本,MFT 执行以下步骤:

  1. 通过调用 ID3D11Device::CreateTexture2D 创建 2D 纹理数组。 在 D3D11_TEXTURE2D_DESC 结构中,将 ArraySize 设置为解码器所需的图面数。 这包括:

    • 引用帧的图面。
    • 用于隔行扫描的曲面(三个图面)。
    • 解码器需要缓冲的图面。

    绑定标志 (BindFlags) 应包括 D3D11_BIND_DECODER 标志和通过输出流属性中 MF_SA_D3D11_BINDFLAGS 属性设置的任何绑定标志。

  2. 对于纹理数组中的每个图面,调用 ID3D11VideoDevice::CreateVideoDecoderOutputView 以创建视频解码器输出视图。 在解码过程中,这些输出视图将传递给 ID3D11VideoContext::DecoderBeginFrame 方法。

  3. 为纹理数组中的每个图面创建媒体示例,如下所示:

    1. 通过调用 MFCreateDXGISurfaceBuffer 函数创建 DXGI 媒体缓冲区。 传入 ID3D11Texture2D 指针和纹理数组中每个元素的偏移。 函数返回 IMFMediaBuffer 指针。
    2. 通过调用 MFCreateVideoSampleFromSurface 函数创建空的媒体样本。 将 pUnkSurface 参数设置为 NULL。 函数返回 IMFSample 指针。
    3. 调用 IMFSample::AddBuffer 将媒体缓冲区添加到示例中。

应该同时销毁所创建的所有纹理,而非只销毁一些并继续使用提醒。

解码

要创建解码器设备,请调用 ID3D11VideoDevice::CreateVideoDecoder。 方法返回指向 ID3D11VideoDecoder 接口的指针。 解码应在 IMFTransform::ProcessOutput 方法内进行。 在每帧上,调用 IMFDXGIDeviceManager::TestDevice 以测试 DXGI 的可用性。 如果设备已更改,软件解码器必须重新创建解码器设备,如下所示:

  1. 通过调用 IMFDXGIDeviceManager::CloseDeviceHandle 关闭设备图柄。
  2. 释放与先前 Direct3D 11 设备相关的所有资源,包括 ID3D11VideoDecoderID3D11VideoContextID3D11Texture2DID3D11VideoDecoderOutputView 接口。
  3. 打开新设备图柄。
  4. 协商新解码器配置,如前面“查找解码器配置”中所述。 此步骤是必要的,因为设备功能可能已更改。
  5. 创建新的解码器设备。

假设设备图柄有效,则解码过程的工作方式如下:

  1. 获取当前未在使用中的可用图面。 所有图面最初都可用。
  2. 在媒体示例中查询 IMFTrackedSample 接口。
  3. 调用 IMFTrackedSample::SetAllocator 并提供指向 IMFAsyncCallback 接口的指针。 (软件解码器必须实现此接口。)当视频呈现器释放示例时,将调用回调。 使用此回调可以跟踪哪些示例当前可用,哪些正在使用中。
  4. 调用 ID3D11VideoContext::DecoderBeginFrame。 将指针传入解码器设备的 ID3D11VideoDecoder 接口和输出视图的 ID3D11VideoDecoderOutputView 接口。
  5. 一次或多次执行以下操作:
    1. 调用 ID3D11VideoContext::GetDecoderBuffer 以获取缓冲区。
    2. 填充缓冲区。
    3. 调用 ID3D11VideoContext::ReleaseDecoderBuffer
    4. 调用 ID3D11VideoContext::SubmitDecoderBuffer。 此方法指示解码器设备对帧执行解码操作。
  6. 调用 ID3D11VideoContext::DecoderEndFrame 以发出当前帧解码结束的信号。

Direct3D 11 使用与 DXVA 2.0 相同的数据结构进行解码操作。 对于原始的 DXVA 配置文件集(用于 H.261、H.263 和 MPEG-2),这些数据结构在 DXVA 1.0 规范 中进行了描述。

在每对 DecoderBeginFrameSubmitDecoderBuffer 调用中,可以多次调用 GetDecoderBuffer,但对于每种类型的缓冲区只能调用一次。 如果两次使用相同的缓冲区类型而不调用 SubmitDecoderBuffer,则会覆盖缓冲区中的数据。

Direct3D 11 视频 API

DirectX 视频加速 2.0