Je capture le bureau à l'aide de l'API DesktopDuplication et convertis les échantillons de RGBA en NV12 dans le GPU et les transfère au matériel MediaFoundation H264 MFT. Cela fonctionne très bien avec les graphiques Nvidia, ainsi qu'avec les encodeurs logiciels, mais échoue lorsque seul le matériel graphique Intel MFT est disponible. Le code fonctionne très bien sur la même machine graphique Intel si je reviens au logiciel MFT. J'ai également veillé à ce que l'encodage soit réellement effectué sur le matériel des machines graphiques Nvidia.
Sur les graphiques Intel, MFT renvoie MEError ( "erreur non spécifiée" ), qui ne se produit que juste après le chargement du premier échantillon, et les appels suivants à ProcessInput (lorsque le générateur d'événements déclenche METransformNeedInput) renvoie "L'appelé n'accepte actuellement aucune entrée supplémentaire" . Il est rare que MFT consomme quelques échantillons supplémentaires avant de renvoyer ces erreurs. Ce comportement est déroutant, j'alimente un échantillon uniquement lorsque le générateur d'événements déclenche METransformNeedInput de manière asynchrone via IMFAsyncCallback, et vérifie également correctement si METransformHaveOutput est déclenché dès qu'un échantillon est alimenté. Cela me déconcerte vraiment lorsque la même logique asynchrone fonctionne correctement avec les encodeurs logiciels MFT Nvidia et Microsoft.
Il existe également une question similaire non résolue dans le forum Intel lui-même. Mon code est similaire à celui mentionné dans le thread Intel, sauf pour le fait que je configure également le gestionnaire de périphériques d3d sur l'encodeur comme ci-dessous.
Et, il existe trois autres threads de débordement de pile signalant un problème similaire sans solution donnée ( encodeur MFTransform-> ProcessInput renvoie E_FAIL et comment créer IMFSample à partir de la texture D11 pour l'encodeur MFT Intel et MFT asynchrone n'envoie pas d'événement MFTransformHaveOutput (décodeur Intel Hardware MJPEG MFT) ). J'ai essayé toutes les options possibles sans amélioration à ce sujet.
Le code du convertisseur de couleur est extrait d'échantillons Intel Media SDK. J'ai également téléchargé mon code complet ici .
Méthode pour définir le gestionnaire 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;
}
Implémentation de rappel asynchrone:
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;
};
Générer un exemple de méthode:
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;
}
Créer un échantillon vidéo et une conversion des couleurs:
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;
}
Le pilote graphique Intel de la machine est déjà à jour.
Seul l'événement TransformNeedInput est déclenché tout le temps, mais l'encodeur se plaint de ne plus pouvoir accepter d'entrée. L'événement TransformHaveOutput n'a jamais été déclenché.
Problèmes similaires signalés sur les forums Intel et MSDN: 1) https://software.intel.com/en-us/forums/intel-media-sdk/topic/607189 2) https://social.msdn.microsoft.com/ Forums / SECURITY / en-US / fe051dd5-b522-4e4b-9cbb-2c06a5450e40 / imfsinkwriter-merit-validation-failed-for-mft-intel-quick-sync-video-h264-encoder-mft? Forum = mediafoundationdevelopment
Mise à jour: J'ai essayé de se moquer uniquement de la source d'entrée (en créant par programmation un échantillon NV12 de rectangle animé) en laissant tout le reste intact. Cette fois, l'encodeur Intel ne se plaint de rien, j'ai même des échantillons de sortie. Sauf que la vidéo de sortie de l'encodeur Intel est déformée alors que l'encodeur Nvidia fonctionne parfaitement bien.
De plus, je reçois toujours l'erreur ProcessInput pour ma source NV12 d'origine avec l'encodeur Intel. Je n'ai aucun problème avec Nvidia MFT et les encodeurs logiciels.
Sortie du matériel Intel MFT: (Veuillez regarder la sortie de l'encodeur Nvidia)
Sortie du matériel Nvidia MFT:
Statistiques d'utilisation des graphiques Nvidia:
Statistiques d'utilisation des graphiques Intel (je ne comprends pas pourquoi le moteur GPU est affiché comme décodage vidéo):
ProcessInput
.