Windows Media Foundation использует IMFTransform для декодирования кадров фильмов mp4 в 2D текстуры


Я пытаюсь декодировать видео mp4 с помощью классов Windows Media Foundation и преобразования кадров в 2D-текстуры, которые могут быть использованы шейдером DirectX для рендеринга. Я смог прочитать исходный поток с помощью MFCreateSourceReaderfromURL и смог прочитать тип носителя потока, который имеет свой основной тип MFMEdiaType_Video и минорный тип как MFVideoFormat_H264, как и ожидалось.

Теперь мне нужно преобразовать этот формат в формат RGB, который может использоваться для инициализации ресурса D3D11_TEXTURE2D и представления ресурсов, которые затем могут быть переданы в пиксельный шейдер HLSL для выборки. Я устал использовать класс IMFTransform , чтобы сделать преобразование за меня, но когда я пытаюсь установить тип вывода на преобразование в любой вариант MFVideoFormat_RGB, я получаю ошибку. Я также попытался установить новый тип вывода на исходном считывателе и просто выборку, надеясь получить образец в правильном формате, но снова мне не повезло.

Итак, мой вопросы будут:

  • Возможно ли такое преобразование?

  • Можно ли это сделать с помощью классов IMFTransform/SourceReader, как я устал выше, и мне просто нужно настроить код или мне нужно сделать этот тип преобразования вручную?

  • Является ли это лучшим способом передачи данных текстур видео в шейдер для выборки или есть более простая альтернатива, о которой я не думал.

Используемая ОС является Windows 7, поэтому я не могу использовать интерфейс SourceReaderEx или ID3D11VideoDevice, потому что, насколько мне известно, эти решения доступны только в Windows 8.

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

3 3

3 ответа:

Я вижу, что у вас есть некоторая ошибка в понимании медиа-Фонда. Вы хотите получить изображение в формате RGB от MFVideoFormat_H264, но вы не используете декодер H264. Вы написали: "Я устал использовать класс IMFTransform" - IMFTransform-это не класс. Это интерфейс для преобразования COM-объектов. Необходимо создать декодер com object Media Foundation H264. CLSID для декодера Microsoft software H264 - это CLSID_CMSH264DecoderMFT. Однако из этого декодера вы можете получить выходное изображение в следующем форматы: Типы Вывода

MFVideoFormat_I420

MFVideoFormat_IYUV

MFVideoFormat_NV12

MFVideoFormat_YUY2

MFVideoFormat_YV12

Вы можете создать D3D11_TEXTURE2D из одного из них. Или вы можете сделать что-то вроде этого из моего проекта CaptureManager SDK:

                CComPtrCustom<IMFTransform> lColorConvert;

                if (!Result(lColorConvert.CoCreateInstance(__uuidof(CColorConvertDMO))))
                {
                    lresult = MediaFoundationManager::setInputType(
                        lColorConvert,
                        0,
                        lVideoMediaType,
                        0);

                    if (lresult)
                    {
                        break;
                    }

                    DWORD lTypeIndex = 0;

                    while (!lresult)
                    {

                        CComPtrCustom<IMFMediaType> lOutputType;

                        lresult = lColorConvert->GetOutputAvailableType(0, lTypeIndex++, &lOutputType);

                        if (!lresult)
                        {


                            lresult = MediaFoundationManager::getGUID(
                                lOutputType,
                                MF_MT_SUBTYPE,
                                lSubType);

                            if (lresult)
                            {
                                break;
                            }

                            if (lSubType == MFVideoFormat_RGB32)
                            {
                                LONG lstride = 0;

                                MediaFoundationManager::getStrideForBitmapInfoHeader(
                                    lSubType,
                                    lWidth,
                                    lstride);

                                if (lstride < 0)
                                    lstride = -lstride;

                                lBitRate = (lHight * (UINT32)lstride * 8 * lNumerator) / lDenominator;

                                lresult = MediaFoundationManager::setUINT32(
                                    lOutputType,
                                    MF_MT_AVG_BITRATE,
                                    lBitRate);

                                if (lresult)
                                {
                                    break;
                                }


                                PROPVARIANT lVarItem;

                                lresult = MediaFoundationManager::getItem(
                                    *aPtrPtrInputMediaType,
                                    MF_MT_FRAME_RATE,
                                    lVarItem);

                                if (lresult)
                                {
                                    break;
                                }

                                lresult = MediaFoundationManager::setItem(
                                    lOutputType,
                                    MF_MT_FRAME_RATE,
                                    lVarItem);

                                if (lresult)
                                {
                                    break;
                                }

                                (*aPtrPtrInputMediaType)->Release();

                                *aPtrPtrInputMediaType = lOutputType.detach();

                                break;
                            }
                        }
                    }
                }

Вы можете установить ColorConvertDMO для преобразования из выходного формата декодера H264 в нужный вам.

Кроме того, вы можете просмотреть код по ссылке: videoInput. Этот код берет живое видео с веб-камеры и декодирует его в RGB. Если вы замените источник веб-камеры на источник видеофайла mp4, вы получите решение, которое близко к вашим потребностям.

С уважением

Возможен ли такой тип преобразования?

Да, это возможно. Stock H. 264 Video Decoder MFT является "Direct3D aware", что означает, что он может декодировать видео в поверхности Direct3D 9/текстуры Direct3D 11, используя DXVA. Или, если аппаратные возможности недостаточны, есть также резервный режим программного обеспечения. Вы заинтересованы в том, чтобы выходные данные доставлялись прямо в текстуру из соображений производительности (в противном случае вам пришлось бы загружать эти данные самостоятельно, тратя процессор и видео ресурсы на это).

Можно ли это сделать с помощью классов IMFTransform/SourceReader, как я устал выше, и мне просто нужно настроить код или мне нужно сделать этот тип преобразования вручную?

IMFTransform это абстрактный интерфейс. Он реализован декодером H. 264 (а также другими MFTs), и вы можете использовать его напрямую, или вы можете использовать API более высокого уровня Source Reader, чтобы заставить его управлять чтением видео из файла и декодированием с помощью этого MFT.

То есть MFT и Source Reader на самом деле не является эксклюзивным альтернативным вариантом, а вместо этого API более высокого и более низкого уровня. Интерфейс MFT предлагается декодером, и вы несете ответственность за подачу H. 264 и слив декодированного вывода. Source Reader управляет тем же MFT и добавляет возможность чтения файлов.

Сам Source Reader доступен в Windows 7, кстати (даже на Vista, может быть ограничен в наборе функций по сравнению с более новыми операционными системами).

Декодирование может быть выполнено следующим кодом:

                    MFT_OUTPUT_DATA_BUFFER loutputDataBuffer;

                    initOutputDataBuffer(
                        lTransform,
                        loutputDataBuffer);

                    DWORD lprocessOutputStatus = 0;

                    lresult = lTransform->ProcessOutput(
                        0,
                        1,
                        &loutputDataBuffer,
                        &lprocessOutputStatus);

                    if ((HRESULT)lresult == E_FAIL)
                    {
                        break;
                    }

Функция initOutputDataBuffer выделяет необходимую память. Пример этой функции представлен здесь:

            Result initOutputDataBuffer(IMFTransform* aPtrTransform,
            MFT_OUTPUT_DATA_BUFFER& aRefOutputBuffer)
        {
            Result lresult;

            MFT_OUTPUT_STREAM_INFO loutputStreamInfo;

            DWORD loutputStreamId = 0;

            CComPtrCustom<IMFSample> lOutputSample;

            CComPtrCustom<IMFMediaBuffer> lMediaBuffer;

            do
            {
                if (aPtrTransform == nullptr)
                {
                    lresult = E_POINTER;

                    break;
                }

                ZeroMemory(&loutputStreamInfo, sizeof(loutputStreamInfo));

                ZeroMemory(&aRefOutputBuffer, sizeof(aRefOutputBuffer));

                lresult = aPtrTransform->GetOutputStreamInfo(loutputStreamId, &loutputStreamInfo);

                if (lresult)
                {
                    break;
                }

                if ((loutputStreamInfo.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) == 0 &&
                    (loutputStreamInfo.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES) == 0)
                {
                    lresult = MFCreateSample(&lOutputSample);

                    if (lresult)
                    {
                        break;
                    }

                    lresult = MFCreateMemoryBuffer(loutputStreamInfo.cbSize, &lMediaBuffer);

                    if (lresult)
                    {
                        break;
                    }

                    lresult = lOutputSample->AddBuffer(lMediaBuffer);

                    if (lresult)
                    {
                        break;
                    }

                    aRefOutputBuffer.pSample = lOutputSample.Detach();
                }
                else
                {
                    lresult = S_OK;
                }

                aRefOutputBuffer.dwStreamID = loutputStreamId;
            } while (false);

            return lresult;
        }

Он должен получить информацию о выходных образцах с помощью метода GetOutputStreamInfo IMFTransform. MFT_OUTPUT_STREAM_INFO содержит информацию о необходимом размере памяти для вывода образца носителя-cbSize. Ему нужно выделить память с таким размером, добавить ее в MediaSample и присоединить к th MFT_OUTPUT_DATA_BUFFER.

Таким образом, вы видите, что написание кода для кодирования и декодирования видео с помощью прямого вызова функции MediaFoundation может быть сложным и требует значительных знаний о нем. Из описания вашей задачи я вижу, что вам нужно только расшифровать видео и представить его. Я могу посоветовать вам попробовать использовать функциональность сеанса Media Foundation. Он разработан инженерами Microsoft и уже включает в себя алгоритмы использования необходимых кодеров и оптимизирован. В проектах Сеанс Media Foundation используется для поиска подходящего декодера для Медиаисточника, который создается для веб-камеры и захвата кадров в несжатом формате. Он уже делает необходимую обработку. Вам нужно только заменить источник Мультимедиа из веб-камеры на источник Мультимедиа из видеофайла. Это может быть проще, чем написание кода с прямым вызовом IMFTransform для декодирования и позволяет упростить многие задачи (например - стабилизацию частоты кадров. Если код будет рендеринг изображения сразу после декодирования, а затем декодировать новый кадр, то он может рендерить 1 минутный видеоклип в течение нескольких секунд, или если рендеринг видео и другого контента может занять более одного кадра длительность видео может быть представлена в стиле "замедленной съемки" и рендеринг 1 минутного видеоклипа может занять 2, 3 или 5 минут. Я не знаю, для какого проекта вам нужно декодирование видео, но у вас должны быть серьезные основания для использования кода с прямым вызовом функций Media Foundation и межфазные границы.

С уважением.