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