Приветствую всех!
Ситуация следующая: есть видео разрешением 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
CComPtr
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
//Создание Менеджера Устройств MF
UINT token;
CComPtr
//Привязываем наше D3D Устройство к Менеджеру Устройств
CH(manager->ResetDevice(pD3DDevice, token));
//Создаем свойства SourceReader
CComPtr
//Создаем SourceReader и загружаем видео, зажатое h264 кодеком
CH(MFCreateSourceReaderFromURL(L"D:\\Work\\WorkFiles\\1.mp4", attributes, &reader));
//Настраиваем выходной медиатип декодера в SourceReader
CComPtr
//Создаем Видео Устройство
CComPtr
//Создаем видеотекстуру
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
//Создаем Видео Процессор
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, к примеру, такие фокусы позволяет.
Комментариев нет:
Отправить комментарий