如何正确使用硬件加速的Media Foundation Source Reader来解码视频?

时间:2016-12-01 14:29:41

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

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

我的应用程序似乎在硬件加速关闭时工作正常,但它并没有提供我需要的性能。当我通过将IMFDXGIDeviceManager传递给用于创建阅读器的IMFAttributes来打开加速度时,事情变得复杂。

如果我使用ID3D11Device驱动程序创建D3D_DRIVER_TYPE_NULL,该应用程序可以正常工作,并且在软件模式下处理帧的速度会更快,但根据CPU和GPU的使用情况来判断,它仍会占用大部分处理CPU。

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

  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. 该应用程序运行正常,并通过硬件加速处理所有帧。

  5. 大部分时间它是1或2,很少3或4。

    这是我机器上不同模式下处理时不受限制的CPU / GPU使用情况(英特尔酷睿i5-6500与HD Graphics 530,Windows 10 Pro)。

    • 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;
    }
    

2 个答案:

答案 0 :(得分:2)

您的代码在概念上是正确的,唯一的注意事项 - 并且不是很明显 - 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<ID3D10Multithread> pMultithread = device;
    pMultithread->SetMultithreadProtected(TRUE);

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

答案 1 :(得分:1)

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设置为解码器的输出子类型,您将摆脱视频处理器MFT,以下代码行也变得无用:

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

/**
 * Optimized algorithm from http://www.codexworld.com
 *
 * @param float $latitudeFrom
 * @param float $longitudeFrom
 * @param float $latitudeTo
 * @param float $longitudeTo
 *
 * @return float [km]
 */
function codexworldGetDistanceOpt($latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo)
{
    $rad = M_PI / 180;
    //Calculate distance from latitude and longitude
    $theta = $longitudeFrom - $longitudeTo;
    $dist = sin($latitudeFrom * $rad) 
        * sin($latitudeTo * $rad) +  cos($latitudeFrom * $rad)
        * cos($latitudeTo * $rad) * cos($theta * $rad);

    return acos($dist) / $rad * 60 *  1.853;
}

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