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
.Réponses:
J'ai regardé ton code.
Selon votre article, je soupçonne un problème de processeur vidéo Intel.
Mon système d'exploitation est Win7, je décide donc de tester le comportement du processeur vidéo avec un D3D9Device sur ma carte Nvidia, puis sur un Intel HD Graphics 4000.
Je suppose que les capacités du processeur vidéo se comporteront de la même manière pour un D3D9Device que pour un D3D11Device. Bien sûr, il faudra vérifier.
J'ai donc fait ce programme pour vérifier: https://github.com/mofo7777/DirectXVideoScreen (voir le sous-projet D3D9VideoProcessor)
Il semble que vous ne vérifiez pas suffisamment les capacités du processeur vidéo.
Avec IDXVAHD_Device :: GetVideoProcessorDeviceCaps, voici ce que je vérifie:
DXVAHD_VPDEVCAPS.MaxInputStreams> 0
DXVAHD_VPDEVCAPS.VideoProcessorCount> 0
DXVAHD_VPDEVCAPS.OutputFormatCount> 0
DXVAHD_VPDEVCAPS.InputFormatCount> 0
DXVAHD_VPDEVCAPS.InputPool == D3DPOOL_DEFAULT
Je vérifie également le format d'entrée et de sortie pris en charge avec IDXVAHD_Device :: GetVideoProcessorOutputFormats et IDXVAHD_Device :: GetVideoProcessorInputFormats.
C'est là que j'ai trouvé une différence entre le GPU Nvidia et le GPU Intel.
NVIDIA: 4 formats de sortie
INTEL: 3 formats de sortie
Sur Intel HD Graphics 4000, le format de sortie NV12 n'est pas pris en charge.
De plus, pour que le programme fonctionne correctement, j'ai besoin de configurer l'état du flux avant d'utiliser VideoProcessBltHD:
Pour 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
Pourriez-vous d'abord vérifier les capacités du processeur vidéo de votre GPU. Voyez-vous la même différence que je vois?
C'est la première chose que nous devons savoir, et il semble que votre programme ne vérifie pas cela, d'après ce que j'ai vu sur votre projet github.
la source
Comme mentionné dans la publication, l'erreur MEError ("erreur non spécifiée") a été renvoyée par le générateur d'événements de Transform immédiatement après avoir alimenté le premier échantillon d'entrée sur le matériel Intel et, d'autres appels viennent de renvoyer "Transform Need more input", mais aucune sortie n'a été produite . Le même code a bien fonctionné sur les machines Nvidia. Après avoir expérimenté et recherché beaucoup, je me suis rendu compte que je créais trop d'instances de D3d11Device.Dans mon cas, j'ai créé 2 à 3 appareils pour la capture, la conversion des couleurs et l'encodeur matériel respectivement. Alors que j'aurais pu simplement réutiliser une seule instance D3dDevice. La création de plusieurs instances D3d11Device peut cependant fonctionner sur des machines haut de gamme. Cela n'est documenté nulle part. Je n'ai pas pu trouver même un indice pour les causes de l'erreur "MEError". Cela n'est mentionné nulle part.
La réutilisation de l'instance D3D11Device a résolu le problème. Publier cette solution car elle pourrait être utile pour les personnes confrontées au même problème que le mien.
la source