Как правильно использовать аппаратное ускорение Media Foundation Source Reader для декодирования видео?

Я нахожусь в процессе написания аппаратно-ускоренного декодера h264 с использованием Media Reader, но столкнулся с проблемой. Я последовал за этот урок и поддержал себя с помощью примеров Windows SDK Media Foundation.


Мое приложение работает нормально, когда аппаратное ускорение отключено, но оно не обеспечивает необходимую мне производительность. Когда я включаю ускорение, передавая IMFDXGIDeviceManager в IMFAttributes используется для создания читателя, все становится сложнее.

Если я создам ID3D11Device используя D3D_DRIVER_TYPE_NULL Драйвер, приложение работает нормально, и кадры обрабатываются быстрее, чем в программном режиме, но, судя по использованию CPU и GPU, он по-прежнему выполняет большую часть обработки на CPU.

С другой стороны, когда я создаю ID3D11Device используя D3D_DRIVER_TYPE_HARDWARE Драйвер и запустить приложение, одна из этих четырех вещей может произойти.

  1. Я получаю только непредсказуемое количество кадров (обычно 1-3) IMFMediaBuffer::Lock Функция возвращает 0x887a0005, который описывается как «Экземпляр устройства GPU приостановлен. Использование GetDeviceRemovedReason определить соответствующее действие «. Когда я звоню ID3D11Device::GetDeviceRemovedReason, Я получаю 0x887a0020, который описывается как «Драйвер столкнулся с проблемой и был переведен в состояние удаления устройства», что не так полезно, как хотелось бы.

  2. Приложение вылетает во внешней DLL на IMFMediaBuffer::Lock вызов. Кажется, что DLL зависит от используемого графического процессора. Для интегрированного графического процессора Intel это igd10iumd32.dll, а для мобильного графического процессора Nvidia это mfplat.dll. Сообщение об этом конкретном сбое выглядит следующим образом: «Исключение, выданное в 0x53C6DB8C (mfplat.dll) в decoder_ tester.exe: 0xC0000005: Место чтения нарушения доступа 0x00000024». Адреса отличаются между исполнениями и иногда это включает чтение, иногда запись.

  3. Графический драйвер перестает отвечать на запросы, система зависает на короткое время, а затем происходит сбой приложения, как в пункте 2, или завершается, как в пункте 1.

  4. Приложение работает нормально и обрабатывает все кадры с аппаратным ускорением.

В большинстве случаев это 1 или 2, редко 3 или 4.


Вот каково использование CPU / GPU при обработке без дросселирования в разных режимах на моей машине (Intel Core i5-6500 с HD Graphics 530, Windows 10 Pro).

  • NULL — CPU: ~ 90%, GPU: ~ 15%
  • АППАРАТНОЕ ОБЕСПЕЧЕНИЕ — ЦП: ~ 15%, GPU: ~ 60%
  • ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ — CPU: ~ 40%, GPU: ~ 7%

Я протестировал приложение на трех машинах. Все они имели встроенные графические процессоры Intel (HD 4400, HD 4600, HD 530). Один из них также имел переключаемый графический процессор Nvidia (GF 840M). Он одинаково работает на всех них, с той лишь разницей, что он падает в другой DLL при использовании графического процессора Nvidia.


У меня нет предыдущего опыта работы с COM или DirectX, но все это противоречиво и непредсказуемо, поэтому для меня это выглядит как повреждение памяти. Тем не менее, я не знаю, где я делаю ошибку. Не могли бы вы помочь мне понять, что я делаю не так?

Пример минимального кода, который я мог бы придумать, приведен ниже. Я использую Visual Studio Professional 2015, чтобы скомпилировать его как проект C ++. Я подготовил определения, чтобы включить аппаратное ускорение и выбрать драйвер оборудования. Прокомментируйте их, чтобы изменить поведение. Также код ожидает этот видео файл присутствовать в каталоге проекта.

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

5

Решение

Концептуально ваш код корректен, с единственным замечанием — и не совсем очевидным — то, что декодер 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);

Также обратите внимание, что этот простой пример кода имеет узкое место в производительности вокруг строк, которые вы добавили для получения непрерывного буфера. Очевидно, это ваш шаг к получению доступа к данным … однако по своей конструкции поведение заключается в том, что декодированные данные уже находятся в видеопамяти, а передача в системную память — дорогостоящая операция. То есть вы добавили серьезный удар по производительности в цикл. Вы будете заинтересованы в проверке достоверности данных таким образом, и когда дело доходит до сравнительного анализа производительности, вам лучше закомментировать это.

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 в качестве выходного подтипа для декодера, вы избавитесь от видеопроцессора 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.

1

По вопросам рекламы [email protected]