如何正确使用硬件加速的Media Foundation Source Reader来解码视频?
我正在使用Media Foundation的Source Reader编写硬件加速的h264解码器,但遇到了问题。我跟着this tutorial,并支持自己的Windows SDK媒体基础示例。如何正确使用硬件加速的Media Foundation Source Reader来解码视频?
当硬件加速关闭时,我的应用程序似乎工作正常,但它不提供我需要的性能。当我通过传递一个IMFDXGIDeviceManager
到IMFAttributes
用于创建阅读器来打开加速时,事情变得复杂。
如果我使用D3D_DRIVER_TYPE_NULL
驱动程序创建ID3D11Device
,应用程序工作正常,帧被处理速度更快,在软件的模式,而是由CPU和GPU使用率判断它仍然没有大部分处理上的CPU。
另一方面,当我使用D3D_DRIVER_TYPE_HARDWARE
驱动程序创建ID3D11Device
驱动程序并运行该应用程序时,可能会发生以下四种情况之一。
我仅前
IMFMediaBuffer::Lock
函数返回其被描述为0x887a0005获得帧的不可预测的数(通常为1-3)“的GPU设备实例已经暂停。使用GetDeviceRemovedReason
以确定适当的行动”。当我打电话给ID3D11Device::GetDeviceRemovedReason
时,我得到0x887a0020,它被描述为“驱动程序遇到问题并且被置于设备移除状态”,这没有我希望的那样有帮助。该应用程序崩溃在
IMFMediaBuffer::Lock
调用外部DLL。看来,DLL取决于使用的GPU。对于Intel集成GPU,它是igd10iumd32.dll,对于Nvidia移动GPU,它是mfplat.dll。此特定崩溃的消息如下所示:“decode_ tester.exe中0x53C6DB8C(mfplat.dll)引发的异常:0xC0000005:访问冲突读取位置0x00000024”。地址在执行过程中有所不同,有时涉及阅读,有时写作。图形驱动器停止响应,则系统挂起的时间很短,然后应用程序崩溃等在点2或涂饰等在点1
该应用工作正常,并处理所有与硬件的帧加速。
大多数是1或2,很少3或4
这里的时间是在CPU/GPU的使用是什么样的,当没有我的机器在不同的模式节流(英特尔酷睿处理带有HD Graphics 530,Windows 10 Pro的i5-6500)。
- NULL - CPU:〜90%,GPU:〜15%
- HARDWARE - CPU:〜15%,GPU:〜60%
- SOFTWARE - 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;
}
你的代码是正确的,在概念,唯一的一句话 - 这不是很明显 - 即媒体基金会解码器是多线程的。您正在使用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);
还要注意,这个简单的代码示例在为获取连续缓冲区而添加的行周围存在性能瓶颈。显然,这是您访问数据的举措......但是,设计行为是解码数据已经存储在视频内存中,而您传输到系统内存是一项昂贵的操作。也就是说,您在循环中添加了严重的性能命中。您将有兴趣以这种方式检查数据的有效性,并且当涉及到性能基准测试时,您应该对此进行评论。
我使用我的建议和您的建议测试了代码。我的拥塞(NV12)不需要创建和设置多线程设备。当设置了RGB32并且通过MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING启用了视频处理器MFT时,您的建议将起作用。 IMHO意味着它是多线程的视频处理器MFT,而不是H264解码器(我使用的是NVidia硬件解码器)。如果我错了,请纠正我。无论如何。 – VuVirt
@VuVirt:它们都是多线程的,因为他们积极利用工作队列内部。如果在使用Direct3D设备时发生有效碰撞,问题就会出现。由于不需要进行解码后转换,与NV12发生冲突的可能性要小得多。 –
谢谢!预先知道解码器或处理器是多线程的吗?我不记得它在文档,教程或示例中提到过。现在,我看MSDN上的解码器描述,还有的'CODECAPI_AVDecNumWorkerThreads'参数,这应该是一个暗示,但显然是在任何地方规定? – Vennor
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_ADVANCED_VIDEO_PROCESSING, TRUE));
和
handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE));
而且,因为你注意到NV12是唯一正常工作的格式。我认为这是D3D和DXGI设备管理器在加速场景中唯一使用它的原因。
您可以尝试使用MF_MT_SUBTYPE,MFVideoFormat_NV12。 – VuVirt
感谢您的提示!事实上,当我将MFVideoFormat_NV12设置为输出子类型时,它开始工作。我使用DXVAChecker为我使用的解码器(或者我认为)列出了[可能的输出格式](https://i.imgsafe.org/12d5643c00.png),并且没有RGB32。这是否意味着我无法使用此解码器将H264直接解码为RGB32?那么为什么它有时会像我的问题的第4点所描述的那样正常工作?或者为什么没有“不支持输出格式”的错误信息?奇怪的是,NV12似乎是使代码正常工作的唯一格式。 – Vennor
请检查我的具体答案 – VuVirt