Страницы

Поиск по вопросам

вторник, 27 ноября 2018 г.

Воспроизведение большого видео с деформацией - DirectX

Приветствую всех!
Ситуация следующая: есть видео разрешением 1920x2400, закодированное hevc (h265), спрятанное в .mp4, подготовленное с частотой 30 фпс; аудиопотока нет, только видео
Есть задача - воспроизводить это видео на двух мониторах 1920x1080 (так называемая вертикальная стерео-пара) После поиска методов для реализации данного алгоритма, было принято решение использовать DirectShow для получения кадров и DirectX для вывода текстуры. Получилась следующая цепочка графов:
FileSourceFilter -> Lav Splitter -> Lav Video Decoder -> SampleGrabber -> NullRenderer
В callback-функции SampleGrabber'а беру и создаю Texture2D, из которой создаю ShaderResourceView. Ну, и соответственно, есть два плоских меша, на которых накладывается текстура. Отрисовка происходит вот так:
const float bg_clear_color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
pD3DDC->ClearRenderTargetView(pD3DRenderTarget, bg_clear_color); pD3DDC->ClearDepthStencilView(pD3DDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
objects[0]->Render(); objects[1]->Render();
pD3DSwapChain->Present(0, 0);
И картинка идет, но происходят подтормаживания. Бьюсь над этим вопросом уже несколько месяцев, не знаю в каком направлении рыть, пробовал всякое, и аппаратное ускорение и снижение битрейта видео до 10-12 мбит/c. Ситуация немного улучшилась, но все равно картинка дергается.
Прошу экспертов и знающих проконсультировать по данному вопросу, может быть сам алгоритм неправильно продуман. Есть чувство, что я что-то упускаю. Буду рад любой помощи, спасибо.
EDIT:
В данный момент по совету @Roman совершаю миграцию своего движка на Media Foundation с DirectShow. Так как мы перешли на MF, начали использовать h264 для надежности, потому что как я понял, h265 сложнее использовать и по-умолчанию драйвера не установлены, придется подключать их дополнительно, как самописные фильтры DS (возможно, я ошибаюсь, но это пока не важно).
Задача немного изменилась и теперь произносится так: есть четыре видео 1920x1080 h264 20 mbps 30 fps. Разрешение было выбрано именно такое, потому что в документации на MSDN мной было замечено, что максимальное разрешение видео, при котором возможно использовать аппаратное ускорение, - 1920x1088. Задача требует максимально быстро получать кадры из каждого видео и писать их в текстуры ID3D11Texture2D, чтобы потом их выводить в сцене Direct3D 11.
Сейчас на рабочей машине стоит две видеокарты NVIDIA GTX 950, к каждой видеокарте подключено по два монитора, и еще один монитор. Все мониторы 1920x1080.
После того как я провел неделю кряду над поиском информации по MF, я был удивлен, как мало информации можно найти на тему видео текстур с поддержкой HWA, проштрудировал практически все мануалы по MF на MSDN, но все равно многое осталось мне непонятным. Сейчас расскажу как у меня сейчас все работает и чего хотелось бы добиться. Выражаю огромную благодарность товарищу @Roman, который многое подсказал и направил меня в нужном направлении.
Direct3D устройство создается у меня следующим кодом:
D3D_FEATURE_LEVEL fl[] = {D3D_FEATURE_LEVEL_11_1}; CH(D3D11CreateDevice(0, D3D_DRIVER_TYPE_HARDWARE, 0, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, fl, ARRAYSIZE(fl), D3D11_SDK_VERSION, &pD3DDevice, 0, &pD3DDC));
При перечислении доступных устройств у меня в списке отображается четыре, Intel(R) HD Graphics, два NDIVIA GTX 950 и Microsoft Basic Render Driver. Хотелось бы использовать два видео на первом NVIDIA устройстве, а остальные два на другом, но вот в чем проблема. Если я создаю устройство с указанием одного из них, мне приходится передавать параметр D3D_DRIVER_TYPE_UNKNOWN, что снижает фпс и одно видео на NV выдает ~25 фпс. Если я инициализирую устройство как в коде выше - по-умолчанию выбирается Intel, но зато фпс выдает ~50 (я подозреваю, что почему-то не включается аппаратное ускорение), что странно.
Моя затея следующая: я хочу максимально задействовать силы NVIDIA карты с HWA, чтобы все кадры видео сразу рендерелись в текстуру, без лишнего копирования в CPU, а потом обратно в GPU, как у меня было реализовано.
CComPtr sample; CH(reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, 0, &flags, 0, &sample.p));
CComPtr buffer; BYTE* data; CH(sample->ConvertToContiguousBuffer(&buffer)); CH(buffer->Lock(&data, NULL_PTR, NULL_PTR));
DWORD dwSize; CH(buffer->GetCurrentLength(&dwSize)); UpdateVideoTexture(data, dwSize);
buffer->Unlock();
Этот код был создан на основе вот этого, но там все также есть затык, получается мы вытаскиваем текстуру из GPU и потом засовываем ее обратно. Этот код медленный. Поэтому я начал копать дальше, и вот что получилось, как я инициализирую SourceReader.
//Инициализируем COM библиотеку для нашего приложения и MF библиотеку CH(CoInitializeEx(NULL_PTR, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); CH(MFStartup(MF_VERSION));
//Говорим библиотеке D3D что мы используем мультипоточные вызовы MF, чтобы избежать undefined behavior const CComQIPtr pMultithread = pD3DDevice; pMultithread->SetMultithreadProtected(TRUE);
//Создание Менеджера Устройств MF UINT token; CComPtr manager; CH(MFCreateDXGIDeviceManager(&token, &manager));
//Привязываем наше D3D Устройство к Менеджеру Устройств CH(manager->ResetDevice(pD3DDevice, token));
//Создаем свойства SourceReader CComPtr attributes; CH(MFCreateAttributes(&attributes, 2)); CH(attributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, manager)); //указываем менеджер CH(attributes->SetUINT32(MF_SOURCE_READER_DISABLE_DXVA, FALSE)); CH(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE));
//Создаем SourceReader и загружаем видео, зажатое h264 кодеком CH(MFCreateSourceReaderFromURL(L"D:\\Work\\WorkFiles\\1.mp4", attributes, &reader));
//Настраиваем выходной медиатип декодера в SourceReader CComPtr output_type; CH(MFCreateMediaType(&output_type)); CH(output_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video)); CH(output_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12)); CH(reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL_PTR, output_type));
//Создаем Видео Устройство CComPtr pVideoDevice; CH(pD3DDevice->QueryInterface(IID_PPV_ARGS(&pVideoDevice)));
//Создаем видеотекстуру CH(D3DHelper_Create2DTexture(pD3DDevice, &pD3DVideoTexture, CORE_WIDTH, CORE_HEIGHT, DXGI_FORMAT_NV12, D3D11_BIND_DECODER, 0, D3D11_USAGE_DEFAULT, 0));
D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC dc; dc.DecodeProfile = D3D11_DECODER_PROFILE_H264_VLD_NOFGT; dc.Texture2D.ArraySlice = 0; dc.ViewDimension = D3D11_VDOV_DIMENSION_TEXTURE2D;
//И описываем D3D View текстуры CH(pVideoDevice->CreateVideoDecoderOutputView((ID3D11Resource*)pD3DVideoTexture, &dc, &pVideoDecoderOutputView)); CH(pD3DDC->QueryInterface(IID_PPV_ARGS(&pVideoContext)));
//Описываем енумератор Видео Процессора D3D11_VIDEO_PROCESSOR_CONTENT_DESC ed; CLEAR(ed);
ed.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE; ed.InputFrameRate.Numerator = 60; ed.InputFrameRate.Denominator = 1; ed.InputWidth = CORE_WIDTH; ed.InputHeight = CORE_HEIGHT; ed.OutputFrameRate.Numerator = 60; ed.OutputFrameRate.Denominator = 1; ed.OutputWidth = CORE_WIDTH; ed.OutputHeight = CORE_HEIGHT; ed.Usage = D3D11_VIDEO_USAGE_OPTIMAL_SPEED;
CComPtr pVideoEnum; CH(pVideoDevice->CreateVideoProcessorEnumerator(&ed, &pVideoEnum));
//Создаем Видео Процессор CH(pVideoDevice->CreateVideoProcessor(pVideoEnum, 0, &pVideoProcessor));
В последствии я планирую вызывать ReadSample, а что делать дальше не знаю, прошу помощи, спасибо. Ради поддержки вышестоящего кода пришлось даже пересесть с Win7 на Win10. Благодарю за любую помощь.
EDIT:
P.S.: Вот код, которым сейчас инициализируется текстура:
D3D11_MAPPED_SUBRESOURCE resource; CH(pD3DDC->Map(pD3DVideoTexture, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource));
memcpy(resource.pData, data, dwSize); pD3DDC->Unmap(pD3DVideoTexture, 0);
SAFE_COM_RELEASE(pD3DVideoTextureRV);
CH(pD3DDevice->CreateShaderResourceView(pD3DVideoTexture, 0, &pD3DVideoTextureRV)); pD3DDC->PSSetShaderResources(0, 1, &pD3DVideoTextureRV);


Ответ

В этой схеме есть сразу несколько потенциальных заторов, один или несколько из которых ограничивают общую работу. Они таковы:
Неизвестна производительность декодера, справляется ли он нормально с задачей или он и тормозит Подключение Sample Grabber таково, что возможно между ним и декодером происходит дополнительное преобразование данных, либо как дополнительный фильтр, либо в рамках LAV декодера - это может сказываться на производительности Sample Grabber выгребает данные в системной памяти независимо от режима аппаратного ускорения Sample Grabber может оперировать ограниченным числом буферов, что в свою очередь может ограничивать возможность декодера своевременно пополнять очередь; само присутствие Sample Grabber как правило является свидетельством неспособности потреблять данные более эффективным способом Не указан тип текстуры заполнение staging текстур в реальном времени имеет слабую производительность
Так, в общем, хорошо бы декодировать аппаратно прямиком в текстуру и дальше уже данными в ресурсе графического адаптера манипулировать рисуя сцену. Такой подход, к сожалению, нереализуем с Sample Grabber. Media Foundation API, к примеру, такие фокусы позволяет.

Комментариев нет:

Отправить комментарий