Cours de programmation DirectShow
Transcription
Cours de programmation DirectShow
Ecriture de filtres DirectShow Objectif: apporter de la flexibilité à DirectShow en écrivant ses propres filtres (qui sont installés avec l'application). I. Introduction Création d'un filtre: C++ = on hérite de CBaseFilter en surchargeant les méthodes dont on souhaite modifier le comportement, et en définissant les variables et les méthodes propres. Classes plus spécialisées pour certains types de filtre (CTransformFilter pour un filtre transformant) avec des surcharges déjà adaptées à la tâche à effectuer. Un filtre définit des bornes en nombre fixe (FileWriter) ou variable à l'exécution (Splitter, Infinite Tee Filter). Lorsque deux bornes sont connectées une négociation entre les bornes permet de fixer: • • • le type de transport le type de media l'allocator de ressources Transmission et traitement des données: • filtres sources: o push source (thread associé) : envoie des échantillons en permanence (webcam, tuner, satellite, cable, …) o pull source (thread en aval) : attend que son voisin lui réclame des échantillons. • filtre transformant : reçoit ses échantillons depuis le(s) filtre(s) sources, les traite, et les transmet. • filtre de rendu: reçoit ses échantillons depuis le(s) filtre(s) sources, et effectue leurs rendus sur la base de leurs timestamps. Support COM: tous ces filtres sont des objets COM. II. Ecrire des filtres DirectShow Partie auto-suffisante. III. Comment connecter les filtres Connexion des bornes: GraphManager (Connect) : lance la négociation entre les bornes Pin, Pout Pout accepte de se connecter Pout (ReceiveConnection) sur Pin Pin accepte de se connecter la connexion devient alors effective. Détail de la négociation lors de la connexion: 1. Négociation du type de média (quel type de flux va transiter entre les deux bornes) • Dépend des paramètres de Connect (type déjà défini, ou négocié entre les bornes). • ReceiveConnection : permet à Pout de proposer un type de connexion à Pin jusqu'à ce qu'un type soit accepté (EnumMediaTypes). • cf note: ne pas tester que le format, mais également la taille de la structure transportée. 2. Négociation de l'allocator • local memory transport : pour définir comment les échantillons seront transportés d'une borne à l'autre. • allocateur : objet responsable de l'allocation de la mémoire. • Pin et Pout partagent le même allocator. • Pin et Pout peuvent fournir un allocator. • Pout choisit l'allocateur à utiliser. • Pin fixe les propriétés de l'allocator (nombre de buffers, taille, …). Négociation de l'allocator • • Détail de la négociation en mode push (Pout:IMemInputPin / Pin:IMemAllocator) • Pout (GetAllocatorRequirement) sur Pin pour récupérer les propriétés de l'allocateur. • Pout (GetAllocator) sur Pin s'il veut que l'allocator soit celui de Pin. • Pout choisit l'allocator. • Pin (SetProperties) pour fixer les propriétés de l'allocator. • Pout (NotifyAllocator) pour informer Pin de l'allocator à utiliser. • Pin (GetProperties) pour vérifier que les propriétés de l'allocator sont acceptables. • Pout est responsable de l'activation/désactivation de l'allocator (start/stop). Détail de l'allocation en mode pull (Pin:IAsyncReader) • Pin (RequestAllocator) sur Pout pour spécifier les propriétés du buffer et éventuellement l'allocator à utiliser. • Pout choisit l'allocator qui est retourné comme paramètre de sortie de RequestAllocator. • Pin doit vérifier que les propriétés de l'allocator sont acceptables. • Pin responsable de l'activation/désactivation de l'allocator (start/stop). Custom Allocator: cas particulier (voir section III.4). Reconnecting Pin: cas particulier où l'ajout d'une connexion peut nécessiter la modification d'une connexion existante. Heureusement, tout ceci est assuré dans CBaseFilter. IV. Flux de données pour les développeurs Dans un graphe, • le flux de données va de gauche à droite. • les données de contrôle vont de droite à gauche. Transmission des échantillons: • push model: Pout appelle Receive sur chaque échantillon qu'elle veut envoyer. • pull model: Pin appelle Request/SyncRead lorsqu'elle veut recevoir l'échantillon suivant. Autres cas traités: • end-of-stream : cas de la fin de flux • flushing : vide le graphe des échantillons actuellement en transit (remplacement par autre chose). • seeking : déplacement dans le flux. V. Thread et sections critiques Préambule: sections critiques sous Windows: Définition: note: inclure Windows.h // gestion d'une section critique sous windows class CriticalSection { public: CriticalSection() { InitializeCriticalSection(&CritSec); } ~CriticalSection() { DeleteCriticalSection(&CritSec); } void Enter() { EnterCriticalSection(&CritSec); } void Leave() { LeaveCriticalSection(&CritSec); } private: CRITICAL_SECTION CritSec; }; // // // // // gestion d'un verrou sur une section critique à la création du verrou on tente l'entrée dans la section critique (bloquant) a la fin de la portée du verrou, on sort automatiquement de la section critique class Lock { public: Lock(CriticalSection* pCS) : pCritSec(pCS) { if (pCritSec) pCritSec->Enter(); } ~Lock() { if (pCritSec) pCritSec->Leave(); } private: CriticalSection *pCritSec; }; Utilisation de la section critique: Dans la définition de la classe définir une variable de ce type: class Classe { private: CriticalSection CritSec; … public: … } // pour les sections critiques Dans les méthodes: HRESULT Classe::Methode(…) { Lock lock(&CritSec); // on entre en section critique. // ici le code à exécuter en section critique. return S_OK; } // sortie automatique de la section critique en // fin de portée de la variable lock De cette façon, l'objet COM (partagé) s'assure qu'aucune modification critique de l'objet n'est faite lorsque l'on se trouve en section critique. Différentes opérations sur un filtre s'effectuent en section critique Doc = détails techniques. VI. Quality-Control Management Un filtre de rendu qui reçoit trop ou pas assez d'échantillons peut envoyer des messages de qualité de façon à ajuster la vitesse du flux dans le graphe. La qualité peut également être mesurée à travers les "dropped frames". VII. DirectShow and Com VII.1 IUnknown Implémentation typique d'un objet COM héritant de deux autres objets COM: class Classe: public IObject1, public IObject2 { public: // IUnknown methods STDMETHODIMP (ULONG) AddRef(); STDMETHODIMP (ULONG) Release(); STDMETHODIMP QueryInterface(REFIID riid, void **ppv); // ou remplacer ces 3 lignes par: DECLARE_IUNKNOWN … private: long RefCount; CriticalSection CritSec; // si nécessaire … } // constructeur / destructeur Classe::Classe() { RefCount = 1; … } Classe::~Classe() { assert(RefCount == 0); … } /******************* IUnknown methods *****************/ STDMETHODIMP_(ULONG) Classe::AddRef() { // incrémentation de la variable sous verrou return InterlockedIncrement(&RefCount); } STDMETHODIMP_(ULONG) Classe::Release() { assert(RefCount >= 0); // décrémentation de la variable sous verrou ULONG uCount = InterlockedDecrement(&RefCount); if (uCount == 0) delete this; } STDMETHODIMP Classe::QueryInterface(REFIID iid, void **ppv) { if (ppv == NULL) return E_POINTER; *ppv = NULL; if (iid == __uuidof(IUnknown)) // double cast nécessaire: on renvoie le IUnknown de IObject1 *ppv = static_cast<IUnknown*>(static_cast<IObject1*>(this)); else if (iid == __uuidof(IObject1)) *ppv = static_cast<IObject1*>(this); else if (iid == __uuidof(IObject2)) *ppv = static_cast<IObject2*>(this); else return E_NOINTERFACE; AddRef(); return S_OK; } L'interface IUnknown référence de l'objet est contrôlée à travers le QueryInterface, même par héritage, on hérite de plusieurs fois cette interface. Rappel: Agrégation directe: l'objet est définit comme membre de la classe (class C1 { … }; class C2 { private: C1 c1; … } ) Héritage: l'objet est intégré à la classe (class C1 {}; class C2: public C1 { … } ) Problème avec une agrégation d'objets (par héritage ou agrégation directe) • plusieurs interfaces IUnknown peuvent exister pour un même objet : dans le QueryInterface précédent, on a choisi de renvoyer un IUnknown particulier. • la méthode NonDelegatingQueryInterface permet d'implanter une méthode permettant de récupérer un pointeur sur le IUnknown souhaité de l'objet (sans AddRef). Note: c'est une méthode privée de IUnknown. CUnknown : implémentation de la classe IUnknown pour DirectShow. Pour tous les objets qui en dérive, NonDelegatingQueryInterface doit être implémentée afin d'être en mesure, pour l'objet agrégé de renvoyer autre chose que l'interface IUnknown (sinon renvoie toujours E_NOINTERFACE, sauf pour IID_IUnknown). Exemple: cf le poly. Class contructor: dans le constructeur, faire appel au constructeur des parents/des objets agrégés afin d'assurer une initialisation correcte de ces derniers. Note: en général, inutile d'inclure CUnknown si l'un des objets agrégés ou hérités en dérive déjà. VII.2 Comment créer une DLL Important afin de savoir comment une application crée une instance d'un nouveau filtre. Création d'un objet COM ClassFactory = classe COM spécialisée dans la création des autres objets COM. FactoryTemplate = spécialisation d'une ClassFactory (toute ClassFactory contient un FactoryTemplate) contenant les informations d'un composant (CLSID, pointeur sur la fonction permettant de créer un objet de ce type). une DLL = un tableau global de FactoryTemplate (autant que de CLSID). Appels à la création d'un objet COM: 1. CoGetClassObject : permet de créer une instance d'un objet IClassFactory. appel DllGetClassObject : • recherche dans la DLL le CLSID correspondant à l'objet à créer. • s'il le trouve, instancie le FactoryTemplate. 2. IClassFactory::CreateInstance : appel de la fonction (définie dans le FactoryTemplate) permettant de créer l'objet. Déclaration d'une dll contenant des objets COM Objets utilisables par la ClassFactory Pour déclarer un nouvel objet utilisable par la ClassFactory, il faut définir (au moins): • un tableau d'objet g_templates[] de type CFactoryTemplate contenant pour chaque objet de la DLL:le nom du composant, le CLSID, la fonction de création d'un objet de ce type (généralement, une méthode publique de la classe). • un variable g_ctemplate contenant la taille de ce tableau. • écrire la fonction d'initialisation. Dans le cas des objets DirectShow, on définit en plus: • une fonction optionnelle d'initialisation • les informations de base pour le filtre, à savoir: o la description du filtre (nom, clsid, mérite, nombre et une table de description des bornes). o pour chaque borne, sa description (cf poly) et la liste des types de media (major & minor). Voir l'imbrication des classes CFactoryTemplate à la page suivante. Exemple: cf poly - Declaring filter information - Guideline for registering filters CFactoryTemplate Nom du filtre m_Name m_ClsID m_IpfnNew m_IpfnInit m_pAMovieSetup_Filter Pointeur sur le CLSID de l’objet Pointeur sur la fonction qui créé une instance de l’objet Pointeur sur une structure AMOVIESETUP_FILTER Informations pour l’enregistrement du filtre dans la base de registres AMOVIESETUP_FILTER const CLSID const WCHAR DWORD UINT const AMOVIESETUP_PIN *clsID; *strName; dwMerit; nPins; *lpPin; CLSID du filtre Nom du filtre Mérite du filtre Nombre de bornes Pointeur sur un tableau de AMOVIESETUP_PIN (à nPins entrées) AMOVIESETUP_PIN LPWSTR strName; BOOL bRendered; BOOL bOutput; BOOL bZero; BOOL bMany; const CLSID *clsConnectsToFilter; const WCHAR *strConnectsToPin; UINT nMediaTypes; const AMOVIESETUP_MEDIATYPE *lpMediaType; AMOVIESETUP_MEDIATYP const CLSID const CLSID *clsMajorType; *clsMinorType; Informations sur une borne Nom de la borne (obsolète) Borne à rendre ? Borne de sortie ? Peut avoir aucune instance ? Peut avoir plusieurs instances ? (Obsolète) (Obsolète) Nombre de types supportés par cette borne Informations sur un type de média GUID majeur du type de média GUID du sous-type de média Représentation d’une structure CFactoryTemplate Déclaration des fonctions de la dll Une DLL doit contenir les fonctions suivantes afin de pouvoir la charger, l'enregistrer (register), le désenregistrer (unregister) Dans une DLL utilisant DirectShow, il faut définit: • le point d'entrée de la DLL (DllMain) extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID); BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) { return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved); } • Les fonctions DllRegisterServer/ DllUnregisterServer DirectShow offre une fonction qui permet d'enregistrer/désenregistrer automatiquement les objets COMs (FactoryTemplates) définis dans g_ctemplate, g_template avec AMovieDllRegisterServer2: Limitation de cette fonction: elle n'enregistre que des filtres DirectShow (CLSID_LegacyAmFilterCategory), ce qui n'est pas le cas des filtres de capture par exemple. Utilisation typique: // DllRegisterServer STDAPI DllRegisterServer() { return AMovieDllRegisterServer2(TRUE); } // DllUnregisterServer STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2(FALSE); } Contenu du .def de la DLL On exporte toute la DLL à l'exception du point d'entrée. // à sortir des commentaires pour avoir un // .ax plutôt qu'une dll. // LIBRARY EXPORTS Gargle.ax DllMain DllGetClassObject DllCanUnloadNow DllRegisterServer DllUnregisterServer PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE Installation de la DLL 1. copier la DLL dans le répertoire d'installation (par exemple /windows/system). 2. enregistrer le .DLL ou le .ax avec regserv32.exe. Cette commande ajoute la clé suivante à la base de registre pour chaque objet COM enregistré dans la DLL. HKEY_CLASSES_ROOT CLSID Filter CLSID REG_SZ: (Default) = Friendly name InprocServer32 REG_SZ: (Default) = File name of the DLL REG_SZ: ThreadingModel = Both Déclaration dans la base de registre • • l'installation de la DLL permet de créer des objets de classe indiquée. en revanche, pour que le filtre puisse être trouvé par le "system device enumerator" (ICreateDevEnum) ou par le "Filter Mapper" (objet COM qui permet aussi d'énumérer des filtre, moins efficace que le "systeme device enumerator" mais parfois utile), il faut ajouter les informations suivantes: HKEY_CLASSES_ROOT CLSID Category Instance Filter CLSID REG_SZ: CLSID = Filter CLSID REG_BINARY: FilterData = Filter information REG_SZ: FriendlyName = Friendly name Où Category est le GUID de la catégorie du filtre. VIII. Ecrire des filtres de transformations En 6 étapes: 1. Choisir une classe de base Principalement (dérive de CBaseFilter) • CTransformInPlaceFilter On renvoie l'échantillon reçu après l'avoir modifié sur place. Limitations: o ce filtre ne peut pas changer le format des données. • o problème de performance et d'accès (R/W) notamment si l'échantillon se trouve en HW. CTransformFilter transformation par copie: l'échantillon d'entrée est lu, et les données modifiées sont copiées dans un échantillon de sortie. CVideoTransformFilter est spécialisé pour le codage/décodage. 2. Déclaration de la classe • • dans la définition de la classe: o faire dériver le filtre d'une classe de base. o dans le constructeur du filtre, faire appel au constructeur de la classe de base (c'est là que l'on passe le nom du filtre ainsi que son clsid). dans le .h associé à la classe, placer le clsid associé à la classe (DEFINE_GUID). Cf le poly pour un exemple. Rappel: le clsid doit être généré avec guidgen pour s'assurer qu'il est unique. 3. Négociation du type de média supporté 1. CTransformFilter::CheckInputType appelée lorsque le filtre en amont propose un type de média au filtre de transformation. cf exemple: poly VIII.3.a. 2. CBasePin::GetMediaType (CBaseFilter dérive de CBasePin) retourne la liste des médias préférés pour le filtre (par ordre de préférence). cf exemple : poly VIII.3.b. HRESULT CWavDestFilter::GetMediaType(int iPosition, CMediaType *pMediaType) { ASSERT(iPosition == 0 || iPosition == 1); if(iPosition == 0) { CheckPointer(pMediaType,E_POINTER); pMediaType->SetType(&MEDIATYPE_Stream); pMediaType->SetSubtype(&MEDIASUBTYPE_WAVE); return S_OK; } return VFW_S_NO_MORE_ITEMS; } Note: inutile pour les CTransformInPlaceFilter. 3. CTransformFiltrer::CheckTransform lorsque le filtre est connecté, cette méthode vérifie que les formats d'entrée et de sortie du filtre sont compatibles avec son fonctionnement. // CheckTransform // To be able to transform the formats must be identical HRESULT CContrast::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) { CheckPointer(mtIn,E_POINTER); CheckPointer(mtOut,E_POINTER); HRESULT hr; if(FAILED(hr = CheckInputType(mtIn))) { return hr; } // format must be a VIDEOINFOHEADER if(*mtOut->FormatType() != FORMAT_VideoInfo) { return E_INVALIDARG; } // formats must be big enough if(mtIn->FormatLength() < sizeof(VIDEOINFOHEADER) || mtOut->FormatLength() < sizeof(VIDEOINFOHEADER)) return E_INVALIDARG; VIDEOINFO *pInput = (VIDEOINFO *) mtIn->Format(); VIDEOINFO *pOutput = (VIDEOINFO *) mtOut->Format(); if(memcmp(&pInput->bmiHeader,&pOutput->bmiHeader, sizeof(BITMAPINFOHEADER))== 0) { return NOERROR; } return E_INVALIDARG; } Note: inutile pour les CTransformInPlaceFilter. 4. Choix des propriétés de l'allocateur La méthode CTransformFilter::DecideBufferSize doit être implémentée (virtuelle pure): elle est appelée par l'allocator afin de déterminer la taille du buffer à utiliser. Cette méthode appelle IMemAllocator::SetProperties pour fixer les propriétés de l'allocator. cf exemple poly VIII.4. Note: inutile pour les CTransformInPlaceFilter. 5. Transformation de l'image CTransformFilter::Transform (ou CTransformInPlaceFilter::Transform) permet de transformer l'échantillon. HRESULT CMyFilter::Transform(IMediaSample *pSource, IMediaSample *pDest) { // Look for format changes from the video renderer. CMediaType *pmt = 0; if (S_OK == pDest->GetMediaType((AM_MEDIA_TYPE**)&pmt) && pmt) { m_pOutput->SetMediaType(pmt); // Notify output pin DeleteMediaType(pmt); } // Get the addresses of the actual bufffers. BYTE *pBufferIn, *pBufferOut; pSource->GetPointer(&pBufferIn); pDest->GetPointer(&pBufferOut); long cbByte = 0; // Process the buffers (ProcessFrame is User Defined) hr = ProcessFrame(pBufferIn, pBufferOut, &cbByte); // Set the size of the destination image. ASSERT(pDest->GetSize() >= cbByte); pDest->SetActualDataLength(cbByte); return hr; } • • • Dans cette méthode, GetMediaType permet de récupérer le type de média et de vérifier s'il a changé (S_OK dans ce cas). En cas de changement, m_pOutput->SetMediaType(pmt) permet d'en informer la borne de sortie. GetPointer permet de récupérer le pointeur sur le buffer de mémoire contenant l'image. SetActualDataLength permet d'indiquer la taille valide du buffer dans le buffer de sortie. Remarques: 1. dans le cas compressé, attention au keyframe (voir la documentation de IMedia::SetSyncPoint). 2. CTransformFilter assure la copie automatique des timestamps (pour le gérer à la main voir IMediaSample::SetTime). 6. Ajout du support COM • • • Comptage de référence: implémentation de AddRef/Release car le filtre dérive de CUnknown. QuerryInterface: doit être implémenté (voir ci-dessous). cf exemple VIII.6. CreateInstance: doit être implémenté car permet à la ClassFactory de créer un objet de ce type. cf exemple VIII.6 Le reste : voir la partie sur la construction d'une DLL. Etude de cas 1/ Exemple de filtre Implémentation du filtre FiltreModif.h // Le type unique du paramètre pour la classe CFiltreModif. struct ParamPourFiltreModif { int param1; }; // Définition des Identifiants (pour la classe et son interface) // {C63BB066-AD42-4e4c-8629-A52D0D0D4D76} DEFINE_GUID(CLSID_FiltreModif, 0xc63bb066, 0xad42, 0x4e4c, 0x86, 0x29, 0xa5, 0x2d, 0xd, 0xd, 0x4d, 0x76); // {E29EC104-0E78-40e3-966B-CDD79CCD8C59} DEFINE_GUID(IID_IFiltreModif, 0xe29ec104, 0xe78, 0x40e3, 0x96, 0x6b, 0xcd, 0xd7, 0x9c, 0xcd, 0x8c, 0x59); #ifdef __cplusplus extern "C" { #endif // façon de déclarer une interface sur l'objet COM DECLARE_INTERFACE_(IFiltreModif, IUnknown) { // Pour recupérer les paramètres du filtre STDMETHOD(GetParams) (THIS_ ParamPourFiltreModif *irp ) PURE; // Pour positionner les paramètres du filtre STDMETHOD(SetParams) (THIS_ ParamPourFiltreModif *irp ) PURE; }; #ifdef __cplusplus } #endif // Déclaration de la classe qui représentera le filtre class CFiltreModif : public CTransformFilter, // classe de base pour les filtres de // tranformation public IFiltreModif // interface par laquelle nous pourrons // accéder au filtre { public: DECLARE_IUNKNOWN; // macro permettant d'implémenter les 3 méthodes // qu'une nouvelle interface doit avoir // Constructeurs et déstructeur static CUnknown *WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr); CFiltreModif(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr); ~CFiltreModif(); // Surcharges de méthodes héritées de CTransformFilter HRESULT CheckInputType( const CMediaType* mtIn); HRESULT CheckTransform( const CMediaType *mtIn, const CMediaType *mtOut); HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties); HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut); HRESULT Transform(IMediaSample *pMediaSample); // Retrouve l'interface sur le filtre STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv); // Définition des méthodes accessibles par l'interface IFiltreModif STDMETHODIMP GetParams(ParamPourFiltreModif *irp); STDMETHODIMP SetParams(ParamPourFiltreModif *irp); protected: // Pour vérifier si un type de média en entrée peut etre accepté BOOL canPerformTransform(const CMediaType *pMediaType) const; // Pour copier ce qui "se trouve" sur le pin d'entrée sur le pin de sortie HRESULT copyMediaSample(IMediaSample *pSource, IMediaSample *pDest) const; // Paramètre du filtre ParamPourFiltreModif m_ParamPourFiltreModif; }; FiltreModif.def LIBRARY EXPORTS FiltreModif.ax DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE FiltreModif.cpp #include "FiltreModif.h" #define TRANSFORM_NAME L"Filtre Modifiant" // Nom du filtre // Renseignement de différentes informations pour que // l'inscription au registre puisse s'effectuer automatiquement // Déclaration d'un type de const AMOVIESETUP_MEDIATYPE { &MEDIATYPE_NULL // , &MEDIASUBTYPE_NULL }; // média spécifique (ici pour les pins) sudPinTypes = type Majeur sous-type // Description des pins const AMOVIESETUP_PIN psudPins[] = { { L"Entree" // strName: nom du pin (obselete) , FALSE // bRendered , FALSE // bOutput: indique si le pin est un pin de sortie , FALSE // bZero: si TRUE le pin peut ne pas etre instancié , , , , , } , { , , , , , , , , } }; FALSE // bMany: si TRUE plusieurs instances de ce pin peuvent l'être &CLSID_NULL // clsConnectsToFilter (obselete) L"" // strConnectsToPin (obselete) 1 // nTypes: nombre de médias supportés &sudPinTypes // lpTypes: pointeur sur AMOVIESETUP_MEDIATYPE L"Sortie" FALSE TRUE FALSE FALSE &CLSID_NULL L"" 1 &sudPinTypes // // // // // // // // // strName bRendered bOutput bZero bMany clsConnectsToFilter strConnectsToPin nTypes lpTypes // Informations pour l'enregistrement du filtre const AMOVIESETUP_FILTER sudFiltreModif = { &CLSID_FiltreModif // clsID , TRANSFORM_NAME // strName: nom du filtre , MERIT_DO_NOT_USE // dwMerit: mérite , 2 // nPins: nombre de pins , psudPins }; // lpPin: pointeur sur AMOVIESETUP_PIN // Necessaire pour le mecanisme de CreateInstance de ClassFactory CFactoryTemplate g_Templates[]= { { TRANSFORM_NAME , &CLSID_FiltreModif , CFiltreModif::CreateInstance , NULL , &sudFiltreModif } }; int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]); //------------------------------------------------------------------------// CreateInstance: pour que COM puisse réer l'objet. //------------------------------------------------------------------------CUnknown * WINAPI CFiltreModif::CreateInstance(LPUNKNOWN punk, HRESULT *phr) { CFiltreModif *pNewObject = new CFiltreModif(NAME("premieressai"),punk,phr); if (pNewObject == NULL) *phr = E_OUTOFMEMORY; return pNewObject; } //-------------------------------------------------------------------------// Constructeur et destructeur //-------------------------------------------------------------------------CFiltreModif::CFiltreModif(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr) : CTransformFilter (tszName, punk, CLSID_FiltreModif) { SetParams(&m_ParamPourFiltreModif); // Fixe les paramètres } CFiltreModif::~CFiltreModif(){} //-------------------------------------------------------------------------// CheckInputType: Examine un type proposé en entrée. // Retourne S_OK si ce type peut etre accapté, VFW_E_TYPE_NOT_ACCEPTED sinon //-------------------------------------------------------------------------HRESULT CFiltreModif::CheckInputType(const CMediaType *mtIn) { if(canPerformTransform(mtIn)) return S_OK; else return VFW_E_TYPE_NOT_ACCEPTED; } //-------------------------------------------------------------------------// CheckTransform: Compare un type d'entrée avec un type de sortie, et // regarde si on peut convertir de l'un à l'autre. //-------------------------------------------------------------------------HRESULT CFiltreModif::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) { if (canPerformTransform(mtIn) && (*mtIn == *mtOut)) return NOERROR; return E_FAIL; } //-------------------------------------------------------------------------// DecideBufferSize: Informe les besoins à l'allocateur du pin de sortie. //-------------------------------------------------------------------------HRESULT CFiltreModif::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties) { // On s'assure que le pin d'entrée est bien connecté if(m_pInput->IsConnected() == FALSE) return E_UNEXPECTED; ASSERT(pAlloc); ASSERT(pProperties); HRESULT hr = NOERROR; // Le nombre de buffers ne doit pas etre nul pProperties->cBuffers = 1; // Recupération de l'allocateur supérieur et de ces propriétés ALLOCATOR_PROPERTIES InProps; IMemAllocator * pInAlloc = NULL; hr = m_pInput->GetAllocator(&pInAlloc); if (SUCCEEDED(hr)) { hr = pInAlloc->GetProperties(&InProps); if(SUCCEEDED(hr)) pProperties->cbBuffer = InProps.cbBuffer; pInAlloc->Release(); } if (FAILED(hr)) return hr; ASSERT(pProperties->cbBuffer); // On positionne les propriétés de l'allocateur // (celui créé "Actual", sert a récupérer les propriétés actuelles) ALLOCATOR_PROPERTIES Actual; hr = pAlloc->SetProperties(pProperties,&Actual); if(FAILED(hr)) return hr; // Même si SetProperties n'a pas retourné d'erreur, les propriétés // actuelles peuvent etre différentes de celles dont nous voulions. // Nous vérifions ce résultat, mais nous ne regardons qu'aux // propriétés qui nous intéressent. ASSERT( Actual.cBuffers == 1 ); if( pProperties->cBuffers > Actual.cBuffers || pProperties->cbBuffer > Actual.cbBuffer) return E_FAIL; return NOERROR; } //-------------------------------------------------------------------------// GetMediaType: Retourne un type de sortie préféré, spécifié par ordre de // préférence, par le premier paramètre. Or ici, nous n'en n'avons qu'un... // celui du pin d'entrée. Le second paramètre recevra le type. //-------------------------------------------------------------------------HRESULT CFiltreModif::GetMediaType(int iPosition, CMediaType *pMediaType) { // Verification si le pin d'entrée est connecté if(m_pInput->IsConnected() == FALSE) return E_UNEXPECTED; // Verification sur la validite du premier paramètre if(iPosition<0) return E_INVALIDARG; // Mais comme nous n'avons qu'un type if(iPosition>0) return VFW_S_NO_MORE_ITEMS; *pMediaType = m_pInput->CurrentMediaType(); return NOERROR; } //-------------------------------------------------------------------------// Transform: Effectue le traitement sur un échantillon. Plus précisement // on recopie l'échantillon en entrée et on demande qu'il soit traité. //-------------------------------------------------------------------------HRESULT CFiltreModif::Transform(IMediaSample *pIn, IMediaSample *pOut) { HRESULT hr= copyMediaSample(pIn, pOut); if(FAILED(hr)) return hr; return Transform(pOut); } //-------------------------------------------------------------------------// copyMediaSample: Méthode nécessaire pour la précedente pour effectuer une // une copie d'un échantillon //-------------------------------------------------------------------------HRESULT CFiltreModif::copyMediaSample(IMediaSample *pSource, IMediaSample *pDest) const { // Copie des données BYTE *pSourceBuffer, *pDestBuffer; long lSourceSize = pSource->GetActualDataLength(); long lDestSize = pDest->GetSize(); ASSERT(lDestSize >= lSourceSize); pSource->GetPointer(&pSourceBuffer); pDest->GetPointer(&pDestBuffer); CopyMemory( (PVOID) pDestBuffer,(PVOID) pSourceBuffer,lSourceSize); // Copie du "Timestamp" REFERENCE_TIME TimeStart, TimeEnd; if(NOERROR == pSource->GetTime(&TimeStart, &TimeEnd)) pDest->SetTime(&TimeStart, &TimeEnd); LONGLONG MediaStart, MediaEnd; if(pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) pDest->SetMediaTime(&MediaStart,&MediaEnd); // Copie la propriété "Sync point" (voir si cela est une image clé..) HRESULT hr = pSource->IsSyncPoint(); if(hr == S_OK) pDest->SetSyncPoint(TRUE); else if(hr == S_FALSE) pDest->SetSyncPoint(FALSE); else return E_UNEXPECTED; // une erreur s'est produite // Copie du type de média AM_MEDIA_TYPE *pMediaType; pSource->GetMediaType(&pMediaType); pDest->SetMediaType(pMediaType); DeleteMediaType(pMediaType); // Copie de la propriété "preroll" (si oui, ne devrait pas être affiché) hr = pSource->IsPreroll(); if(hr == S_OK) pDest->SetPreroll(TRUE); else if(hr == S_FALSE) pDest->SetPreroll(FALSE); else return E_UNEXPECTED; // une erreur s'est produite // Copie de la propriété "discontinuity" (pour savoir si l'echantillon // représente une coupure dans le flux de données) hr = pSource->IsDiscontinuity(); if (hr == S_OK) pDest->SetDiscontinuity(TRUE); else if(hr == S_FALSE) pDest->SetDiscontinuity(FALSE); else return E_UNEXPECTED; // Copie la longueur des données valides situées dans la buffer long lDataLength = pSource->GetActualDataLength(); pDest->SetActualDataLength(lDataLength); return NOERROR; } //-------------------------------------------------------------------------// canPerformTransform: Méthode vérifiant si un type de média en entrée // peut etre accepté. //-------------------------------------------------------------------------BOOL CFiltreModif::canPerformTransform(const CMediaType *pMediaType) const { // Nous n'acceptons que les types suivants: (RGB24, ARGB32 or RGB32) if(IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video)) { if(IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB24)) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pMediaType->Format(); return (pvi->bmiHeader.biBitCount == 24); } if(IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_ARGB32)) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pMediaType->Format(); return (pvi->bmiHeader.biBitCount == 32); } if(IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB32)) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pMediaType->Format(); return (pvi->bmiHeader.biBitCount == 32); } } return FALSE; } //-------------------------------------------------------------------------// Transform: Transformation de l'échantillon //-------------------------------------------------------------------------HRESULT CFiltreModif::Transform(IMediaSample *pMediaSample) { // Récupération du type de média actuel AM_MEDIA_TYPE* pmt = &(m_pInput->CurrentMediaType()); VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pmt->pbFormat; BYTE *pData; long lDataLen; // Pointeur sur le buffer actuel de l'image // Contient la longueur d'un échantillon pMediaSample->GetPointer(&pData); lDataLen = pMediaSample->GetSize(); // Récuperation de la taille (en octets) // Récupération des propriétés de l'image à partir du BITMAPINFOHEADER // biBitCount: nombre de bits par pixel int iPixelSize = pvi->bmiHeader.biBitCount / 8; // -> nombre d'octets par pixel int cxImage = pvi->bmiHeader.biWidth; int cyImage = pvi->bmiHeader.biHeight; int cbImage = cyImage * cxImage * iPixelSize; int numPixels = cxImage * cyImage; // m_ParamPourFiltreModif.param1 BYTE *prgb = (BYTE*) pData; UINT bTemp; for (int iPixel=0; iPixel < numPixels; iPixel++, prgb+=iPixelSize) { // en affectant la valeur moyenne ds composantes (R, V et B) // à chacunes des composantes bTemp = BYTE( 0.2903 * float(*(prgb)) + 0.6051 * float(*(prgb+1)) + 0.1046 * float(*(prgb+2))); *(prgb) = BYTE(bTemp); *(prgb + 1) = BYTE(bTemp); *(prgb + 2) = BYTE(bTemp); } return NOERROR; } //-------------------------------------------------------------------------// NonDelegatingQueryInterface: Permet de récupérer notre interface // IFiltreModif //-------------------------------------------------------------------------STDMETHODIMP CFiltreModif::NonDelegatingQueryInterface(REFIID riid, void **ppv) { CheckPointer(ppv,E_POINTER); if(riid == IID_IFiltreModif) return GetInterface((IFiltreModif *) this, ppv); else return CTransformFilter::NonDelegatingQueryInterface(riid, ppv); } //------------------------------------------------------------------------// GetParams: Permet de récupérer les paramètes du filtre //------------------------------------------------------------------------STDMETHODIMP CFiltreModif::GetParams(ParamPourFiltreModif *irp) { CheckPointer(irp,E_POINTER); // Vérifie que cela ne pointe pas n'importe où *irp = m_ParamPourFiltreModif; return NOERROR; } //------------------------------------------------------------------------// SetParams: Permet de fixer les paramètes du filtre //------------------------------------------------------------------------STDMETHODIMP CFiltreModif::SetParams(ParamPourFiltreModif *irp) { m_ParamPourFiltreModif = *irp; return NOERROR; } //-------------------------------------------------------------------------// DllRegisterServer: Fonction appelée pour l'enregistrement // (appelée par regsrv32 par exemple ...) //-------------------------------------------------------------------------STDAPI DllRegisterServer() { return AMovieDllRegisterServer2( TRUE ); } //-------------------------------------------------------------------------// DllUnregisterServer: Idem //-------------------------------------------------------------------------STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2( FALSE ); } Utilisation du filtre Le filtre doit être préalablement installé. render.cpp #include "FiltreModif.h" // pour que le CLSID du filtre modifiant soit connu IGraphBuilder *pGraph = NULL; IBaseFilter *pFiltrePerso = NULL; // Pointeur si notre filtre modifiant IMediaControl *pControl = NULL; // Pointeur sur interface de control du graph IFiltreModif *pIPE=NULL; // Pointeur sur l'interface de notre filtre #define TestStatus(h,x) if FAILED(h) { printf("%s\n",x); goto EXIT; } void ReleaseAll(void) { if(pGraph!=NULL){ pGraph->Release(); pGraph=NULL;} if(pFiltrePerso!=NULL){ pFiltrePerso->Release(); pFiltrePerso=NULL;} if(pControl!=NULL){ pControl->Release(); pControl=NULL;} if(pIPE!=NULL){ pIPE->Release(); pIPE=NULL;} } int main(int argc, char* argv[]) { HRESULT hr = CoInitialize(NULL); TestStatus(hr,"ERROR - COM cannot be initialized"); // Creation de l'objet IGraphBuilder hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); TestStatus(hr,"ERROR - can't create IGraphBuilder."); // Récupération de notre filtre modifiant et ajout au graphe hr = CoCreateInstance(CLSID_FiltreModif, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pFiltrePerso); TestStatus(hr,"ERROR - can't create FiltreModif."); hr = pGraph->AddFilter(pFiltrePerso, L"Filtre Modifiant"); TestStatus(hr,"ERROR - can't add filter"); hr = pGraph->RenderFile(L"./LOTR.avi",L""); TestStatus(hr,"ERROR - can't render file."); // sauvegarde du graphe SaveGraphFile(pGraph,L"./Graphe_genere.GRF"); // Récupération de l'interface sur notre filtre hr = pFiltrePerso->QueryInterface(IID_IFiltreModif, (void **)&pIPE); TestStatus(hr,"ERROR - can't get IFiltreModif interface."); ParamPourFiltreModif ParametreDuFiltre; ParametreDuFiltre.param1=0; // par defaut, ne fait rien pIPE->SetParams(&ParametreDuFiltre); // Demande de recuperation de l'interface de control du graphe // (essentiellement pour le lancer et stopper) hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); TestStatus(hr,"ERROR - can't create IMediaControl."); hr = pControl->Run(); TestStatus(hr,"ERROR - can't run."); MessageBox(NULL, "click to end", "Message", MB_OK); EXIT: ReleaseAll(); CoUninitialize(); return 0; } 2/ video renderer : texture 3D exemple: voir l'exemple Texture3D9 3/ Terminer sur la personnalisation des compositeurs. VMR9 : allocateur (cf la documentation). et VMR9multi Interface COM: version ATL This appendix gives a quick overview of how to use the ATL smart pointer classes, CComPtr and CComQIPtr. One of the central ideas in COM is that objects manage their own lifetimes. An object is created with a reference count of 1. Whenever a function receives an interface pointer, it must release the pointer after it’s done using it. Whenever a function gives away an interface pointer, it must AddRef the pointer. Once the object’s reference count goes to zero, the object deletes itself. Reference counts save you from having to figure out which code path is the last to use an object, because your code never explicitly deletes the object. As long as every function manages its own reference counts correctly, everything works. That’s the theory. In practice, getting reference counts right can be rather irritating. It’s all too easy to overlook a stray AddRef or Release, and missing just one will result in memory leaks or objects that prematurely delete themselves. This is where smart pointers can make your life easier. A smart pointer is a C++ class that manages a COM interface pointer, including the reference count. There are various smart pointer classes available. For example, Microsoft Visual C++ defines a _com_ptr_t class. This book uses the ATL classes, but the principles are the same. Declare a CComPtr object in the following way. CComPtr<ISomeInterface> pA; // "ISomeInterface" is any COM interface. The interface type is an argument to the class template, and the CComPtr object contains a raw interface pointer of that type. The class overloads operator->() and operator & (), so you can access the interface pointer transparently. hr = GetSomeObject(&pA); pA->SomeMethod(); behavior. // The & operator has the expected behavior. // The '->' operator also has the expected When the CComPtr object goes out of scope, it automatically releases the interface — but only if the interface pointer is valid. If the underlying pointer was never initialized in the first place, the CComPtr object safely goes out of scope without calling Release. That means that you can write code that looks like the following. HRESULT MyFunction() { CComPtr<ISomeInterface> pA; HRESULT hr = GetSomeObject(&pA); // Returns an interface pointer. if (FAILED(hr)) { return hr; // Because pA was not initialized, it is not released. } pA->SomeMethod(); // On exit, pA is released correctly. } Without smart pointers, if you exit in the middle of a function, you have to carefully work out which pointers to release and which are still NULL. Or else, to avoid that problem, you end up with endlessly nested SUCCEEDED tests. The CComPtr destructor handles it all for you. The equality and inequality operators are overloaded, and the CComPtr object evaluates to true only when the interface pointer is valid. With this feature, you can use an if test to check the validity of the pointer before trying to dereference it. if (pControl) // Is this a valid pointer? { pControl->Run(); } The CComPtr object also provides a helper method for calling CoCreateInstance. Internally, the CComPtr::CoCreateInstance method calls the COM function of the same name with the correct interface identifier (IID) and reasonable default values for the other parameters (NULL for pUnkOuter and CLSCTX_ALL for dwClsContext, although you can specify other values). CComPtr<IGraphBuilder> pGraph. pGraph.CoCreateInstance(CLSID_FilterGraph); Notice that you don’t have to coerce pGraph to a void** type, because the smart pointer knows what the type is. This prevents insidious errors caused by passing in the wrong pointer, which can easily happen in regular COM programming because the void** cast circumvents the type checking done by the compiler. The CComPtr::QueryInterface method works similarly. CComPtr<IMediaControl> pControl. // Query the pGraph pointer for the IMediaControl interface. pGraph.QueryInterface(&pControl); There may be times when you need to release a smart pointer explicitly, before it goes out of scope — for example, if the pointer is reinitialized inside a loop. In that case, call the CComPtr::Release method to release the interface. Unlike derefencing the raw pointer and calling Release, the smart pointer’s Release method is always safe to call, even when the raw pointer is not valid. pGraph.Release(); // Releases pGraph. If you return a pointer from a function, the pointer must have an outstanding reference count according to the rules of COM. (It’s the caller’s responsibility to release the interface.) Use the Detach method to return a pointer without releasing it. void GetObject(ISomeInterface **ppI) { CComPtr<ISomeInterface> pI; // Initialize pI to a valid pointer (not shown). // Return the interface pointer to the caller. *ppI = pI.Detach(); // When pI goes out of scope, it does not call Release. // The caller must release the interface. } This code is functionally equivalent to the following. void GetObject(ISomeInterface **ppI) { ISomeInterface *pI; // Initialize pI to a valid pointer (not shown). **ppI = pI; // Copy the raw pointer to the [out] parameter. } Finally, if you want to be really terse, the CComQIPtr class can be used to call QueryInterface in zero lines of code — the class constructor automatically calls QueryInterface. The constructor argument is the interface pointer that you want to query. // Query the pGraph pointer for the IMediaControl interface. CComQIPtr<IMediaControl> pControl(pGraph); if (pControl) // Did the QueryInterface call succeed? { pControl->Run(); // OK to dereference the pointer. } Smart pointers take a little while to get used to, but in the long run they can save you a lot of time debugging reference-count problems. The book ATL Internals, by Brent Rector and Chris Sells, has a good description of the ATL smart pointers. For a discussion of the COM reference counting rules in general, see Inside COM, by Dale Rogerson (Microsoft Press). Annexe: convention d'appel des fonctions All arguments are widened to 32 bits when they are passed. Return values are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX:EAX register pair. Larger structures are returned in the EAX register as pointers to hidden return structures. Parameters are pushed onto the stack from right to left. The compiler generates prolog and epilog code to save and restore the ESI, EDI, EBX, and EBP registers, if they are used in the function. Note When a struct, union, or class is returned from a function by value, all definitions of the type need to be the same, else the program may fail at runtime. For information on how to define your own function prolog and epilog code, see Naked Function Calls. The following calling conventions are supported by the Visual C/C++ compiler. Keyword Stack cleanup Parameter passing Pushes parameters on the stack, in reverse order __cdecl Caller (right to left) Pushes parameters on the stack, in reverse order __stdcall Callee (right to left) __fastcall Callee Stored in registers, then pushed on stack thiscall Callee Pushed on stack; this pointer stored in ECX (not a keyword) For related information, see Obsolete Calling Conventions. The following example shows the results of making a function call using various calling conventions. This example is based on the following function skeleton. Replace calltype with the appropriate calling convention. void calltype MyFunc( char c, short s, int i, double f ); ... void MyFunc( char c, short s, int i, double f ) { … } … MyFunc ('x', 12, 8192, 2.7183); __cdecl The C decorated function name is "_MyFunc." The __cdecl calling convention __stdcall and thiscall The C decorated name (__stdcall) is "_MyFunc@20." The C++ decorated name is proprietary. The __stdcall and thiscall calling conventions __fastcall The C decorated name (__fastcall) is "@MyFunc@20." The C++ decorated name is proprietary. The __fastcall calling convention