Estoy capturando el escritorio usando DesktopDuplication API y convirtiendo las muestras de RGBA a NV12 en GPU y alimentando el mismo al hardware MediaFoundation H264 MFT. Esto funciona bien con los gráficos de Nvidia, y también con los codificadores de software, pero falla cuando solo está disponible el hardware de gráficos Intel MFT. El código funciona bien en la misma máquina de gráficos Intel si recurro a Software MFT. También me aseguré de que la codificación se realice en hardware en máquinas gráficas Nvidia.
En los gráficos Intel, MFT devuelve MEError ( "Error no especificado" ), que ocurre solo después de que se alimenta la primera muestra, y las llamadas posteriores a ProcessInput (cuando el generador de eventos activa METransformNeedInput) devuelve "La persona que llama actualmente no acepta más entradas" . Es raro que MFT consuma algunas muestras más antes de devolver estos errores. Este comportamiento es confuso, estoy alimentando una muestra solo cuando el generador de eventos activa METransformNeedInput de forma asincrónica a través de IMFAsyncCallback, y también compruebo correctamente si METransformHaveOutput se activa tan pronto como se alimenta una muestra. Esto realmente me desconcierta cuando la misma lógica asincrónica funciona bien con los codificadores de hardware MFT y Microsoft de Nvidia.
También hay una pregunta similar sin resolver en el foro de Intel. Mi código es similar al mencionado en el subproceso de Intel, excepto por el hecho de que también estoy configurando el administrador de dispositivos d3d en el codificador como se muestra a continuación.
Y, hay otros tres subprocesos de desbordamiento de pila que informan un problema similar sin una solución dada ( codificador MFTransform-> ProcessInput devuelve E_FAIL y Cómo crear IMFSample a partir de la textura D11 para el codificador Intel MFT y MFT asíncrono no envía el evento MFTransformHaveOutput (Decodificador Intel Hardware MJPEG MFT) ). He probado todas las opciones posibles sin mejorar esto.
El código del convertidor de color se toma de muestras de Intel Media SDK. También he subido mi código completo aquí .
Método para configurar el administrador d3d:
void SetD3dManager() {
HRESULT hr = S_OK;
if (!deviceManager) {
// Create device manager
hr = MFCreateDXGIDeviceManager(&resetToken, &deviceManager);
}
if (SUCCEEDED(hr))
{
if (!pD3dDevice) {
pD3dDevice = GetDeviceDirect3D(0);
}
}
if (pD3dDevice) {
// NOTE: Getting ready for multi-threaded operation
const CComQIPtr<ID3D10Multithread> pMultithread = pD3dDevice;
pMultithread->SetMultithreadProtected(TRUE);
hr = deviceManager->ResetDevice(pD3dDevice, resetToken);
CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, reinterpret_cast<ULONG_PTR>(deviceManager.p)), "Failed to set device manager.");
}
else {
cout << "Failed to get d3d device";
}
}
Getd3ddevice:
CComPtr<ID3D11Device> GetDeviceDirect3D(UINT idxVideoAdapter)
{
// Create DXGI factory:
CComPtr<IDXGIFactory1> dxgiFactory;
DXGI_ADAPTER_DESC1 dxgiAdapterDesc;
// Direct3D feature level codes and names:
struct KeyValPair { int code; const char* name; };
const KeyValPair d3dFLevelNames[] =
{
KeyValPair{ D3D_FEATURE_LEVEL_9_1, "Direct3D 9.1" },
KeyValPair{ D3D_FEATURE_LEVEL_9_2, "Direct3D 9.2" },
KeyValPair{ D3D_FEATURE_LEVEL_9_3, "Direct3D 9.3" },
KeyValPair{ D3D_FEATURE_LEVEL_10_0, "Direct3D 10.0" },
KeyValPair{ D3D_FEATURE_LEVEL_10_1, "Direct3D 10.1" },
KeyValPair{ D3D_FEATURE_LEVEL_11_0, "Direct3D 11.0" },
KeyValPair{ D3D_FEATURE_LEVEL_11_1, "Direct3D 11.1" },
};
// Feature levels for Direct3D support
const D3D_FEATURE_LEVEL d3dFeatureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
constexpr auto nFeatLevels = static_cast<UINT> ((sizeof d3dFeatureLevels) / sizeof(D3D_FEATURE_LEVEL));
CComPtr<IDXGIAdapter1> dxgiAdapter;
D3D_FEATURE_LEVEL featLevelCodeSuccess;
CComPtr<ID3D11Device> d3dDx11Device;
std::wstring_convert<std::codecvt_utf8<wchar_t>> transcoder;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory));
CHECK_HR(hr, "Failed to create DXGI factory");
// Get a video adapter:
dxgiFactory->EnumAdapters1(idxVideoAdapter, &dxgiAdapter);
// Get video adapter description:
dxgiAdapter->GetDesc1(&dxgiAdapterDesc);
CHECK_HR(hr, "Failed to retrieve DXGI video adapter description");
std::cout << "Selected DXGI video adapter is \'"
<< transcoder.to_bytes(dxgiAdapterDesc.Description) << '\'' << std::endl;
// Create Direct3D device:
hr = D3D11CreateDevice(
dxgiAdapter,
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
(0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
d3dFeatureLevels,
nFeatLevels,
D3D11_SDK_VERSION,
&d3dDx11Device,
&featLevelCodeSuccess,
nullptr
);
// Might have failed for lack of Direct3D 11.1 runtime:
if (hr == E_INVALIDARG)
{
// Try again without Direct3D 11.1:
hr = D3D11CreateDevice(
dxgiAdapter,
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
(0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
d3dFeatureLevels + 1,
nFeatLevels - 1,
D3D11_SDK_VERSION,
&d3dDx11Device,
&featLevelCodeSuccess,
nullptr
);
}
// Get name of Direct3D feature level that succeeded upon device creation:
std::cout << "Hardware device supports " << std::find_if(
d3dFLevelNames,
d3dFLevelNames + nFeatLevels,
[featLevelCodeSuccess](const KeyValPair& entry)
{
return entry.code == featLevelCodeSuccess;
}
)->name << std::endl;
done:
return d3dDx11Device;
}
Implementación de devolución de llamada asíncrona:
struct EncoderCallbacks : IMFAsyncCallback
{
EncoderCallbacks(IMFTransform* encoder)
{
TickEvent = CreateEvent(0, FALSE, FALSE, 0);
_pEncoder = encoder;
}
~EncoderCallbacks()
{
eventGen = nullptr;
CloseHandle(TickEvent);
}
bool Initialize() {
_pEncoder->QueryInterface(IID_PPV_ARGS(&eventGen));
if (eventGen) {
eventGen->BeginGetEvent(this, 0);
return true;
}
return false;
}
// dummy IUnknown impl
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { return E_NOTIMPL; }
virtual ULONG STDMETHODCALLTYPE AddRef(void) override { return 1; }
virtual ULONG STDMETHODCALLTYPE Release(void) override { return 1; }
virtual HRESULT STDMETHODCALLTYPE GetParameters(DWORD* pdwFlags, DWORD* pdwQueue) override
{
// we return immediately and don't do anything except signaling another thread
*pdwFlags = MFASYNC_SIGNAL_CALLBACK;
*pdwQueue = MFASYNC_CALLBACK_QUEUE_IO;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult* pAsyncResult) override
{
IMFMediaEvent* event = 0;
eventGen->EndGetEvent(pAsyncResult, &event);
if (event)
{
MediaEventType type;
event->GetType(&type);
switch (type)
{
case METransformNeedInput: InterlockedIncrement(&NeedsInput); break;
case METransformHaveOutput: InterlockedIncrement(&HasOutput); break;
}
event->Release();
SetEvent(TickEvent);
}
eventGen->BeginGetEvent(this, 0);
return S_OK;
}
CComQIPtr<IMFMediaEventGenerator> eventGen = nullptr;
HANDLE TickEvent;
IMFTransform* _pEncoder = nullptr;
unsigned int NeedsInput = 0;
unsigned int HasOutput = 0;
};
Generar método de muestra:
bool GenerateSampleAsync() {
DWORD processOutputStatus = 0;
HRESULT mftProcessOutput = S_OK;
bool frameSent = false;
// Create sample
CComPtr<IMFSample> currentVideoSample = nullptr;
MFT_OUTPUT_STREAM_INFO StreamInfo;
// wait for any callback to come in
WaitForSingleObject(_pEventCallback->TickEvent, INFINITE);
while (_pEventCallback->NeedsInput) {
if (!currentVideoSample) {
(pDesktopDuplication)->releaseBuffer();
(pDesktopDuplication)->cleanUpCurrentFrameObjects();
bool bTimeout = false;
if (pDesktopDuplication->GetCurrentFrameAsVideoSample((void**)& currentVideoSample, waitTime, bTimeout, deviceRect, deviceRect.Width(), deviceRect.Height())) {
prevVideoSample = currentVideoSample;
}
// Feed the previous sample to the encoder in case of no update in display
else {
currentVideoSample = prevVideoSample;
}
}
if (currentVideoSample)
{
InterlockedDecrement(&_pEventCallback->NeedsInput);
_frameCount++;
CHECK_HR(currentVideoSample->SetSampleTime(mTimeStamp), "Error setting the video sample time.");
CHECK_HR(currentVideoSample->SetSampleDuration(VIDEO_FRAME_DURATION), "Error getting video sample duration.");
CHECK_HR(_pTransform->ProcessInput(inputStreamID, currentVideoSample, 0), "The resampler H264 ProcessInput call failed.");
mTimeStamp += VIDEO_FRAME_DURATION;
}
}
while (_pEventCallback->HasOutput) {
CComPtr<IMFSample> mftOutSample = nullptr;
CComPtr<IMFMediaBuffer> pOutMediaBuffer = nullptr;
InterlockedDecrement(&_pEventCallback->HasOutput);
CHECK_HR(_pTransform->GetOutputStreamInfo(outputStreamID, &StreamInfo), "Failed to get output stream info from H264 MFT.");
CHECK_HR(MFCreateSample(&mftOutSample), "Failed to create MF sample.");
CHECK_HR(MFCreateMemoryBuffer(StreamInfo.cbSize, &pOutMediaBuffer), "Failed to create memory buffer.");
CHECK_HR(mftOutSample->AddBuffer(pOutMediaBuffer), "Failed to add sample to buffer.");
MFT_OUTPUT_DATA_BUFFER _outputDataBuffer;
memset(&_outputDataBuffer, 0, sizeof _outputDataBuffer);
_outputDataBuffer.dwStreamID = outputStreamID;
_outputDataBuffer.dwStatus = 0;
_outputDataBuffer.pEvents = nullptr;
_outputDataBuffer.pSample = mftOutSample;
mftProcessOutput = _pTransform->ProcessOutput(0, 1, &_outputDataBuffer, &processOutputStatus);
if (mftProcessOutput != MF_E_TRANSFORM_NEED_MORE_INPUT)
{
if (_outputDataBuffer.pSample) {
CComPtr<IMFMediaBuffer> buf = NULL;
DWORD bufLength;
CHECK_HR(_outputDataBuffer.pSample->ConvertToContiguousBuffer(&buf), "ConvertToContiguousBuffer failed.");
if (buf) {
CHECK_HR(buf->GetCurrentLength(&bufLength), "Get buffer length failed.");
BYTE* rawBuffer = NULL;
fFrameSize = bufLength;
fDurationInMicroseconds = 0;
gettimeofday(&fPresentationTime, NULL);
buf->Lock(&rawBuffer, NULL, NULL);
memmove(fTo, rawBuffer, fFrameSize > fMaxSize ? fMaxSize : fFrameSize);
bytesTransfered += bufLength;
FramedSource::afterGetting(this);
buf->Unlock();
frameSent = true;
}
}
if (_outputDataBuffer.pEvents)
_outputDataBuffer.pEvents->Release();
}
else if (MF_E_TRANSFORM_STREAM_CHANGE == mftProcessOutput) {
// some encoders want to renegotiate the output format.
if (_outputDataBuffer.dwStatus & MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE)
{
CComPtr<IMFMediaType> pNewOutputMediaType = nullptr;
HRESULT res = _pTransform->GetOutputAvailableType(outputStreamID, 1, &pNewOutputMediaType);
res = _pTransform->SetOutputType(0, pNewOutputMediaType, 0);//setting the type again
CHECK_HR(res, "Failed to set output type during stream change");
}
}
else {
HandleFailure();
}
}
return frameSent;
}
Crear muestra de video y conversión de color:
bool GetCurrentFrameAsVideoSample(void **videoSample, int waitTime, bool &isTimeout, CRect &deviceRect, int surfaceWidth, int surfaceHeight)
{
FRAME_DATA currentFrameData;
m_LastErrorCode = m_DuplicationManager.GetFrame(¤tFrameData, waitTime, &isTimeout);
if (!isTimeout && SUCCEEDED(m_LastErrorCode)) {
m_CurrentFrameTexture = currentFrameData.Frame;
if (!pDstTexture) {
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
desc.Format = DXGI_FORMAT_NV12;
desc.Width = surfaceWidth;
desc.Height = surfaceHeight;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.SampleDesc.Count = 1;
desc.CPUAccessFlags = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
m_LastErrorCode = m_Id3d11Device->CreateTexture2D(&desc, NULL, &pDstTexture);
}
if (m_CurrentFrameTexture && pDstTexture) {
// Copy diff area texels to new temp texture
//m_Id3d11DeviceContext->CopySubresourceRegion(pNewTexture, D3D11CalcSubresource(0, 0, 1), 0, 0, 0, m_CurrentFrameTexture, 0, NULL);
HRESULT hr = pColorConv->Convert(m_CurrentFrameTexture, pDstTexture);
if (SUCCEEDED(hr)) {
CComPtr<IMFMediaBuffer> pMediaBuffer = nullptr;
MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), pDstTexture, 0, FALSE, (IMFMediaBuffer**)&pMediaBuffer);
if (pMediaBuffer) {
CComPtr<IMF2DBuffer> p2DBuffer = NULL;
DWORD length = 0;
(((IMFMediaBuffer*)pMediaBuffer))->QueryInterface(__uuidof(IMF2DBuffer), reinterpret_cast<void**>(&p2DBuffer));
p2DBuffer->GetContiguousLength(&length);
(((IMFMediaBuffer*)pMediaBuffer))->SetCurrentLength(length);
//MFCreateVideoSampleFromSurface(NULL, (IMFSample**)videoSample);
MFCreateSample((IMFSample * *)videoSample);
if (videoSample) {
(*((IMFSample **)videoSample))->AddBuffer((((IMFMediaBuffer*)pMediaBuffer)));
}
return true;
}
}
}
}
return false;
}
El controlador de gráficos Intel en la máquina ya está actualizado.
Solo el evento TransformNeedInput se activa todo el tiempo, pero el codificador se queja de que no puede aceptar más entradas. El evento TransformHaveOutput nunca se ha activado.
Problemas similares reportados en foros de intel & msdn: 1) https://software.intel.com/en-us/forums/intel-media-sdk/topic/607189 2) https://social.msdn.microsoft.com/ Foros / SEGURIDAD / es-ES / fe051dd5-b522-4e4b-9cbb-2c06a5450e40 / imfsinkwriter-merit-validation-failure-for-mft-intel-quick-sync-video-h264-encoder-mft? Forum = mediafoundationdevelopment
Actualización: he tratado de burlarme solo de la fuente de entrada (mediante la creación programática de una muestra de animación NV12 rectángulo) dejando todo lo demás intacto. Esta vez, el codificador Intel no se queja de nada, incluso tengo muestras de salida. Excepto el hecho de que el video de salida del codificador Intel está distorsionado, mientras que el codificador Nvidia funciona perfectamente bien.
Además, sigo recibiendo el error ProcessInput para mi fuente NV12 original con codificador Intel. No tengo problemas con Nvidia MFT y los codificadores de software.
Salida del hardware Intel MFT: (Mire la salida del codificador Nvidia)
Salida de hardware Nvidia MFT:
Estadísticas de uso de gráficos de Nvidia:
Estadísticas de uso de gráficos Intel (no entiendo por qué el motor GPU se muestra como decodificación de video):
ProcessInput
.Respuestas:
Miré tu código.
Según su publicación, sospecho que hay un problema con el procesador de video Intel.
Mi sistema operativo es Win7, así que decido probar el comportamiento del procesador de video con un dispositivo D3D9 en mi tarjeta Nvidia y luego con un Intel HD Graphics 4000.
Supongo que las capacidades del procesador de video se comportarán de la misma manera para un dispositivo D3D9 que para un dispositivo D3D11. Por supuesto, será necesario verificar.
Así que hice este programa para verificar: https://github.com/mofo7777/DirectXVideoScreen (ver D3D9VideoProcessor subproyecto)
Parece que no verifica suficientes cosas sobre las capacidades del procesador de video.
Con IDXVAHD_Device :: GetVideoProcessorDeviceCaps, esto es lo que compruebo:
DXVAHD_VPDEVCAPS.MaxInputStreams> 0
DXVAHD_VPDEVCAPS.VideoProcessorCount> 0
DXVAHD_VPDEVCAPS.OutputFormatCount> 0
DXVAHD_VPDEVCAPS.InputFormatCount> 0
DXVAHD_VPDEVCAPS.InputPool == D3DPOOL_DEFAULT
También verifico el formato de entrada y salida compatible con IDXVAHD_Device :: GetVideoProcessorOutputFormats e IDXVAHD_Device :: GetVideoProcessorInputFormats.
Aquí es donde encontré una diferencia entre la GPU Nvidia y la GPU Intel.
NVIDIA: formato de 4 salidas
INTEL: formato de salida 3
En Intel HD Graphics 4000, no hay soporte para el formato de salida NV12.
También para que el programa funcione correctamente, necesito configurar el estado de transmisión antes de usar VideoProcessBltHD:
Para D3D11:
ID3D11VideoProcessorEnumerator :: GetVideoProcessorCaps == IDXVAHD_Device :: GetVideoProcessorDeviceCaps
(D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT) ID3D11VideoProcessorEnumerator :: CheckVideoProcessorFormat == IDXVAHD_Device :: GetVideoProcessorOutputFormats
(D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT) ID3D11VideoProcessorEnumerator :: CheckVideoProcessorFormat == IDXVAHD_Device :: GetVideoProcessorInputFormats
ID3D11VideoContext :: (...) == IDXVAHD_VideoProcessor :: SetVideoProcessStreamState
¿Podría primero verificar las capacidades del procesador de video de su GPU? ¿Ves la misma diferencia que yo?
Esto es lo primero que necesitamos saber, y parece que su programa no verifica esto, por lo que he visto en su proyecto github.
fuente
Como se mencionó en la publicación, el generador de eventos de Transform devolvió el error MEError ("Error no especificado") inmediatamente después de alimentar la primera muestra de entrada en el hardware de Intel, y otras llamadas simplemente devolvieron "Transformar necesita más entrada", pero no se produjo ninguna salida . Sin embargo, el mismo código funcionó bien en las máquinas Nvidia. Después de experimentar e investigar mucho, descubrí que estaba creando demasiadas instancias del dispositivo D3d11. En mi caso, creé de 2 a 3 dispositivos para captura, conversión de color y codificador de hardware, respectivamente. Mientras que, simplemente podría haber reutilizado una sola instancia de D3dDevice. Sin embargo, la creación de varias instancias D3d11Device podría funcionar en máquinas de gama alta. Esto no está documentado en ninguna parte. No pude encontrar ni una pista de las causas del error "MEError". No se menciona en ninguna parte.
La reutilización de la instancia D3D11Device resolvió el problema. Publicar esta solución ya que podría ser útil para las personas que enfrentan el mismo problema que la mía.
fuente