FAQ DirectX
FAQ DirectXConsultez toutes les FAQ
Nombre d'auteurs : 4, nombre de questions : 63, dernière mise à jour : 14 juin 2021
- Qu'est-ce qu'une ressource multimédia ?
- Qu'est-ce qu'un codec ?
- Principe de base d'un « stream » ou « flux multimédia » et relation avec DirectShow ?
- DirectShow et COM ?
- Qu'est-ce que l'interface IGraphBuilder ?
- Et les autres interfaces ?
- Pourquoi utiliser les CComPtr ?
- Comment manipuler les interfaces avec les CComPtr ?
- L'ordre de destruction des interfaces est-il important ?
- Qu'est-ce que HRESULT ?
- À quoi sert Graphedit ?
- Qu'est-ce qu'une PIN ?
- Qu'est-ce que Connect Intelligent ?
- Pourquoi DirectShow fait-il un usage massif de la programmation défensive ?
Une ressource multimédia contient les données audio, vidéo, sous-titres, ou l'ensemble, et tout ce qui peut permettre d'identifier ce contenu.
Elle peut venir de différentes sources :
- fichier classique (stocké sur un disque dur, clé USB, etc.…) ;
- webcam (au travers de l'USB) ;
- micro ;
- télévision (carte d'acquisition TV) ;
- réseau (Internet).
Un codec est l'abréviation de « codeur-décodeur ». C'est une sorte de programme qui code ou décode un flux multimédia.
Son existence tient au fait que les formats multimédias sont très divers (format compressé, crypté, empaqueté).
Exemple : le format audio MP3 n'est pas lisible tel quel par la carte son. C'est un format compressé qui permet un gain de place, et qui doit subir un traitement avant de pouvoir être lu par la carte son. Le codec va s'occuper de ce traitement (une décompression du format MP3 vers un format LPCM pour cet exemple).
Les codecs sont reconnaissables à leur extension (.ax, .dll, etc.).
Plusieurs sites connus permettent de télécharger des packs de codec. Ces codecs vont vous permettre de lire différents formats vidéo.
Pour comprendre DirectShow, il faut connaître le principe de base de fonctionnement d'un stream. Ou comment à partir d'une ressource multimédia, on en arrive au résultat final qui sera l'émission d'un son, d'une image ou les deux en même temps. Les cas de figure peuvent être différents suivant le résultat recherché.
Nous donnons ici en exemple la lecture d'un fichier au format WAV et la lecture d'un fichier au format MPEG2. Le fichier se trouve sur le disque dur et nous voulons diffuser l'image ou le son sur le poste.
Les différents traitements à effectuer sont les suivants.
.WAV
- Récupération du fichier sur le disque dur.
- Récupération des données audio (les données sont déjà dans un format utilisable par la carte son).
- Rendu du son.
.MPEG2
- Récupération du fichier sur le disque dur. Comme la taille est importante, la récupération se fait en plusieurs fois.
- Démultiplexage des données : les données récupérées sont constituées de données audio et vidéo qu'il faut séparer. En effet les données audio sont destinées à la carte son et les données vidéo à la carte graphique.
- Décompression des données audio d'un côté et décompression des données vidéo de l'autre (dans le format MPEG2, le son et la vidéo sont compressés).
- Rendu du son et de la vidéo.
Dans le traitement MPEG2, vous voyez que la décompression des données audio et vidéo doit se faire en même temps. Un stream s'effectue le plus souvent dans un environnement multithreadé. En fait, toutes les étapes ci-dessus s'effectuent chacune dans un thread différent. Vous commencez à voir apparaître la notion de « filtres DirectShow ».
Un filtre est un programme qui effectue un traitement particulier à l'intérieur d'un flux multimédia.
Pour l'exemple du MPEG2
- « file source filter » : le filtre du fichier source.
- « splitter filter » : le filtre de démultiplexage et de découpage des données.
- « audio decoder filter » : le filtre de décodage audio.
- « video decoder filter » : le filtre de décodage vidéo.
- « audio renderer filter » : le filtre de rendu audio.
- « video decoder filter » : le filtre de rendu vidéo.
Pour l'exemple du WAV
- « file source filter » : le filtre du fichier source.
- « audio parser filter » : le filtre de découpage des données audio.
- « audio renderer filter » : le filtre de rendu audio.
L'intérêt de ce système de filtre est multiple :
- on simplifie la programmation en découpant un programme en sous-programmes ;
- ces filtres pourront travailler en même temps grâce au multithreading ;
- directShow propose une méthode d'interconnexion entre tous ces filtres. On peut donc insérer ou retirer un filtre. On peut créer son propre filtre et l'utiliser avec les filtres existants.
Chaque filtre travaille à une certaine vitesse pour effectuer son traitement. Cette vitesse de traitement peut varier au sein d'un même filtre. Par exemple, pour le décodeur vidéo, certaines trames vidéo prennent plus de temps à être décompressées que d'autres, pour le même fichier. Tous ces filtres doivent effectuer leur traitement en même temps. Le son et l'image doivent être calés, c'est-à-dire que par exemple le son d'une voix humaine doit correspondre au mouvement des lèvres de l'image.
Il faut donc synchroniser tout cela et c'est le rôle du Graph Manager. Vous voyez apparaître la notion de graphe. Un graphe est la représentation de l'ensemble de ces filtres interconnectés.
Comment les API DirectShow vont-elles faire pour utiliser des filtres dont elles n'ont même pas connaissance ? La réponse est COM (Component Object Model).
Un filtre doit répondre à plusieurs exigences. Il doit être enregistré sur le système (commande regsvr32). C'est-à-dire que ce filtre est inscrit dans la base de registre, et qu'il possède un identifiant unique (GUID). Les informations qui sont dans la base de registre permettent de retrouver le fichier sur le disque, et de savoir dans quelles conditions il peut être utilisé.
Pour l'utilisation des API DirectShow, le principe est identique. On parle alors d'interface. Une interface est un élément de votre programme qui va vous permettre d'utiliser des composants prêts à l'emploi, qui sont en général des DLL COM.
Un des avantages du modèle COM, c'est qu'une même DLL basée sur COM, peut être utilisée par différents langages de programmation (C, C++, Delphi, etc.).
La compréhension de COM n'est pas indispensable avec DirectShow : un ensemble de classes de l'API permettent par exemple de programmer un filtre qui respectera les exigences de COM.
Tout ceci implique que pour utiliser les interfaces, il faut initialiser COM dans une application Directshow.
HRESULT hr =
CoInitializeEx
(
NULL
, COINIT_MULTITHREADED);
Et aussi désinitialiser COM, lorsque l'on n'en a plus besoin :
CoUninitialize
(
);
C'est le point de départ de toute programmation avec DirectShow qui veut gérer un stream. Cette interface sera votre « Graph Manager ». Vous allez pouvoir manipuler des filtres, intervenir sur ceux-ci grâce notamment à des « sous-interfaces » de IGraphBuilder, mais ce n'est pas obligatoire, tout dépend de ce que vous voulez faire.
Sa déclaration :
IGraphBuilder*
pGraphe =
NULL
;
Cette interface est capable de construire un graphe de filtre, simplement en prenant en paramètre le fichier à rendre.
Une fois que vous avez construit un graphe à partir d'un fichier, ce serait bien de pouvoir le jouer. Il va donc falloir utiliser des interfaces spécifiques à chaque action. On obtient ces interfaces à partir de l'interface IGraphBuilder.
Quelques exemples décrits de façon incomplète :
- IMediaControl : permet de lancer le stream, de le stopper, de le mettre en pause ;
- IMediaEventEx : permet de récupérer les évènements du stream au sein même de votre application, par l'intermédiaire du « handle » de fenêtre. Vous pourrez savoir par exemple si le stream est terminé (fin de lecture du média) ;
- IVideoWindow : permet à votre application de positionner l'image dans votre fenêtre window, selon vos souhaits ;
- IBasicAudio : permet d'ajuster le niveau de sortie du son. Comme son nom l'indique, ses traitements sur le son restent basiques ;
- IBasicVideo : permet par exemple d'obtenir les dimensions des trames vidéo qui circulent dans le graphe ;
- IMediaSeeking : permet de se positionner dans le flux, par exemple pour commencer la lecture au milieu du fichier ;
- IMediaPosition : permet par exemple de connaître la cadence de lecture d'un fichier et de la modifier ;
- IVideoFrameStep : permet une lecture du fichier par paliers (vers l'avant ou l'arrière).
Il existe d'autres interfaces. Il faut aussi savoir que toutes ces interfaces ne sont pas forcément utilisables. Par exemple si vous lisez un fichier MP3 qui ne contient que des données audio, les interfaces IVideoWindow et IBasicVideo ne seront pas accessibles. De même, certains formats multimédias ne permettront pas le retour en arrière et par conséquent l'interface IVideoFrameStep ne fonctionnera pas pour des valeurs négatives.
DirectShow est basé sur le modèle COM. Si vous voulez être en parfaite adéquation avec ce modèle, et aussi parce que COM possède ses propres méthodes de fonctionnement que vous ne connaissez peut-être pas, les CComPtr sont alors fortement conseillés. Il s'agit de pointeurs intelligents (voir FAQ C++ si vous ne connaissez pas ce concept) conçus pour gérer des pointeurs sur objets COM, que vous pouvez trouver livrés avec Visual Studio par exemple.
Une autre raison, c'est que vous manipulez des pointeurs sur des interfaces. Ces interfaces effectuent en leur sein un comptage de référence : vous pouvez utiliser plusieurs pointeurs sur une même interface dans la même application. Les CComPtr font un comptage de référence et détruisent les interfaces référencées, vous évitant les fuites de mémoire ou l'utilisation d'un pointeur qui aurait été détruit (Release).
// Initialisation des pointeurs
CComPtr<
IGraphBuilder>
pGraphe;
CComPtr<
IMediaControl>
pMediaControl;
HRESULT hr;
Ici on déclare un pointeur (CComPtr) sur l'interface IGraphBuilder et un autre sur l'interface IMediaControl. Par défaut pGraphe et pMediaControl sont initialisés à NULL.
hr =
pGraphe.CoCreateInstance(CLSID_FilterGraph);
Ici on initialise notre pointeur. Le paramètre CLSID_FilterGraph va permettre de définir le composant demandé. C'est son identifiant unique. Cette méthode d'initialisation permet d'obtenir une instance sur un objet qui se trouve sur la machine locale.
hr =
pGraphe->
QueryInterface(IID_IMediaControl, reinterpret_cast
<
void
**>
(&
pMediaControl));
Ici on initialise notre pointeur pMediaControl par l'intermédiaire de l'interface IGraphBuilder. Les parmètres sont l'adresse du pointeur pMediaControl, et son identifiant COM (IID_IMediaControl).
Les interfaces IGraphBuilder et IMediaControl seront détruites (Release) de façon automatique lorsque la durée de vie des pointeurs sera terminée : si votre pointeur est membre d'une classe, ceci se fera lors de la destruction de l'objet de cette classe.
Pour le faire de façon explicite :
pMediaControl =
NULL
;
pGraphe =
NULL
;
Pour plus d'informations :
CComPtr - MSDN
Oui. La règle générale est que la première interface initialisée doit être la dernière interface détruite. De même qu'il faut initialiser COM avant d'utiliser les interfaces, il faut désinitialiser COM après avoir détruit toutes les interfaces.
Le type HRESULT permet de déterminer avec précision la valeur de retour d'une méthode ou d'une fonction COM et de connaître la gravité de celle-ci.
Il existe plusieurs macros de manipulation de ce type, les principales étant :
- FAILED : permet de savoir si la fonction a échoué ;
- SUCCEEDED : permet de savoir si la fonction a réussi ;
- AMGetErrorText : fonction spécifique à DirectShow qui permet de voir la valeur de HRESULT sous forme de texte (voir MSDN).
Graphedit est un outil quasi indispensable qui est fourni avec le SDK. Il permet :
- de charger une ressource multimédia et de la jouer ;
- une visualisation graphique de votre graphe et de ses différents filtres et de leur interconnexion ;
- de connaître les codecs installés sur votre machine et de les insérer dans le graphe ;
- de manipuler les filtres avec de simples clics de souris ;
- de se rendre compte de la compatibilité entre les filtres et si une connexion est possible ;
- de paramétrer les filtres lorsque la possibilité est offerte ;
- et plein d'autres choses encore…
Une PIN est l'endroit d'un filtre qui gère la connexion avec les autres filtres.
Il y a les PIN de sortie et les PIN d'entrée. Un filtre possède une ou plusieurs PIN.
Les propriétés d'une PIN vont permettre de déterminer si la connexion avec une autre PIN est possible. Par exemple la PIN d'entrée d'un filtre de rendu audio n'acceptera pas la connexion avec la PIN de sortie d'un filtre de décodage vidéo.
Lorsque vous appelez la méthode RenderFile de l'interface IGraphBuilder et que la valeur de retour de cette fonction est un succès, alors votre graphe est prêt à être utilisé.
DirectShow a utilisé une méthode appelée Connect Intelligent, qui lui a permis de trouver les filtres adéquats de votre système et qui ont permis la construction du graphe. Les filtres sont connectés entre eux.
Avant d'en dire plus, il faut connaître la notion de merit. Imaginons que vous vouliez lire un fichier MP3. Pour ce faire, vous allez avoir besoin d'un décodeur audio pour le format MP3. Des codecs MP3, il en existe plusieurs, et il est possible que vous ayez plusieurs codecs MP3 enregistrés sur votre machine. Ces codecs font la même chose, ils décodent le format MP3.
Lorsque le graphe est construit, DirectShow va choisir en premier le codec qui a le merit le plus élevé. Si ce codec est compatible avec les autres filtres (on parle de « négociation de PIN »), alors c'est ce filtre qui sera ajouté au graphe.
Lorsque DirectShow construit le graphe, il cherche les filtres enregistrés sur la machine, les fait entrer en négociation et quand toute la chaîne de filtres est connectée, le graphe est construit.
Cette méthode a des avantages et des inconvénients. Heureusement il est possible de modifier ce comportement par programmation et d'imposer l'utilisation d'un filtre particulier, par exemple.
Cette méthode nous assure la construction du graphe, dans l'ordre de mérite des filtres, mais ne nous assure pas que le graphe obtenu est opérationnel. En effet, il peut arriver que certains filtres se connectent entre eux parce qu'ils répondent au critère de la négociation de PIN, mais que le traitement des données qu'ils en font n'est pas du tout celui escompté. Ce comportement arrive souvent lorsque vous installez de multiples packs de codecs et que vous ne comprenez pas pourquoi votre lecteur préféré ne lit plus les vidéos…
D'abord, qu'est-ce que la programmation défensive ?
C'est la technique qui consiste à vérifier la validité de tous les paramètres passés à une fonction :
HRESULT FonctionDefensive(CComPtr<
IGraphBuilder>&
pGraphe, CComPtr<
IMediaControl>&
pMediaControl)
{
if
(pGraphe ==
NULL
||
pMediaControl ==
NULL
)
return
E_POINTER;
....
return
S_OK;
}
Vous manipulez des interfaces COM, qui sont des pointeurs, vous devez vérifier leur validité. Dans un environnement multithreadé, ce n'est pas de la titillation. De plus une classe C++ ainsi élaborée sera réutilisable facilement sans surprise de « segmentation fault ».