Проблемы с буферизацией MFT и качеством видео в Windows Media Foundation (потеря цветов, не очень плавные кривые, особенно текст) - PullRequest
0 голосов
/ 05 марта 2019

Я пытаюсь закодировать буферы RGBA, захваченные из источника изображения (RGBA) (рабочий стол / камера), в необработанный H264 с помощью Windows Media Foundation, перенести их и декодировать необработанные кадры H264, полученные на другом конце, в режиме реального времени.Я пытаюсь достичь как минимум 30 кадров в секунду.Кодер работает довольно хорошо, но не декодер.

Я понимаю, что MFT-файлы Microsoft WMF буферизуют до 30 кадров перед отправкой закодированных / декодированных данных.

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

И кодер, и декодер способны излучать по крайней мере от 10 до 15 кадров в секунду, когда я делаю источник изображения вотправлять непрерывные изменения (путем стимулирования изменений).Кодировщик может использовать поддержку аппаратного ускорения.Я могу достичь скорости 30 кадров в секунду на стороне кодера, и мне еще предстоит реализовать аппаратное декодирование с использованием поверхностей DirectX.Здесь проблема не в частоте кадров, а в буферизации данных с помощью MFT.

Итак, я попытался истощить MFT декодера, отправив команду MFT_MESSAGE_COMMAND_DRAIN и многократно вызывая ProcessOutput, пока декодер не вернется MF_E_TRANSFORM_NEED_MORE_INPUT .Теперь происходит то, что декодер теперь излучает только один кадр на 30 входных буферов h264, я тестировал его даже с непрерывным потоком данных, и поведение такое же. Похоже, что декодер отбрасывает все промежуточные кадры в GOP.

Это нормально для меня, если он буферизует только первые несколько кадров, но моя реализация декодера выдает только тогда, когда буфер заполнен, всевремя даже после фазы синтаксического анализа SPS и PPS.

Я сталкиваюсь с исходным кодом хрома Google (https://github.com/adobe/chromium/blob/master/content/common/gpu/media/dxva_video_decode_accelerator.cc), они используют тот же подход.

mpDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);

Моя реализация основана наhttps://github.com/GameTechDev/ChatHeads/blob/master/VideoStreaming/EncodeTransform.cpp

и

https://github.com/GameTechDev/ChatHeads/blob/master/VideoStreaming/DecodeTransform.cpp

Мои вопросы: я что-то упустил? Подходит ли Windows Media Foundation для потоковой передачи в реальном времени«Будет ли отводить кодер и декодер для сценариев использования в режиме реального времени?».

У меня есть только два варианта: сделать этот WMF работающим в режиме реального времени или использовать что-то вроде QuickSync от Intel.Я выбрал WMF для своего POC, потому что Windows Media Foundation неявно поддерживает аппаратные / графические / программные резервные копии в случае, если какой-либо из MFT недоступен, и он внутренне выбирает лучший из доступных MFT безочень много кодирования.

У меня проблемы с качеством видео, хотя свойство bitrate установлено на 3Mbps.Но это меньше всего по сравнению с проблемами буферизации.Я неделями бьюсь головой об клавиатуру, это так сложно исправить.Мы будем благодарны за любую помощь.

Код:

Настройка энкодера:

IMFAttributes* attributes = 0;
    HRESULT  hr = MFCreateAttributes(&attributes, 0);

    if (attributes)
    {
        //attributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, TRUE);
        attributes->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4);
    }//end if (attributes)

    hr = MFCreateMediaType(&pMediaTypeOut);
    // Set the output media type.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateMediaType(&pMediaTypeOut);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, cVideoEncodingFormat); // MFVideoFormat_H264
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, VIDEO_BIT_RATE); //18000000
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, VIDEO_FPS, 1); // 30
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, mStreamWidth, mStreamHeight);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(MF_MT_MPEG2_PROFILE, eAVEncH264VProfile_High);
    }

    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
    }

    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(MF_MT_MAX_KEYFRAME_SPACING, 16);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(CODECAPI_AVEncCommonRateControlMode, eAVEncCommonRateControlMode_UnconstrainedVBR);//eAVEncCommonRateControlMode_Quality, eAVEncCommonRateControlMode_UnconstrainedCBR);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(CODECAPI_AVEncCommonQuality, 100);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, FALSE);
    }
    if (SUCCEEDED(hr))
    {
        BOOL allSamplesIndependent = TRUE;
        hr = pMediaTypeOut->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, allSamplesIndependent);
    }
    if (SUCCEEDED(hr))
    {
        hr = pMediaTypeOut->SetUINT32(MF_MT_COMPRESSED, TRUE);
    }

    if (SUCCEEDED(hr))
    {
        hr = mpEncoder->SetOutputType(0, pMediaTypeOut, 0);
    }

// Обработка входящего образца.Игнорируя параметры timestamp & duration, мы просто визуализируем данные в режиме реального времени.

HRESULT ProcessSample(IMFSample **ppSample, LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn)
{
    IMFMediaBuffer *buffer = nullptr;
    DWORD bufferSize;
    HRESULT hr = S_FALSE;

    if (ppSample)
    {
        hr = (*ppSample)->ConvertToContiguousBuffer(&buffer);

        if (SUCCEEDED(hr))
        {
            buffer->GetCurrentLength(&bufferSize);

            hr = ProcessInput(ppSample);

            if (SUCCEEDED(hr))
            {
                //hr = mpDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);

                //if (SUCCEEDED(hr)) 
                {
                    while (hr != MF_E_TRANSFORM_NEED_MORE_INPUT)
                    {
                        hr = ProcessOutput(time, duration, oDtn);
                    }
                }
            }
            else
            {
                if (hr == MF_E_NOTACCEPTING)
                {
                    while (hr != MF_E_TRANSFORM_NEED_MORE_INPUT)
                    {
                        hr = ProcessOutput(time, duration, oDtn);
                    }

                }
            }
        }

    }

    return (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ? (oDtn.numBytes > 0 ? oDtn.returnCode : hr) : hr);
}

// Находит и возвращает h264 MFT (заданный в параметре подтипа), если доступно ... в противном случае происходит сбой.

HRESULT FindDecoder(const GUID& subtype)
{
    HRESULT hr = S_OK;
    UINT32 count = 0;

    IMFActivate  **ppActivate = NULL;

    MFT_REGISTER_TYPE_INFO info = { 0 };

    UINT32 unFlags = MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_ASYNCMFT;

    info.guidMajorType = MFMediaType_Video;
    info.guidSubtype = subtype;

    hr = MFTEnumEx(
        MFT_CATEGORY_VIDEO_DECODER,
        unFlags,
        &info,
        NULL,
        &ppActivate,
        &count
    );

    if (SUCCEEDED(hr) && count == 0)
    {
        hr = MF_E_TOPO_CODEC_NOT_FOUND;
    }

    if (SUCCEEDED(hr))
    {
        hr = ppActivate[0]->ActivateObject(IID_PPV_ARGS(&mpDecoder));
    }

    CoTaskMemFree(ppActivate);
    return hr;
}

// восстанавливает семпл из кодированных данных

HRESULT ProcessData(char *ph264Buffer, DWORD bufferLength, LONGLONG& time, LONGLONG& duration, TransformOutput &dtn)
{
    dtn.numBytes = 0;
    dtn.pData = NULL;
    dtn.returnCode = S_FALSE;

    IMFSample *pSample = NULL;
    IMFMediaBuffer *pMBuffer = NULL;

    // Create a new memory buffer.
    HRESULT hr = MFCreateMemoryBuffer(bufferLength, &pMBuffer);

    // Lock the buffer and copy the video frame to the buffer.
    BYTE *pData = NULL;
    if (SUCCEEDED(hr))
        hr = pMBuffer->Lock(&pData, NULL, NULL);

    if (SUCCEEDED(hr))
        memcpy(pData, ph264Buffer, bufferLength);

    pMBuffer->SetCurrentLength(bufferLength);
    pMBuffer->Unlock();

    // Create a media sample and add the buffer to the sample.
    if (SUCCEEDED(hr))
        hr = MFCreateSample(&pSample);

    if (SUCCEEDED(hr))
        hr = pSample->AddBuffer(pMBuffer);

    LONGLONG sampleTime = time - mStartTime;

    // Set the time stamp and the duration.
    if (SUCCEEDED(hr))
        hr = pSample->SetSampleTime(sampleTime);

    if (SUCCEEDED(hr))
        hr = pSample->SetSampleDuration(duration);

    hr = ProcessSample(&pSample, sampleTime, duration, dtn);

    ::Release(&pSample);
    ::Release(&pMBuffer);

    return hr;
}

// Обрабатывает выходной семпл для декодера

HRESULT ProcessOutput(LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn/*output*/)
{
    IMFMediaBuffer *pBuffer = NULL;
    DWORD mftOutFlags;
    MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
    IMFSample *pMftOutSample = NULL;
    MFT_OUTPUT_STREAM_INFO streamInfo;

    memset(&outputDataBuffer, 0, sizeof outputDataBuffer);

    HRESULT hr = mpDecoder->GetOutputStatus(&mftOutFlags);
    if (SUCCEEDED(hr))
    {
        hr = mpDecoder->GetOutputStreamInfo(0, &streamInfo);
    }


    if (SUCCEEDED(hr))
    {
        hr = MFCreateSample(&pMftOutSample);
    }


    if (SUCCEEDED(hr))
    {
        hr = MFCreateMemoryBuffer(streamInfo.cbSize, &pBuffer);
    }


    if (SUCCEEDED(hr))
    {   
        hr = pMftOutSample->AddBuffer(pBuffer);
    }

    if (SUCCEEDED(hr))
    {
        DWORD dwStatus = 0;

        outputDataBuffer.dwStreamID = 0;
        outputDataBuffer.dwStatus = 0;
        outputDataBuffer.pEvents = NULL;
        outputDataBuffer.pSample = pMftOutSample;

        hr = mpDecoder->ProcessOutput(0, 1, &outputDataBuffer, &dwStatus);
    }

    if (SUCCEEDED(hr))
    {
        hr = GetDecodedBuffer(outputDataBuffer.pSample, outputDataBuffer, time, duration, oDtn);
    }

    if (pBuffer)
    {
        ::Release(&pBuffer);
    }

    if (pMftOutSample)
    {
        ::Release(&pMftOutSample);
    }

    return hr;
}

// Выводим декодированный семпл

HRESULT GetDecodedBuffer(IMFSample *pMftOutSample, MFT_OUTPUT_DATA_BUFFER& outputDataBuffer, LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn/*output*/)
{
    // ToDo: These two lines are not right. Need to work out where to get timestamp and duration from the H264 decoder MFT.
    HRESULT hr = outputDataBuffer.pSample->SetSampleTime(time);

    if (SUCCEEDED(hr))
    {
        hr = outputDataBuffer.pSample->SetSampleDuration(duration);
    }


    if (SUCCEEDED(hr))
    {
        hr = pMftOutSample->ConvertToContiguousBuffer(&pDecodedBuffer);
    }

    if (SUCCEEDED(hr))
    {
        DWORD bufLength;
        hr = pDecodedBuffer->GetCurrentLength(&bufLength);
    }

    if (SUCCEEDED(hr))
    {
        byte *pEncodedYUVBuffer;
        DWORD buffCurrLen = 0;
        DWORD buffMaxLen = 0;
        pDecodedBuffer->GetCurrentLength(&buffCurrLen);
        pDecodedBuffer->Lock(&pEncodedYUVBuffer, &buffMaxLen, &buffCurrLen);
        ColorConversion::YUY2toRGBBuffer(pEncodedYUVBuffer, 
                                        buffCurrLen, 
                                        mpRGBABuffer,
                                        mStreamWidth,
                                        mStreamHeight,
                                        mbEncodeBackgroundPixels,
                                        mChannelThreshold);

        pDecodedBuffer->Unlock();       
        ::Release(&pDecodedBuffer);

        oDtn.pData = mpRGBABuffer;
        oDtn.numBytes = mStreamWidth * mStreamHeight * 4;
        oDtn.returnCode = hr; // will be S_OK..
    }

    return hr;
}

Обновление: Вывод декодера теперь удовлетворительный после включения режима CODECAPI_AVLowLatency, но с задержкой в ​​2 секунды в потоке по сравнению с отправителем я могу достичь 15-20 кадров в секунду, этонамного лучше по сравнению с предыдущим.Качество ухудшается, когда от источника к кодеру поступает большее количество изменений.Мне еще предстоит реализовать аппаратное ускоренное декодирование.

Обновление 2: Я выяснил, что настройки меток времени и длительности влияют на качество видео, если они установлены неправильно.Дело в том, что мой источник изображения не излучает кадры с постоянной скоростью, но похоже, что кодер и декодеры ожидают постоянной частоты кадров.Когда я устанавливаю длительность как постоянную и увеличиваю время выборки с постоянными шагами, качество видео кажется лучшим, но не самым лучшим.Я не думаю, что я делаю правильный подход.Есть ли способ указать кодеру и декодеру переменную частоту кадров?

Update3: Я могу получить приемлемую производительность как от кодеров, так и от декодеров после установки CODECAPI_AVEncMPVDefaultBPictureCount (0),Свойства CODECAPI_AVEncCommonRealTime и CODECAPI_AVEncCommonLowLatency.Еще предстоит изучить аппаратное ускорение декодирования.Я надеюсь, что смогу добиться максимальной производительности, если будет реализовано аппаратное декодирование.

Качество видео все еще низкое, края и кривые не четкие.Текст выглядит размытым, и это не приемлемо.Качество в порядке для видео и изображений, но не для текстов и фигур.

Update4

Кажется, что некоторая информация о цвете теряется на этапе субдискретизации YUV.Я попытался преобразовать RGBA-буфер в YUV2, а затем обратно. Потеря цвета видна, но неплохо.Потери из-за преобразования YUV не так плохи, как качество изображения, которое отображается после преобразования RGBA-> YUV2 -> H264 -> YUV2 -> RGBA.Очевидно, что не только преобразование YUV2 является единственной причиной потери качества, но также и кодер H264, который в дальнейшем вызывает алиасинг.Я все равно получил бы лучшее качество видео, если бы кодирование H264 не приводило к эффектам наложения.Я собираюсь изучить кодеки WMV.Единственное, что меня до сих пор беспокоит, это this , код работает довольно хорошо и способен захватывать экран и сохранять поток в формате mp4 в файле.Единственное отличие состоит в том, что я использую фундаментальное преобразование Media с входным форматом MFVideoFormat_YUY2 по сравнению с подходом записи приемника с MFVideoFormat_RGB32 в качестве входного типа в упомянутом коде.У меня все еще есть надежда, что с помощью Media Foundation можно добиться лучшего качества.Дело в том, что MFTEnum / ProcessInput завершается ошибкой, если я указываю MFVideoFormat_ARGB32 в качестве формата ввода в MFT_REGISTER_TYPE_INFO (MFTEnum) / SetInputType соответственно.

Оригинал:

enter image description here

Декодированное изображение (После RGBA -> YUV2 -> H264 -> YUV2 -> RGBA преобразование):

Нажмите, чтобы открыть в новой вкладкечтобы просмотреть полное изображение, чтобы увидеть эффект наложения.

enter image description here

Ответы [ 2 ]

0 голосов
/ 14 марта 2019

Я пытаюсь понять вашу проблему.

Моя программа ScreenCaptureEncode использует настройки кодировщика Microsoft по умолчанию:

  • Профиль: базовый уровень
  • Уровень: 40
  • CODECAPI_AVEncCommonQuality: 70
  • Битрейт: 2000000

По моим результатам, я считаю, что качество хорошее / приемлемое.

Выможно изменить профиль / уровень / битрейт с помощью MF_MT_MPEG2_PROFILE / MF_MT_MPEG2_LEVEL / MF_MT_AVG_BITRATE.Для CODECAPI_AVEncCommonQuality кажется, что вы пытаетесь использовать локально зарегистрированный кодировщик, потому что вы на Win7, чтобы установить это значение на 100, я думаю.

, но я не думаю, что это сильно изменит ситуацию.

Итак.

Вот 3 скриншота с экраном печати клавиатуры:

  • экран
  • кодированный экран, воспроизводимый видеопроигрывателем вполноэкранный режим
  • кодированный экран, воспроизводимый видеопроигрывателем в не полноэкранном режиме

enter image description here

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

Я думаю, что алиасинг исходит от вашего видеопроигрывателя, и потому что не воспроизводится в полноэкранном режиме.

PS: я использую видеоплеер MPC-HC.

PS2: моя программа нуждается в улучшении:

  • (не уверен), используйте IDirect3D9Ex для улучшения буферизованного механизма.В Windows7 для рендеринга лучше использовать IDirect3D9Ex (без буфера подкачки).Возможно, это то же самое для экрана захвата (список задач).
  • Я должен использовать два потока, один для экрана захвата и один для кодирования.

РЕДАКТИРОВАТЬ

Читали ли вы это:

CODECAPI_AVLowLatencyMode

Режим низкой задержки полезен для связи в реальном времени или захвата в реальном времени, когда задержкадолжно быть сведено к минимуму.Однако режим с низкой задержкой может также снизить качество декодирования или кодирования .

О том, почему моя программа использует MFVideoFormat_RGB32, а ваша - MFVideoFormat_YUY2.По умолчанию в SinkWriter включены конвертеры.SinkWriter преобразует MFVideoFormat_RGB32 в совместимый формат кодера h264.Для кодировщика Microsoft прочитайте это: Видеокодер H.264

Формат ввода:

  • MFVideoFormat_I420
  • MFVideoFormat_IYUV
  • MFVideoFormat_NV12
  • MFVideoFormat_YUY2
  • MFVideoFormat_YV12

Таким образом, MFVideoFormat_RGB32 отсутствует.SinkWriter выполняет преобразование с использованием Color Converter DSP, я думаю.

, поэтому определенно проблема не в преобразовании rgb в yuv до кодирования.

PS (последнее)

, как сказал Маркус Шуман,

H.264 был больше предназначен для естественного контента, а не для захвата экрана.

Он должен был упомянутьчто проблема особенно связана с захватом текста.

Вы только что обнаружили ограничение кодера.Я просто думаю, что ни один кодировщик не подходит для кодирования текста с приемлемым растягиванием, как я упоминал при рендеринге видеоплеера.

Вы видите псевдоним при окончательном захвате видео, потому что это фиксированная информация внутри фильма.Воспроизведение этого фильма в полноэкранном режиме (то же, что и при захвате) - это нормально.

В Windows текст рассчитывается в соответствии с разрешением экрана.Так что дисплей всегда хорош.

это мой последний вывод.

0 голосов
/ 08 марта 2019

Большинство потребительских кодеров H.264 подбирают информацию о цвете до 4: 2: 0.(RGB в YUV) Это означает, что до того, как процесс кодирования даже запустит ваше растровое изображение RGB, теряет 75% информации о цвете.H.264 был больше разработан для естественного контента, а не для захвата экрана.Но есть кодеки, которые специально разработаны для обеспечения хорошего сжатия содержимого экрана.Например: https://docs.microsoft.com/en-us/windows/desktop/medfound/usingthewindowsmediavideo9screencodec Даже если вы увеличиваете битрейт вашего кодера H.264 - вы работаете только с 25% исходной информации о цвете для начала.

Таким образом, ваши изменения формата выглядят такэто:

Вы начинаете с 1920x1080 красных, зеленых и синих пикселей.Вы превращаетесь в YUV.Теперь у вас есть 1920x1080 люма, Cb и Cr.где Cb и Cr являются компонентами разности цветов.Это просто другой способ представления цветов.Теперь вы масштабируете плоскости Cb и Cr до 1/4 их первоначального размера.Таким образом, ваши результирующие каналы Cb и Cr составляют около 960x540, а ваша плоскость яркости по-прежнему составляет 1920x1080.Масштабируя информацию о цвете с 1920x1080 до 960x540, вы уменьшаете размер до 25% от исходного размера.Затем в кодер передаются полноразмерные плоскости яркости и 25% цветоразностных каналов.Этот уровень уменьшения информации о цвете называется подвыборкой до 4: 2: 0.Вход субсэмплирования требуется кодером и выполняется автоматически мультимедийной структурой.Вы можете сделать немного, чтобы избежать его - выбрать другой формат за пределами.

R = red
G = green
B = blue

Y = luminescence
U = blue difference  (Cb)
V = red difference  (Cr)

YUV используется для выделения сигнала яркости (Y), который может храниться с высоким разрешением или передаваться с высокимполоса пропускания и два компонента цветности (U и V), которые могут быть уменьшены по полосе пропускания, подвыбраны, сжаты или обработаны отдельно для повышения эффективности системы.(Википедия)

Original format

RGB (4:4:4) 3 bytes per pixel

R  R  R  R   R  R  R  R    R  R  R  R   R  R  R  R
G  G  G  G   G  G  G  G    G  G  G  G   G  G  G  G
B  B  B  B   B  B  B  B    B  B  B  B   B  B  B  B

Encoder input format - before H.264 compression

YUV (4:2:0) 1.5 bytes per pixel (6 bytes per 4 pixel)

Y  Y  Y  Y   Y  Y  Y  Y   Y  Y  Y  Y   Y  Y  Y  Y
    UV           UV           UV           UV
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...