Estou tentando transmitir a área de trabalho (superfície DirectX no formato NV12) como vídeo H264 sobre fluxo RTP usando o codificador de hardware Live555 e Windows Media Foundation no Windows10 e esperando que seja renderizado por ffplay (ffmpeg 4.2). Mas apenas obtendo uma tela verde como abaixo,
Referi-me mediafoundation amostra MFWebCamToRTP & Encoding DirectX superfície usando MFT hardware para implementar FramedSource das live555 e mudar a fonte de entrada para a superfície DirectX em vez de Webcam.
Aqui está um trecho da minha implementação do retorno de chamada doGetNextFrame do Live555 para alimentar amostras de entrada da superfície directX:
virtual void doGetNextFrame()
{
if (!_isInitialised)
{
if (!initialise()) {
printf("Video device initialisation failed, stopping.");
return;
}
else {
_isInitialised = true;
}
}
//if (!isCurrentlyAwaitingData()) return;
DWORD processOutputStatus = 0;
HRESULT mftProcessOutput = S_OK;
MFT_OUTPUT_STREAM_INFO StreamInfo;
IMFMediaBuffer *pBuffer = NULL;
IMFSample *mftOutSample = NULL;
DWORD mftOutFlags;
bool frameSent = false;
bool bTimeout = false;
// Create sample
CComPtr<IMFSample> videoSample = NULL;
// Create buffer
CComPtr<IMFMediaBuffer> inputBuffer;
// Get next event
CComPtr<IMFMediaEvent> event;
HRESULT hr = eventGen->GetEvent(0, &event);
CHECK_HR(hr, "Failed to get next event");
MediaEventType eventType;
hr = event->GetType(&eventType);
CHECK_HR(hr, "Failed to get event type");
switch (eventType)
{
case METransformNeedInput:
{
hr = MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), surface, 0, FALSE, &inputBuffer);
CHECK_HR(hr, "Failed to create IMFMediaBuffer");
hr = MFCreateSample(&videoSample);
CHECK_HR(hr, "Failed to create IMFSample");
hr = videoSample->AddBuffer(inputBuffer);
CHECK_HR(hr, "Failed to add buffer to IMFSample");
if (videoSample)
{
_frameCount++;
CHECK_HR(videoSample->SetSampleTime(mTimeStamp), "Error setting the video sample time.\n");
CHECK_HR(videoSample->SetSampleDuration(VIDEO_FRAME_DURATION), "Error getting video sample duration.\n");
// Pass the video sample to the H.264 transform.
hr = _pTransform->ProcessInput(inputStreamID, videoSample, 0);
CHECK_HR(hr, "The resampler H264 ProcessInput call failed.\n");
mTimeStamp += VIDEO_FRAME_DURATION;
}
}
break;
case METransformHaveOutput:
{
CHECK_HR(_pTransform->GetOutputStatus(&mftOutFlags), "H264 MFT GetOutputStatus failed.\n");
if (mftOutFlags == MFT_OUTPUT_STATUS_SAMPLE_READY)
{
MFT_OUTPUT_DATA_BUFFER _outputDataBuffer;
memset(&_outputDataBuffer, 0, sizeof _outputDataBuffer);
_outputDataBuffer.dwStreamID = outputStreamID;
_outputDataBuffer.dwStatus = 0;
_outputDataBuffer.pEvents = NULL;
_outputDataBuffer.pSample = nullptr;
mftProcessOutput = _pTransform->ProcessOutput(0, 1, &_outputDataBuffer, &processOutputStatus);
if (mftProcessOutput != MF_E_TRANSFORM_NEED_MORE_INPUT)
{
if (_outputDataBuffer.pSample) {
//CHECK_HR(_outputDataBuffer.pSample->SetSampleTime(mTimeStamp), "Error setting MFT sample time.\n");
//CHECK_HR(_outputDataBuffer.pSample->SetSampleDuration(VIDEO_FRAME_DURATION), "Error setting MFT sample duration.\n");
IMFMediaBuffer *buf = NULL;
DWORD bufLength;
CHECK_HR(_outputDataBuffer.pSample->ConvertToContiguousBuffer(&buf), "ConvertToContiguousBuffer failed.\n");
CHECK_HR(buf->GetCurrentLength(&bufLength), "Get buffer length failed.\n");
BYTE * rawBuffer = NULL;
fFrameSize = bufLength;
fDurationInMicroseconds = 0;
gettimeofday(&fPresentationTime, NULL);
buf->Lock(&rawBuffer, NULL, NULL);
memmove(fTo, rawBuffer, fFrameSize);
FramedSource::afterGetting(this);
buf->Unlock();
SafeRelease(&buf);
frameSent = true;
_lastSendAt = GetTickCount();
_outputDataBuffer.pSample->Release();
}
if (_outputDataBuffer.pEvents)
_outputDataBuffer.pEvents->Release();
}
//SafeRelease(&pBuffer);
//SafeRelease(&mftOutSample);
break;
}
}
break;
}
if (!frameSent)
{
envir().taskScheduler().triggerEvent(eventTriggerId, this);
}
return;
done:
printf("MediaFoundationH264LiveSource doGetNextFrame failed.\n");
envir().taskScheduler().triggerEvent(eventTriggerId, this);
}
Inicialize o método:
bool initialise()
{
HRESULT hr;
D3D11_TEXTURE2D_DESC desc = { 0 };
HDESK CurrentDesktop = nullptr;
CurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
if (!CurrentDesktop)
{
// We do not have access to the desktop so request a retry
return false;
}
// Attach desktop to this thread
bool DesktopAttached = SetThreadDesktop(CurrentDesktop) != 0;
CloseDesktop(CurrentDesktop);
CurrentDesktop = nullptr;
if (!DesktopAttached)
{
printf("SetThreadDesktop failed\n");
}
UINT32 activateCount = 0;
// h264 output
MFT_REGISTER_TYPE_INFO info = { MFMediaType_Video, MFVideoFormat_H264 };
UINT32 flags =
MFT_ENUM_FLAG_HARDWARE |
MFT_ENUM_FLAG_SORTANDFILTER;
// ------------------------------------------------------------------------
// Initialize D3D11
// ------------------------------------------------------------------------
// Driver types supported
D3D_DRIVER_TYPE DriverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
// Feature levels supported
D3D_FEATURE_LEVEL FeatureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_1
};
UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);
D3D_FEATURE_LEVEL FeatureLevel;
// Create device
for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
{
hr = D3D11CreateDevice(nullptr, DriverTypes[DriverTypeIndex], nullptr,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
FeatureLevels, NumFeatureLevels, D3D11_SDK_VERSION, &device, &FeatureLevel, &context);
if (SUCCEEDED(hr))
{
// Device creation success, no need to loop anymore
break;
}
}
CHECK_HR(hr, "Failed to create device");
// Create device manager
UINT resetToken;
hr = MFCreateDXGIDeviceManager(&resetToken, &deviceManager);
CHECK_HR(hr, "Failed to create DXGIDeviceManager");
hr = deviceManager->ResetDevice(device, resetToken);
CHECK_HR(hr, "Failed to assign D3D device to device manager");
// ------------------------------------------------------------------------
// Create surface
// ------------------------------------------------------------------------
desc.Format = DXGI_FORMAT_NV12;
desc.Width = surfaceWidth;
desc.Height = surfaceHeight;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.SampleDesc.Count = 1;
hr = device->CreateTexture2D(&desc, NULL, &surface);
CHECK_HR(hr, "Could not create surface");
hr = MFTEnumEx(
MFT_CATEGORY_VIDEO_ENCODER,
flags,
NULL,
&info,
&activateRaw,
&activateCount
);
CHECK_HR(hr, "Failed to enumerate MFTs");
CHECK(activateCount, "No MFTs found");
// Choose the first available encoder
activate = activateRaw[0];
for (UINT32 i = 0; i < activateCount; i++)
activateRaw[i]->Release();
// Activate
hr = activate->ActivateObject(IID_PPV_ARGS(&_pTransform));
CHECK_HR(hr, "Failed to activate MFT");
// Get attributes
hr = _pTransform->GetAttributes(&attributes);
CHECK_HR(hr, "Failed to get MFT attributes");
// Unlock the transform for async use and get event generator
hr = attributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
CHECK_HR(hr, "Failed to unlock MFT");
eventGen = _pTransform;
CHECK(eventGen, "Failed to QI for event generator");
// Get stream IDs (expect 1 input and 1 output stream)
hr = _pTransform->GetStreamIDs(1, &inputStreamID, 1, &outputStreamID);
if (hr == E_NOTIMPL)
{
inputStreamID = 0;
outputStreamID = 0;
hr = S_OK;
}
CHECK_HR(hr, "Failed to get stream IDs");
// ------------------------------------------------------------------------
// Configure hardware encoder MFT
// ------------------------------------------------------------------------
CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, reinterpret_cast<ULONG_PTR>(deviceManager.p)), "Failed to set device manager.\n");
// Set low latency hint
hr = attributes->SetUINT32(MF_LOW_LATENCY, TRUE);
CHECK_HR(hr, "Failed to set MF_LOW_LATENCY");
hr = MFCreateMediaType(&outputType);
CHECK_HR(hr, "Failed to create media type");
hr = outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
CHECK_HR(hr, "Failed to set MF_MT_MAJOR_TYPE on H264 output media type");
hr = outputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
CHECK_HR(hr, "Failed to set MF_MT_SUBTYPE on H264 output media type");
hr = outputType->SetUINT32(MF_MT_AVG_BITRATE, TARGET_AVERAGE_BIT_RATE);
CHECK_HR(hr, "Failed to set average bit rate on H264 output media type");
hr = MFSetAttributeSize(outputType, MF_MT_FRAME_SIZE, desc.Width, desc.Height);
CHECK_HR(hr, "Failed to set frame size on H264 MFT out type");
hr = MFSetAttributeRatio(outputType, MF_MT_FRAME_RATE, TARGET_FRAME_RATE, 1);
CHECK_HR(hr, "Failed to set frame rate on H264 MFT out type");
hr = outputType->SetUINT32(MF_MT_INTERLACE_MODE, 2);
CHECK_HR(hr, "Failed to set MF_MT_INTERLACE_MODE on H.264 encoder MFT");
hr = outputType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
CHECK_HR(hr, "Failed to set MF_MT_ALL_SAMPLES_INDEPENDENT on H.264 encoder MFT");
hr = _pTransform->SetOutputType(outputStreamID, outputType, 0);
CHECK_HR(hr, "Failed to set output media type on H.264 encoder MFT");
hr = MFCreateMediaType(&inputType);
CHECK_HR(hr, "Failed to create media type");
for (DWORD i = 0;; i++)
{
inputType = nullptr;
hr = _pTransform->GetInputAvailableType(inputStreamID, i, &inputType);
CHECK_HR(hr, "Failed to get input type");
hr = inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
CHECK_HR(hr, "Failed to set MF_MT_MAJOR_TYPE on H264 MFT input type");
hr = inputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
CHECK_HR(hr, "Failed to set MF_MT_SUBTYPE on H264 MFT input type");
hr = MFSetAttributeSize(inputType, MF_MT_FRAME_SIZE, desc.Width, desc.Height);
CHECK_HR(hr, "Failed to set MF_MT_FRAME_SIZE on H264 MFT input type");
hr = MFSetAttributeRatio(inputType, MF_MT_FRAME_RATE, TARGET_FRAME_RATE, 1);
CHECK_HR(hr, "Failed to set MF_MT_FRAME_RATE on H264 MFT input type");
hr = _pTransform->SetInputType(inputStreamID, inputType, 0);
CHECK_HR(hr, "Failed to set input type");
break;
}
CheckHardwareSupport();
CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, NULL), "Failed to process FLUSH command on H.264 MFT.\n");
CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL), "Failed to process BEGIN_STREAMING command on H.264 MFT.\n");
CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL), "Failed to process START_OF_STREAM command on H.264 MFT.\n");
return true;
done:
printf("MediaFoundationH264LiveSource initialisation failed.\n");
return false;
}
HRESULT CheckHardwareSupport()
{
IMFAttributes *attributes;
HRESULT hr = _pTransform->GetAttributes(&attributes);
UINT32 dxva = 0;
if (SUCCEEDED(hr))
{
hr = attributes->GetUINT32(MF_SA_D3D11_AWARE, &dxva);
}
if (SUCCEEDED(hr))
{
hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_H264, TRUE);
}
#if defined(CODECAPI_AVLowLatencyMode) // Win8 only
hr = _pTransform->QueryInterface(IID_PPV_ARGS(&mpCodecAPI));
if (SUCCEEDED(hr))
{
VARIANT var = { 0 };
// FIXME: encoder only
var.vt = VT_UI4;
var.ulVal = 0;
hr = mpCodecAPI->SetValue(&CODECAPI_AVEncMPVDefaultBPictureCount, &var);
var.vt = VT_BOOL;
var.boolVal = VARIANT_TRUE;
hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonLowLatency, &var);
hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonRealTime, &var);
hr = attributes->SetUINT32(CODECAPI_AVLowLatencyMode, TRUE);
if (SUCCEEDED(hr))
{
var.vt = VT_UI4;
var.ulVal = eAVEncCommonRateControlMode_Quality;
hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var);
// This property controls the quality level when the encoder is not using a constrained bit rate. The AVEncCommonRateControlMode property determines whether the bit rate is constrained.
VARIANT quality;
InitVariantFromUInt32(50, &quality);
hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonQuality, &quality);
}
}
#endif
return hr;
}
Comando ffplay:
ffplay -protocol_whitelist file,udp,rtp -i test.sdp -x 800 -y 600 -profile:v baseline
SDP:
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
t=0 0
c=IN IP4 127.0.0.1
m=video 1234 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1
Não sei o que estou perdendo, estou tentando consertar isso há quase uma semana sem nenhum progresso e tentei quase tudo o que pude. Além disso, os recursos online para codificar uma superfície DirectX como vídeo são muito limitados.
Qualquer ajuda seria apreciada.
Respostas:
É mais difícil do que parece.
Se você deseja usar o codificador como está fazendo, chamando a interface IMFTransform diretamente, é necessário converter os quadros RGB em NV12. Se você quer um bom desempenho, deve fazê-lo na GPU. É possível fazer isso com pixel shaders, renderizar 2 quadros, tamanho grande no DXGI_FORMAT_R8_UNORM renderizar alvo com brilho, meio tamanho no DXGI_FORMAT_R8G8_UNORM com cores e gravar dois pixel shaders para produzir valores NV12. Os dois destinos de renderização podem renderizar em dois planos da mesma textura NV12, mas somente desde o Windows 8.
Outro método é usar o escritor de coletor . Ele pode hospedar várias MFTs ao mesmo tempo, para que você possa fornecer texturas RGB na VRAM, o gravador de coletor as converterá primeiro em NV12 com uma MFT (que provavelmente será o hardware proprietário implementado pelo driver da GPU, assim como o codificador) e, em seguida, passar para o codificador MFT. É relativamente fácil codificar em um arquivo mp4, use a API MFCreateSinkWriterFromURL para criar o gravador. No entanto, é muito mais difícil obter amostras brutas do gravador de coletor, é necessário implementar um coletor de mídia personalizado, coletor de fluxo personalizado para o fluxo de vídeo e chamar MFCreateSinkWriterFromMediaSink para criar o gravador.
Tem mais.
Independentemente dos métodos de codificação, você não pode reutilizar texturas de quadros. Cada quadro obtido do DD, você deve criar uma nova textura e passá-la para o MF.
Os codificadores de vídeo esperam uma taxa de quadros constante. O DD não fornece isso a você, ele fornece um quadro sempre que algo muda na tela. Pode ser 144 FPS se você tiver um monitor de jogos, pode ser 2 FPS se a única alteração for o cursor piscando. Idealmente, você deve enviar quadros à MF a uma taxa de quadros constante, especificada no seu tipo de mídia de vídeo.
Se você deseja transmitir para a rede, na maioria das vezes você também precisa fornecer conjuntos de parâmetros. A menos que você esteja usando o codificador h265 de hardware Intel que não possui comentários da Intel , a MF fornece esses dados no atributo MF_MT_MPEG_SEQUENCE_HEADER do tipo de mídia, chamando SetCurrentMediaType na interface IMFMediaTypeHandler. Você pode implementar essa interface para ser notificado. Você só obterá esses dados depois de começar a codificar. Ou seja, se você usar um gravador de coletor, para o
IMFTransform
método é mais fácil, você deve obter oMF_E_TRANSFORM_STREAM_CHANGE
código doProcessOutput
método e, em seguida, chamarGetOutputAvailableType
para obter o tipo de mídia atualizado com esse blob mágico.fonte
DXGI_FORMAT_B8G8R8A8_UNORM
formato. Os MFTs de codificador H264 e h265 suportam apenas NV12 e outros dois, igualmente estranhos. Alguém tem que se converter. Você usa duplicação de área de trabalho; você já não pode suportar o Windows 7 com ele. Use um escritor de pia. Tenho certeza de que as MFTs de hardware da nVidia / Intel para converter RGB em NV12 são mais eficientes em termos de energia do que as ALUs de pixel shader, provavelmente implementadas puramente em hardware.Como
ffplay
está reclamando dos parâmetros do fluxo, eu diria que ele não pode pegar o SPS / PPS. Você não os definiu no seu SDP codificado - consulte o RFC-3984 e procuresprop-parameter-sets
. Um exemplo da RFC:Eu suponho fortemente que
ffplay
está esperando isso no SDP. Não me lembro de cor de como obter SPS / PPS do codificador de base de mídia, mas eles estão na carga útil de amostra e você precisa extraí-los consultando as unidades NAL adequadas ou no google como extrair os dados extras do codificador - o primeiro hit que obtive parecia promissor.fonte
Soonts fornece todas as coisas necessárias para resolver seu problema.
A primeira coisa que você precisa fazer é a conversão de formato entre DXGI_FORMAT_B8G8R8A8_UNORM e MFVideoFormat_NV12:
informações de conversão de formato
Eu acho que é melhor usar o shader para fazer a conversão de formato, porque todas as texturas permanecerão na GPU (melhor para o desempenho).
É o primeiro passo que você precisa fazer. Você terá outras pessoas para melhorar seu programa.
fonte