Estou capturando a área de trabalho usando a API DesktopDuplication e convertendo as amostras de RGBA para NV12 na GPU e alimentando o mesmo no hardware MediaFoundation H264 MFT. Isso funciona bem com gráficos Nvidia e também com codificadores de software, mas falha quando apenas o hardware gráfico Intel MFT está disponível. O código funciona bem na mesma máquina gráfica Intel se eu recorrer ao Software MFT. Também assegurei que a codificação seja realmente feita em hardware nas máquinas gráficas da Nvidia.
Nos gráficos Intel, o MFT retorna MEError ( "Erro não especificado" ), que ocorre apenas após a alimentação da primeira amostra, e as chamadas subseqüentes a ProcessInput (quando o gerador de eventos aciona METransformNeedInput) retornam "O receptor atualmente não está aceitando mais informações" . É raro o MFT consumir mais algumas amostras antes de retornar esses erros. Esse comportamento é confuso. Estou alimentando uma amostra apenas quando o gerador de eventos aciona METransformNeedInput de forma assíncrona por IMFAsyncCallback e também verifico corretamente se METransformHaveOutput é acionado assim que uma amostra é alimentada. Isso realmente me deixa desconcertado quando a mesma lógica assíncrona funciona bem com os codificadores de hardware Nvidia MFT e Microsoft.
Há também uma pergunta não resolvida semelhante no próprio fórum da intel. Meu código é semelhante ao mencionado no segmento Intel, exceto pelo fato de eu também estar configurando o gerenciador de dispositivos d3d para o codificador, como abaixo.
E existem três outros threads de estouro de pilha que relatam um problema semelhante sem solução fornecida (o codificador MFTransform-> ProcessInput retorna E_FAIL & Como criar um exemplo de IMFS a partir da textura D11 para o codificador Intel MFT e o MFT assíncrono não está enviando o evento MFTransformHaveOutput (decodificador MJPEG de hardware da Intel) MFT) ). Eu tentei todas as opções possíveis sem melhorar isso.
O código do conversor de cores é obtido a partir de amostras da intel media sdk. Também enviei meu código completo aqui .
Método para definir o gerenciador do 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;
}
Implementação de retorno de chamada assí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;
};
Gerar método de amostra:
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;
}
Crie amostra de vídeo e conversão de cores:
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;
}
O driver gráfico da intel na máquina já está atualizado.
Somente o evento TransformNeedInput está sendo acionado o tempo todo, mas o codificador reclama que não pôde aceitar mais nenhuma entrada. O evento TransformHaveOutput nunca foi acionado.
Problemas semelhantes relatados nos fóruns intel & msdn: 1) https://software.intel.com/en-us/forums/intel-media-sdk/topic/607189 2) https://social.msdn.microsoft.com/ Forums / SECURITY / pt-BR / fe051dd5-b522-4e4b-9cbb-2c06a5450e40 / imfsinkwriter-validação de mérito falhou-para-mft-intel-quick-sync-video-h264-encoder-mft? Forum = mediafoundationdevelopment
Atualização: Eu tentei zombar apenas da fonte de entrada (criando programaticamente um exemplo de retângulo NV12 animado), deixando tudo intocado. Desta vez, o codificador intel não reclama nada, eu até tenho amostras de saída. Exceto o fato de que o vídeo de saída do codificador intel está distorcido, enquanto o codificador Nvidia funciona perfeitamente.
Além disso, ainda estou recebendo o erro ProcessInput da minha fonte NV12 original com o codificador intel. Não tenho problemas com os codificadores Nvidia MFT e software.
Saída da MFT de hardware Intel: (consulte a saída do codificador Nvidia)
Saída do hardware da Nvidia MFT:
Estatísticas de uso de gráficos da Nvidia:
Estatísticas de uso de gráficos da Intel (não entendo por que o mecanismo GPU é exibido como decodificação de vídeo):
ProcessInput
.Respostas:
Eu olhei para o seu código.
De acordo com o seu post, suspeito de um problema no processador de vídeo Intel.
Meu sistema operacional é o Win7, por isso decido testar o comportamento do processador de vídeo com um D3D9Device na minha placa Nvidia e depois em um Intel HD Graphics 4000.
Suponho que os recursos do processador de vídeo se comportem da mesma maneira para um D3D9Device e para um D3D11Device. Claro que será necessário verificar.
Então, eu fiz este programa para verificar: https://github.com/mofo7777/DirectXVideoScreen (consulte o subprojeto D3D9VideoProcessor)
Parece que você não verifica coisas suficientes sobre os recursos do processador de vídeo.
Com IDXVAHD_Device :: GetVideoProcessorDeviceCaps, aqui está o que eu verifico:
DXVAHD_VPDEVCAPS.MaxInputStreams> 0
DXVAHD_VPDEVCAPS.VideoProcessorCount> 0
DXVAHD_VPDEVCAPS.OutputFormatCount> 0
DXVAHD_VPDEVCAPS.InputFormatCount> 0
DXVAHD_VPDEVCAPS.InputPool == D3DPOOL_DEFAULT
Também verifico o formato de entrada e saída suportado por IDXVAHD_Device :: GetVideoProcessorOutputFormats e IDXVAHD_Device :: GetVideoProcessorInputFormats.
Foi aqui que encontrei uma diferença entre a Nvidia GPU e a Intel GPU.
NVIDIA: formato de 4 saídas
Formato de saída INTEL: 3
Na Intel HD Graphics 4000, não há suporte para o formato de saída NV12.
Também para o programa funcionar corretamente, preciso configurar o estado do fluxo antes de usar o 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
Você pode verificar primeiro os recursos do processador de vídeo da sua GPU. Você vê a mesma diferença que eu vejo?
Esta é a primeira coisa que precisamos saber, e parece que seu programa não verifica isso, pelo que vi no seu projeto do github.
fonte
Conforme mencionado no post, o erro MEError ("Erro não especificado") foi retornado pelo gerador de eventos do Transform imediatamente após alimentar a primeira amostra de entrada no hardware Intel e, mais chamadas acabaram de retornar "Transform Need more input", mas nenhuma saída foi produzida . O mesmo código funcionou bem em máquinas Nvidia. Depois de experimentar e pesquisar bastante, descobri que estava criando muitas instâncias do D3d11Device. No meu caso, criei 2 a 3 dispositivos para captura, conversão de cores e codificador de hardware, respectivamente. Visto que eu poderia simplesmente ter reutilizado uma única instância D3dDevice. Porém, a criação de várias instâncias do D3d11Device pode funcionar em máquinas de última geração. Isso não está documentado em nenhum lugar. Não consegui encontrar sequer uma pista para as causas do erro "MEError". Não é mencionado em lugar algum.
Reutilizar a instância D3D11Device resolveu o problema. Publicar esta solução, pois pode ser útil para pessoas que enfrentam o mesmo problema que o meu.
fonte