5

Je suis en train d'écrire un décodeur h264 à accélération matérielle en utilisant Source Reader de Media Foundation, mais j'ai rencontré un problème. J'ai suivi this tutorial et pris en charge avec des exemples de Windows SDK Media Foundation. Mon application semble fonctionner correctement lorsque l'accélération matérielle est désactivée, mais elle ne fournit pas les performances dont j'ai besoin. Lorsque j'active l'accélération en passant un IMFDXGIDeviceManager à IMFAttributes utilisé pour créer le lecteur, les choses se compliquent. Si je crée le ID3D11Device en utilisant un pilote D3D_DRIVER_TYPE_NULL, l'application fonctionne bien et les trames sont traitées plus rapidement qu'en mode logiciel, mais à en juger par l'utilisation du processeur et du GPU, elle fait encore la majorité du traitement sur le processeur. En revanche, lorsque je crée le ID3D11Device à l'aide d'un pilote D3D_DRIVER_TYPE_HARDWARE et exécutez l'application, l'une de ces quatre choses peut se produire.Comment utiliser correctement un Media Foundation Source Reader accéléré pour décoder une vidéo?

  1. Je ne reçois que d'un nombre imprévisible de cadres (en général 1-3) avant IMFMediaBuffer::Lock fonction retourne 0x887a0005 qui est décrit comme « l'utilisation GetDeviceRemovedReason pour déterminer l'action appropriée instance de dispositif de GPU a été suspendu. ». Quand j'appelle ID3D11Device::GetDeviceRemovedReason, je reçois 0x887a0020 qui est décrit comme "Le pilote a rencontré un problème et a été mis dans l'état supprimé de l'appareil" ce qui n'est pas aussi utile que je le souhaite.

  2. L'application se bloque dans une DLL externe sur l'appel IMFMediaBuffer::Lock. Il semble que la DLL dépend du GPU utilisé. Pour Intel GPU intégré c'est igd10iumd32.dll et pour GPU mobile Nvidia c'est mfplat.dll. Le message pour ce plantage particulier est comme suit: "Exception levée à 0x53C6DB8C (mfplat.dll) dans decoder_ tester.exe: 0xC0000005: violation d'accès l'emplacement de lecture 0x00000024". Les adresses sont différentes entre les exécutions et parfois elles impliquent la lecture, parfois l'écriture.

  3. Le pilote graphique cesse de répondre, le système se bloque pendant un court laps de temps, puis l'application se bloque comme au point 2 ou finitions comme au point 1.

  4. L'application fonctionne très bien et traite tous les cadres avec du matériel accélération.

La plupart du temps, il est 1 ou 2, rarement 3 ou 4.


Voici ce que l'utilisation du CPU/GPU est comme lors du traitement sans étranglant dans différents modes sur ma machine (Intel Core i5-6500 avec HD Graphics 530, Windows 10 Pro).

  • NULL - CPU: ~ 90%, GPU: ~ 15%
  • MATÉRIEL - CPU: ~ 15%, GPU: ~ 60%
  • LOGICIEL - CPU: ~ 40%, GPU: ~ 7 %

J'ai testé l'application sur trois machines. Tous avaient des processeurs graphiques Intel intégrés (HD 4400, HD 4600, HD 530). L'un d'eux avait également un GPU dédié Nvidia commutable (GF 840M). Il bahave identique sur chacun d'eux, la seule différence est qu'il se bloque dans une DLL différente lorsque le GPU de Nvidia est utilisé.


Je n'ai aucune expérience avec COM ou DirectX, mais tout cela est incohérent et imprévisible, il ressemble à une me corruption de mémoire. Pourtant, je ne sais pas où je fais l'erreur. Pourriez-vous m'aider s'il vous plaît à trouver ce que je fais mal?

L'exemple de code minimal que j'ai pu trouver est ci-dessous. J'utilise Visual Studio Professional 2015 pour le compiler en tant que projet C++. J'ai préparé des définitions pour activer l'accélération matérielle et sélectionner le pilote matériel. Commentez-les pour changer le comportement. En outre, le code attend this video file être présent dans le répertoire du projet.

#include <iostream> 
#include <string> 
#include <atlbase.h> 
#include <d3d11.h> 
#include <mfapi.h> 
#include <mfidl.h> 
#include <mfreadwrite.h> 
#include <windows.h> 

#pragma comment(lib, "d3d11.lib") 
#pragma comment(lib, "mf.lib") 
#pragma comment(lib, "mfplat.lib") 
#pragma comment(lib, "mfreadwrite.lib") 
#pragma comment(lib, "mfuuid.lib") 

#define ENABLE_HW_ACCELERATION 
#define ENABLE_HW_DRIVER 

void handle_result(HRESULT hr) 
{ 
    if (SUCCEEDED(hr)) 
     return; 

    WCHAR message[512]; 

    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, hr, 
     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), message, ARRAYSIZE(message), nullptr); 

    printf("%ls", message); 
    abort(); 
} 

int main(int argc, char** argv) 
{ 
    handle_result(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); 
    handle_result(MFStartup(MF_VERSION)); 

    { 
     CComPtr<IMFAttributes> attributes; 

     handle_result(MFCreateAttributes(&attributes, 3)); 

#if defined(ENABLE_HW_ACCELERATION) 
     CComPtr<ID3D11Device> device; 
     D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0 }; 

#if defined(ENABLE_HW_DRIVER) 
     handle_result(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_VIDEO_SUPPORT, 
      levels, ARRAYSIZE(levels), D3D11_SDK_VERSION, &device, nullptr, nullptr)); 
#else 
     handle_result(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_NULL, nullptr, D3D11_CREATE_DEVICE_SINGLETHREADED, 
      levels, ARRAYSIZE(levels), D3D11_SDK_VERSION, &device, nullptr, nullptr)); 
#endif 

     UINT token; 
     CComPtr<IMFDXGIDeviceManager> manager; 

     handle_result(MFCreateDXGIDeviceManager(&token, &manager)); 
     handle_result(manager->ResetDevice(device, token)); 

     handle_result(attributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, manager)); 
     handle_result(attributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE)); 
     handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE)); 
#else 
     handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE)); 
#endif 

     CComPtr<IMFSourceReader> reader; 

     handle_result(MFCreateSourceReaderFromURL(L"Rogue One - A Star Wars Story - Trailer.mp4", attributes, &reader)); 

     CComPtr<IMFMediaType> output_type; 

     handle_result(MFCreateMediaType(&output_type)); 
     handle_result(output_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video)); 
     handle_result(output_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32)); 
     handle_result(reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, output_type)); 

     unsigned int frame_count{}; 

     std::cout << "Started processing frames" << std::endl; 

     while (true) 
     { 
      CComPtr<IMFSample> sample; 
      DWORD flags; 

      handle_result(reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
       0, nullptr, &flags, nullptr, &sample)); 

      if (flags & MF_SOURCE_READERF_ENDOFSTREAM || sample == nullptr) 
       break; 

      std::cout << "Frame " << frame_count++ << std::endl; 

      CComPtr<IMFMediaBuffer> buffer; 
      BYTE* data; 

      handle_result(sample->ConvertToContiguousBuffer(&buffer)); 
      handle_result(buffer->Lock(&data, nullptr, nullptr)); 

      // Use the frame here. 

      buffer->Unlock(); 
     } 

     std::cout << "Finished processing frames" << std::endl; 
    } 

    MFShutdown(); 
    CoUninitialize(); 

    return 0; 
} 
+1

Vous pourriez essayer avec MF_MT_SUBTYPE, MFVideoFormat_NV12. – VuVirt

+0

Merci pour l'indice! En effet, il commence à fonctionner quand je mets MFVideoFormat_NV12 comme sous-type de sortie. J'ai utilisé DXVAChecker pour lister [les formats de sortie possibles] (https://i.imgsafe.org/12d5643c00.png) pour le décodeur que j'utilise (ou je pense) et il n'y a pas de RGB32 là. Cela signifie-t-il que je ne peux pas décoder H264 directement en RGB32 en utilisant ce décodeur? Alors pourquoi cela fonctionnerait-il parfois bien comme décrit au point 4 de ma question? Ou pourquoi n'y aurait-il pas de message d'erreur "format de sortie non supporté"? Curieusement, NV12 semble être le seul format de la liste qui fait fonctionner le code. – Vennor

+0

S'il vous plaît vérifier ma réponse détaillée – VuVirt

Répondre

2

Votre code est correct, conceptuellement, avec la seule remarque - et ce n'est pas évident - que le décodeur Media Foundation est multithread. Vous l'alimentez avec une seule version filetée du périphérique Direct3D. Vous devez le contourner ou vous obtenez ce que vous obtenez actuellement: les violations d'accès et les gels, c'est-à-dire un comportement indéfini.

// NOTE: No single threading 
    handle_result(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 
     (0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT, 
     levels, ARRAYSIZE(levels), D3D11_SDK_VERSION, &device, nullptr, nullptr)); 

    // NOTE: Getting ready for multi-threaded operation 
    const CComQIPtr<ID3D10Multithread> pMultithread = device; 
    pMultithread->SetMultithreadProtected(TRUE); 

Notez également que ce simple exemple de code a un goulot d'étranglement autour des lignes que vous avez ajoutées pour obtenir un tampon contigu. Apparemment, c'est votre démarche pour avoir accès aux données ... cependant, le comportement par conception est que les données décodées sont déjà dans la mémoire vidéo, et votre transfert vers la mémoire système est une opération coûteuse. Autrement dit, vous avez ajouté une performance sévère à la boucle. Vous serez intéressé par la vérification de la validité des données de cette façon, et quand il s'agit de benchmarking de performance, vous devriez plutôt commenter cela.

+1

J'ai testé le code avec le mien et vos suggestions. Ma suggestion (NV12) fonctionne sans créer et mettre en place un périphérique multithread. Votre suggestion fonctionne lorsque RGB32 est défini et que le processeur vidéo MFT est activé via MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING. Quel IMHO signifie que c'est le processeur vidéo MFT qui est multithread et pas le décodeur H264 (j'utilise un décodeur matériel NVidia btw). Corrigez-moi si j'ai tort, s'il-vous plait. +1 de toute façon. – VuVirt

+2

@VuVirt: ils sont tous deux multithreadés car ils utilisent activement les files d'attente de travail en interne. Le problème survient lorsqu'il y a effectivement une collision lors de l'utilisation du périphérique Direct3D. Il y a beaucoup moins de risques de collision avec NV12 car aucune conversion post-décodage n'est nécessaire. –

+0

Merci! Est-il possible de savoir à l'avance si un décodeur ou un processeur est multithread? Je ne me souviens pas qu'il soit mentionné dans la documentation, le tutoriel ou les exemples. Maintenant que je regarde la description du décodeur sur MSDN, il y a le paramètre 'CODECAPI_AVDecNumWorkerThreads', qui devrait être un indice, mais est-il indiqué clairement n'importe où? – Vennor

1

Les types de sortie du décodeur vidéo H264 peuvent être trouvés ici: https://msdn.microsoft.com/en-us/library/windows/desktop/dd797815(v=vs.85).aspx. RGB32 n'en fait pas partie. Dans ce cas, votre application s'appuie sur le processeur vidéo MFT pour effectuer la conversion à partir de MFVideoFormat_I420, MFVideoFormat_IYUV, MFVideoFormat_NV12, MFVideoFormat_YUY2, MFVideoFormat_YV12 à RGB32. Je suppose que c'est le processeur vidéo MFT qui agit bizarrement et qui fait que votre programme se comporte mal. Voilà pourquoi en fixant NV12 comme le sous-type de sortie du décodeur vous débarrasser du processeur vidéo MFT et les lignes de code suivantes deviennent inutiles ainsi:

handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE)); 

et

handle_result(attributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE)); 

plus , comme vous avez remarqué NV12 est le seul format qui fonctionne correctement. Je pense que la raison en est que c'est le seul qui est utilisé dans les scénarios accélérés par le gestionnaire de périphériques D3D et DXGI.