Я пытаюсь закодировать буферы 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](https://i.stack.imgur.com/Ix399.png)
Декодированное изображение (После RGBA -> YUV2 -> H264 -> YUV2 -> RGBA преобразование):
Нажмите, чтобы открыть в новой вкладкечтобы просмотреть полное изображение, чтобы увидеть эффект наложения.
![enter image description here](https://i.stack.imgur.com/lTQpI.png)