【问题标题】:How to properly use a hardware accelerated Media Foundation Source Reader to decode a video?如何正确使用硬件加速的 Media Foundation Source Reader 解码视频?
【发布时间】:2021-06-23 22:53:50
【问题描述】:

我正在使用 Media Foundation 的 Source Reader 编写硬件加速的 h264 解码器,但遇到了问题。我关注了this tutorial,并通过 Windows SDK Media Foundation 示例支持自己。


关闭硬件加速后,我的应用似乎可以正常工作,但无法提供我需要的性能。当我通过将用于创建阅读器的IMFDXGIDeviceManager 传递给IMFAttributes 来打开加速时,事情变得复杂了。

如果我使用D3D_DRIVER_TYPE_NULL 驱动程序创建ID3D11Device,该应用程序运行良好,并且帧的处理速度比在软件模式下更快,但从 CPU 和 GPU 使用情况来看,它仍然在 CPU 上完成大部分处理.

另一方面,当我使用D3D_DRIVER_TYPE_HARDWARE 驱动程序创建ID3D11Device 并运行应用程序时,可能会发生以下四种情况之一。

  1. IMFMediaBuffer::Lock 函数返回 0x887a0005 之前,我只得到不可预测的帧数(通常为 1-3),这被描述为“GPU 设备实例已暂停。使用 GetDeviceRemovedReason 确定适当的操作” .当我打电话给ID3D11Device::GetDeviceRemovedReason 时,我得到了 0x887a0020,它被描述为“驱动程序遇到问题并被置于设备移除状态”,这并没有我希望的那么有用。

  2. 应用程序在调用IMFMediaBuffer::Lock 时在外部 dll 中崩溃。似乎 dll 取决于所使用的 GPU。对于 Intel 集成 GPU,它是 igd10iumd32.dll,对于 Nvidia 移动 GPU,它是 mfplat.dll。此特定崩溃的消息如下:“decoder_tester.exe 中的 0x53C6DB8C (mfplat.dll) 引发异常:0xC0000005:访问冲突读取位置 0x00000024”。执行之间的地址不同,有时涉及读取,有时涉及写入。

  3. 图形驱动程序停止响应,系统挂起一小段时间,然后应用程序像第 2 点一样崩溃或像第 1 点一样结束。

  4. 应用运行良好,并通过硬件加速处理所有帧。

大多数时候是 1 或 2,很少是 3 或 4。


以下是在我的机器上以不同模式(Intel Core i5-6500 with HD Graphics 530、Windows 10 Pro)进行处理而不进行节流时的 CPU/GPU 使用情况。

  • NULL - CPU:~90%,GPU:~15%
  • 硬件 - CPU:~15%,GPU:~60%
  • 软件 - CPU:~40%,GPU:~7%

我在三台机器上测试了该应用程序。它们都具有英特尔集成 GPU(HD 4400、HD 4600、HD 530)。其中之一还具有可切换的 Nvidia 专用 GPU (GF 840M)。它对所有这些都一样,唯一的区别是在使用 Nvidia 的 GPU 时它会在不同的 dll 中崩溃。


我以前没有使用 COM 或 DirectX 的经验,但所有这些都是不一致且不可预测的,因此对我来说这似乎是内存损坏。不过,我不知道我在哪里犯了错误。你能帮我找出我做错了什么吗?

我能想到的最小代码示例如下。我正在使用 Visual Studio Professional 2015 将其编译为 C++ 项目。我准备了定义以启用硬件加速并选择硬件驱动程序。将它们注释掉以改变行为。此外,代码期望 this video file 出现在项目目录中。

#include <iostream>
#include <string>
#include <atlbase.h>
#include <d3d11.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <windows.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "mf.lib")
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")

#define ENABLE_HW_ACCELERATION
#define ENABLE_HW_DRIVER

void handle_result(HRESULT hr)
{
    if (SUCCEEDED(hr))
        return;

    WCHAR message[512];

    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, hr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), message, ARRAYSIZE(message), nullptr);

    printf("%ls", message);
    abort();
}

int main(int argc, char** argv)
{
    handle_result(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
    handle_result(MFStartup(MF_VERSION));

    {
        CComPtr<IMFAttributes> attributes;

        handle_result(MFCreateAttributes(&attributes, 3));

#if defined(ENABLE_HW_ACCELERATION)
        CComPtr<ID3D11Device> device;
        D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0 };

#if defined(ENABLE_HW_DRIVER)
        handle_result(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
            levels, ARRAYSIZE(levels), D3D11_SDK_VERSION, &device, nullptr, nullptr));
#else
        handle_result(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_NULL, nullptr, D3D11_CREATE_DEVICE_SINGLETHREADED,
            levels, ARRAYSIZE(levels), D3D11_SDK_VERSION, &device, nullptr, nullptr));
#endif

        UINT token;
        CComPtr<IMFDXGIDeviceManager> manager;

        handle_result(MFCreateDXGIDeviceManager(&token, &manager));
        handle_result(manager->ResetDevice(device, token));

        handle_result(attributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, manager));
        handle_result(attributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE));
        handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE));
#else
        handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE));
#endif

        CComPtr<IMFSourceReader> reader;

        handle_result(MFCreateSourceReaderFromURL(L"Rogue One - A Star Wars Story - Trailer.mp4", attributes, &reader));

        CComPtr<IMFMediaType> output_type;

        handle_result(MFCreateMediaType(&output_type));
        handle_result(output_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video));
        handle_result(output_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32));
        handle_result(reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, output_type));

        unsigned int frame_count{};

        std::cout << "Started processing frames" << std::endl;

        while (true)
        {
            CComPtr<IMFSample> sample;
            DWORD flags;

            handle_result(reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,
                0, nullptr, &flags, nullptr, &sample));

            if (flags & MF_SOURCE_READERF_ENDOFSTREAM || sample == nullptr)
                break;

            std::cout << "Frame " << frame_count++ << std::endl;

            CComPtr<IMFMediaBuffer> buffer;
            BYTE* data;

            handle_result(sample->ConvertToContiguousBuffer(&buffer));
            handle_result(buffer->Lock(&data, nullptr, nullptr));

            // Use the frame here.

            buffer->Unlock();
        }

        std::cout << "Finished processing frames" << std::endl;
    }

    MFShutdown();
    CoUninitialize();

    return 0;
}

【问题讨论】:

  • 你可以试试 MF_MT_SUBTYPE, MFVideoFormat_NV12。
  • 感谢您的提示!事实上,当我将 MFVideoFormat_NV12 设置为输出子类型时,它就开始工作了。我使用 DXVAChecker 为我使用的解码器列出了possible output formats(或者我认为),并且那里没有 RGB32。这是否意味着我无法使用此解码器将 H264 直接解码为 RGB32?那为什么它有时会像我的问题第 4 点中描述的那样正常工作?或者为什么没有“不支持输出格式”的错误消息?奇怪的是,NV12 似乎是列表中唯一能使代码正常工作的格式。
  • 请查看我的详细回答

标签: c++ com directx hardware-acceleration ms-media-foundation


【解决方案1】:

从概念上讲,您的代码是正确的,唯一的注释 - 而且不是很明显 - Media Foundation 解码器是多线程的。您正在为它提供单线程版本的 Direct3D 设备。你必须解决它,否则你会得到你目前得到的结果:访问冲突和冻结,这是未定义的行为。

    // NOTE: No single threading
    handle_result(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 
        (0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
        levels, ARRAYSIZE(levels), D3D11_SDK_VERSION, &device, nullptr, nullptr));

    // NOTE: Getting ready for multi-threaded operation
    const CComQIPtr<ID3D11Multithread> pMultithread = device;
    pMultithread->SetMultithreadProtected(TRUE);

另请注意,这个简单的代码示例在您为获取连续缓冲区而添加的行周围存在性能瓶颈。显然,访问数据是您的举动……但是设计行为是解码的数据已经在视频内存中,并且您将其传输到系统内存是一项昂贵的操作。也就是说,您在循环中添加了严重的性能损失。您将对通过这种方式检查数据的有效性感兴趣,而在进行性能基准测试时,您应该将其注释掉。

【讨论】:

  • 我用我的和你的建议测试了代码。我的建议(NV12)无需创建和设置多线程设备即可工作。当设置 RGB32 并通过 MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING 启用视频处理器 MFT 时,您的建议有效。恕我直言,这意味着它是多线程的视频处理器 MFT,而不是 H264 解码器(顺便说一句,我使用的是 NVidia 硬件解码器)。如果我错了,请纠正我。无论如何 +1。
  • @VuVirt:它们都是多线程的,因为它们在内部积极使用工作队列。当使用 Direct3D 设备时发生有效冲突时,就会出现问题。由于不需要解码后转换,因此与 NV12 发生冲突的可能性要小得多。
  • 谢谢!有没有办法事先知道解码器或处理器是否是多线程的?我不记得在文档、教程或示例中提到过它。现在看了MSDN上的解码器描述,有CODECAPI_AVDecNumWorkerThreads参数,应该是个提示,但是在哪里都说清楚了?
  • 任何时候只要把它当作多线程。 Media Foundation 本身是多线程的。您发现的值控制(可能控制)特定编解码器如何进行自己的内部并行处理。
  • 我知道这是旧的,但我无法让这行代码工作:“const CComQIPtr pMultithread = device;”。 Visual Studio 抱怨“没有合适的用户定义的转换”。
【解决方案2】:

H264 视频解码器的输出类型可以在这里找到:https://msdn.microsoft.com/en-us/library/windows/desktop/dd797815(v=vs.85).aspx。 RGB32 不是其中之一。在这种情况下,您的应用程序依赖视频处理器 MFT 来执行从任何 MFVideoFormat_I420、MFVideoFormat_IYUV、MFVideoFormat_NV12、MFVideoFormat_YUY2、MFVideoFormat_YV12 到 RGB32 的转换。我想是视频处理器 MFT 行为异常并导致您的程序行为不端。这就是为什么通过将 NV12 设置为解码器的输出子类型,您将摆脱 Video Processor MFT 并且以下代码行也变得无用:

handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE));

handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE));

此外,您注意到 NV12 是唯一可以正常工作的格式。我认为这样做的原因是它是 D3D 和 DXGI 设备管理器在加速场景中使用的唯一一个。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-22
    • 2012-07-04
    • 2019-03-16
    • 1970-01-01
    • 2017-09-16
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    相关资源
    最近更新 更多