Mes contributions sur Delphi:


Cliquez sur la photo pour m'envoyer un e-mail:


Visitez les autres sections du site:

shome.gif


 Visit my English pages


Site optimisé pour un affichage en 1024x768.

 Dernière mise à Jour  de cette page:

le vendredi 29 avril 2005

 

      film.gif


4ème leçon: Convertir un fichier DV


Pour suivre cette quatrième leçon, vous devez avoir de bonnes notions de delphi, avoir installé DSPack et connaître les concepts évoqués dans les leçons précédentes ( voir 1 2 3). Dans la présente leçon 4, nous allons travailler à convertir un fichier DV de type I en un fichier DV de type II, et réciproquement.  Pour ceux qui ne souhaitent pas programmer et qui auraient des problèmes de conversion, signalons qu'ils peuvent télécharger la version 4.0 de DVdate qui intègre entre autres ces fonctions de conversion.


1. Comprendre les graphes de conversion DV

Pour comprendre ce que nous voulons faire, il faut d'abord rappeler ce que sont les DV de type I et les DV de type II. Microsoft explique sur cette page de quoi il s'agit. Les DV de type I ont un seul flux qui mêle audio et vidéo, alors que les DV de type II ont deux flux séparés, l'un pour audio, l'autre pour vidéo. En réalité, il semble que dans le second cas, le dit flux vidéo contienne de manière redondante l'audio, en plus du flux audio séparé. C'est pourquoi les DV de type II prennent plus de place. En ce qui me concerne, je n'utilise cependant que du type II depuis que je me suis retouvé face à Premiere 6.0 sans pouvoir lire des heures de vidéo qui étaient en type I et que j'ai du entièrement convertir en type II.

Nous allons dans un premier temps étudier, grâce à graphEdit, les graphes à construire pour ces conversions. D'abord pour passer du type I au type II:

On part d'un fichier de type1 que l'on prend sur le disque dur, ou sur un CD. C'est le filtre File Source (ASync) qui s'en charge, comme dans les leçons précédentes. Ensuite AVI Splitter est le filtre qui extrait les flux DV de l'encapsulation avi. Comme nous avions un fichier DV de type I, il n'y a qu'un seul flux qui en sort, et c'est un flux mixte audio+vidéo (stream 00). Ce flux passe ensuite dans le filtre DV Splitter qui sait séparer la partie audio de la partie vidéo pour donner deux flux distincts (DVVidOut0 et AudOut00) . Enfin, nous passons par le filtre Avi Mux qui réencapsule les deux flux en un fichier avi que le dernier filtre File Writer écrit sur le disque en un fichier qui sera donc cette fois en DV de type II.

De la même manière, pour convertir un fichier de type II en un fichier de type I, il faudra utiliser le graphe suivant:

Ici on part d'un fichier DV de type II sur le disque dur ou sur un CD, qui est chargé par le filtre File Source (ASync). Ensuite on le passe dans Avi Splitter pour le décapsuler de l'avi et extraire les flux DV. S'agissant d'un fichier de type II, on y trouvera cette fois (au moins) deux flux (Stream 00 et Stream 01). Ensuite on les passe dans le filtre DV Muxer (ne pas confondre avec Avi Mux) qui les combine en un seul flux DV mixte. Enfin AVI Mux le réencapsule en avi, et File Writer écrit le résultat en un fichier sur le disque dur.

Vous aurez noté que dans les deux cas de figure, on n'a procédé à aucune recompression, mais simplement retricoté ou détricoté les samples audio et vidéo, en utilisant DVSplitter pour détricoter et DV Muxer pour retricoter. Essayez maintenant de construire vous aussi ces graphes directement dans GraphEdit. Quatre indications pour cela: tous les filtres cités sont dans la rubrique DirectShow Filters et sont des filtres standard fournis par Microsoft. Voyez dans la leçon1 comment les installer dans le graphe. En second lieu, pour relier deux pins par une fléche, vous cliquez sur le premier pin et glissez la souris jusqu'au second pin, avec le bouton gauche enfoncé. Troisièmement, je vous recommande de commencer par la conversion de type II en type I, car il faut trouver un fichier vidéo correspondant pour alimenter File Source (Async) et il y a plus de chance que vous ayez des clips DV de type II, ou si vous n'en avez pas vous pouvez en fabriquer avec DVDate cité plus haut. Enfin, veillez à donner à votre fichier de destination un nom différent ou un dossier différent de celui du fichier source.

Là aussi, comme dans les leçons précédentes, vous pourrez constater que nos graphes sont pleinement opérationnels. Si vous cliquez sur Play vous constaterez (au bout d'un temps plus ou moins long) que la conversion a effectivement été faite. Si le temps vous paraît trop long, vous pourrez l'interrompre en cliquant sur stop .

retour vers le haut de la page


2. Saisir la source et la destination (par programme)

Nous commençons maintenant notre programme delphi. J'aime bien dans un projet directshow séparer le plus possible la partie relative à la construction des graphes, que je mets dans un datamodule, et la partie qui constitue l'interface avec l'utilisateur, qui peut être dans la form principale. Nous allons créer donc un Datamodule comprenant les composants de DSPack , et définissant deux fonctions buildgraphDV2to1 et buildgraphDV1to2.

Ouvrez donc un nouveau Datamodule, nommez le dm pour faciliter les références à ses composants ou à ses fonctions et nommez DVConvertirUnit l'unité qui se trouve derrière. Jetez sur le datamodule dm un Tfiltergraph (1er composant de l'onglet DSPack) et deux composants TFilter (quatrième composant de l'onglet DSPack). On appellera le 1er composant TFilter Source et le second Destination. Ils représenteront la source et la destination du programme de conversion.

La première chose à faire est de placer ces composants filtres dans le graphe filtergraph1 et de préciser leur BaseFilter, c'est-à-dire de préciser quel filtre enregistré sur le système ils représentent. Sans surprise, ces deux filtres seront respectivement File Source (Async) et File Writer.  Cela se fait dans les propriétés du composant selon les schémas ci-après, par exemple pour source:

Attention, chez moi dans certains cas quand on clique sur l'éditeur de propriétés de BaseFilter une erreur se produit. Il faut alors enregistrer le projet, quitter delphi et le relancer pour que tout rentre dans l'ordre. A nouveau les caprices de nos bestioles...

Ensuite il faut commencer à implanter nos deux fonctions dans l'unité ConvertirDVunit derrière le datamodule dm. Je détaillerai seulement la fonction buildgraphDV2to1. Nous allons définir le fichier que l'on veut ouvrir dans Source et celui qu'on veut utiliser comme Destination. Pour cela il faut écrire un peu de code:

    function Tdm.buildgraphDV2to1(type2,type1:string):Boolean;

    var buffer1,buffer2:array[0..255]of widechar;

        wtype1,wtype2:pwidechar;

    begin

      result:=False;

    try

      wtype2:=stringtowidechar(type2,buffer2,255);

         wtype1:=stringtowidechar(type1,buffer1,255);

         filtergraph1.Active:=true;

         (source as IFileSourceFilter).load(wtype2,nil);

         (destination as IFileSinkFilter2).SetFileName(wtype1,nil);

      result:=True;

    except

    end;

    end;

A ce stade, ce n'est que l'embryon d'une fonction qui prend comme argument le nom (avec le chemin) d'un fichier de type2 à convertir, et comme second argument le nom et le chemin du fichier à créer qui sera de type 1. Le résultat de la fonction sera True si la construction du graphe a réussi, et False dans le cas contraire. C'est ce qui est obtenu par la clause try except. Il y a aussi une petite complication dûe au fait que les fonctions de directshow utilisent des pwidechar, alors qu'en delphi nous voulons utiliser des strings normales. D'où la conversion via des buffers de widechar. J''ai limité les buffer à 255 caractères. Il faudra s'en souvenir dans les programmes qui appellent cette fonction.

La partie intéresante de ce petit bout de code vient ensuite. Il faut savoir que IFilesourceFilter et IFileSinkFilter2 sont des interfaces. Certains objets que l'on crée dans directshow ont une ou plusieurs interfaces qui présentent alors de nouvelles propriétés ou méthodes. C'est le cas ici de nos filtres source et destination, car ils sont construits sur des BaseFilter qui prévoient ce mécanisme.  

Attention, pour pouvoir se référer à ces interfaces dans delphi, il faut ajouter à votre clause Uses l'unité qui comprend les headers de directshow. Donc rajouter dans l'en-tête de l'unit::

    Uses directshow9;

Remarque: pour voir quelles sont les interfaces possibles pour un filtre, on peut observer le BaseFilter Editor que nous avons actionné plus haut, et qui fournit les interfaces utilisables, non seulement pour les filtres, mais aussi pour chacun de leurs pins. Par ailleurs on peut avoir ici une liste de toutes les interfaces implantées en standard par Microsoft, mais chaque objet d'un autre éditeur peut comporter aussi ses propres interfaces.

Dans notre exemple, le rôle de ces interfaces et de leurs méthodes load ou setfilename est assez clair. Vous pouvez approfondir cela en cherchant des détails sur IFileSourceFilter et IFileSinkFilter2 dans la référence de directshow.

Ce qui est magnifique avec delphi, c'est l'opérateur as qui permet d'invoquer vraiment très simplement ces interfaces, comparé à ce qu'on peut voir dans la littérature C++.

Enfin, une fois de plus, vous voyez l'importance de gérer la propriété Active du filtergraph. Ici elle doit être à True, sinon les deux interfaces en cause ne seront pas accessibles, et on aura une erreur. C'est d'ailleurs le moment de vous rappeler l'intérêt de mettre dans OnDestroy de la datamodule le petit bout de code vu à la leçon2 qui sécurise la fermeture de l'application:

    filtergraph1.cleargraph;

    filtergraph1.active:=false;

     

A ce stade, nous avons simplement implanté et paramétré les filtres des extrémités dans notre graphe. Il reste à les relier par des filtres capables d'assurer la conversion.

retour vers le haut de la page


3. Connecter les filtres à la main

Pour connecter source à destination, nous allons utiliser une méthode brutale qui ne repose pas du tout sur l'intelligence de directshow, mais consiste au contraire à connecter tous les pins à la main. C'est pour vous montrer une méthode bestiale, mais utile dans bien des circonstances où l'on veut imposer à directshow un comportement qu'il ne prendrait pas spontanément.

D'abord installez sur votre datamodule les 3 filtres supplémentaires dont nous avons besoin: AVI Splitter, DV Muxer et AVI Mux, et mettez leurs propriétés filtergraph sur Filtergraph1, ainsi que leur BaseFilter au filtre correspondant. Si vous avez suivi jusqu'ici, cela devrait être facile. Donnez leur comme nom respectivement avisplitter, dvmuxer, avimux car le nom d'un composant en delphi ne peut pas comporter d'espaces.

Voici le code de la fonction complète:

    function Tdm.buildgraphDV2to1(type2,type1:string):Boolean;

    var   buffer1,buffer2:array[0..255]of widechar;

          wtype1,wtype2:pwidechar;

          sourcepl,avisplitterpl,dvmuxerpl,avimuxpl,destinationpl:TPinlist;

{On déclare pour chacun des 5 filtres de notre graphe, une variable de type TPinList qui listera tous les pins du filtre. Attention, la classe TPinlist est déclarée dans DSUtil, qu'il faut donc ajouter à la clause Uses de votre unité.}

 

    begin

      result:=False;

    try

      wtype2:=stringtowidechar(type2,buffer2,255);

      wtype1:=stringtowidechar(type1,buffer1,255);

      filtergraph1.Active:=true;

      (source as IFileSourceFilter).load(wtype2,nil);

      (destination as IFileSinkFilter2).SetFileName(wtype1,nil);

    except

      exit;

    end;

{C'est en substance le code que nous avions déjà écrit plus haut, sauf que result:=True sera renvoyé plus loin pour n'intervenir que quand le graphe complet a pu être construit.}

 

// créer les listes de pins de chaque filtre

    try

      sourcepl:=TPinlist.create(source as IBaseFilter);

      avisplitterpl:=TPinlist.create(avisplitter as IBaseFilter);

      dvmuxerpl:=TPinlist.create(dvmuxer as IBaseFilter);

      avimuxpl:=TPinlist.create(avimux as IBaseFilter);

      destinationpl:=TPinlist.create(destination as IBaseFilter);

{Ici on crée chacun des Tpinlist à partir du filtre correspondant. Notez que IBaseFIlter est une nouvelle interface qu'il faut mobiliser sur nos filtres pour les utiliser dans le constructeur de TPinList.}

 

// connecter les pins

    try

      sourcepl[0].Connect(avisplitterpl[0],nil);

      avisplitterpl.Update;

      avisplitterpl[0].Connect(dvmuxerpl[0],nil);

      dvmuxerpl.Update;

      avisplitterpl[1].Connect(dvmuxerpl[1],nil);

      dvmuxerpl.update;

      dvmuxerpl[3].Connect(avimuxpl[1],nil);

      avimuxpl[0].Connect(destinationpl[0],nil);

      result:=True;

    except

    end;

{C'est le coeur de notre fonction, où nous connectons un par un les pins souhaités, en les prenant par leur numéro dans la pinlist correspondante. Deux éléments rendent cette partie assez difficile, et font qu'il y a souvent besoin de tâtonnements, qui peuvent être aidés par un recours à GraphEdit. Le premier élément difficile est que l'on ne sait pas a priori quel est le numéro du pin qui nous intéresse. Il faut essayer, vérifier avec GraphEdit (et acquérir un peu de jugeotte par l'expérience). La deuxième difficulté est que certains filtres créent des pins seulement après que les pins précédents aient été connectés. C'est typiquement le cas de Avi Splitter, qui au départ n'a qu'un pin d'entrée, et qui crée autant de pins de sortie qu'il y a de streams une fois la connexion établie. D'où le rôle de la méthode update, qui remet à jour la liste des pins, pour pouvoir désigner les derniers pins créés.}

 

// détruire les listes de pin

    finally

      destinationpl.Free;

      avimuxpl.Free;

      dvmuxerpl.Free;

      avisplitterpl.Free;

      sourcepl.Free;

    end;

       

    end;

{Une fois toutes les connexions établies, on peut détruire les listes de pins.}

 

Il faut noter que cette fonction a une vertu tout à fait intéressante, c'est qu'elle permet aussi de savoir si un fichier est un avi DV de type2, sans pour autant procéder à sa conversion. Si le résultat est True, elle l'est, sinon elle ne l'est pas. Ceci est rendu possible par une particularité: c'est que IFileSinkFilter2 ne fera pas d'erreur pendant la construction du graphe, même si on met une destination erronée, par exemple un chemin qui n'existe pas. Du coup, si le résultat est False, c'est qu'il y a eu une erreur soit due à IFileSourceFilter qui n'a pas pu lire le fichier source, soit parce que les pins n'ont pas pu être connectés, ce qui montre que ce n'est pas un fichier sain de type II.

retour vers le haut de la page


4. Terminer l'application

 

Essayez de terminer vous-même une application complète, permettant de convertir un fichier DV dans les deux sens. Je me contenterai ici de vous donner quelques indications. Pour ceux qui sèchent, je fournis en téléchargement gratuit le code source de l'unité ConvertirDVunit complète sous delphi7 ainsi qu'un projet de démonstration très sommaire. La version 4.0 de DVDate donne un meilleur exemple de ce qu'on peut faire avec ce datamodule, en comprenant des fonctions de conversion de type I en type II et réciproquement.

 

Commencez par implanter dans l'unité du datamodule la deuxième fonction: Tdm.buildgraphDV1to2 avec les mêmes arguments (sauf que vous échangerez type1 et type 2 pour une meilleure lisibilité). C'est très proche de la fonction précédente, sauf qu'il faut utiliser DV Splitter à la place de DV Muxer.

 

Créez ensuite une application qui appellera le datamodule. Vous incluez sur sa Form principale un bouton Ouvrir qui permet de choisir un fichier avi sur le disque dur (ou un lecteur). Dans le code qui gère l'ouverture du fichier appliquez lui les deux fonctions successivement. Si l'une d'elles renvoit True, alors affichez quelque part que c'est un fichier du type correspondant et proposez à l'utilisateur un bouton pour convertir le fichier en l'autre type. Dans le code de ce bouton , il suffira de lancer la méthode play de filtergraph1. Prévoir éventuellement un bouton pour interrompre la conversion, qui appelera la méthode stop. L'un des problèmes est d'ailleurs de savoir à quel moment la conversion est terminée. Pour cela utilisez l'évènement OnGraphComplete du filtergraph1 qui se déclenche justement quand un graphe a été joué jusqu'au bout.

 

Encore une chose: il faut donner un fichier destination dès l'appel aux fonctions. Pour cela soit vous demandez à l'utilisateur de choisir la destination par défaut avant même de choisir le fichier à ouvrir. Soit vous optez pour une destination dans le même dossier que la source mais avec un nom modifié par un ajout, soit vous optez pour un répertoire fixe, par exemple "Mes Documents" . Tout cela n'est pas bien difficile et peut devenir aussi sophistiqué que vous voulez. Si vous voulez non pas convertir un avi mais seulement tester son type, vous pouvez mettre n'importe quoi comme destination. Tant qu'on ne fera pas play, le File Writer ne vérifie pas si la destination est correcte.

retour vers le haut de la page


5. Conclusion

Au terme de cette leçon, vous avez appris à manipuler les filtres dans un programme delphi, et à lister puis connecter leurs pins à la main pour construire une graphe par programme. Vous avez aussi compris ce qu'étaient les DV de type I et les DV de type II. Et vous avez entrevu le monde tout à fait spécifique des interfaces, et vu avec soulagement combien il est facile de les invoquer sous delphi. On vous a aussi signalé l'utilité de l'évènement OnGraphComplete pour savoir à quel moment un graphe a fini de jouer.

 

Ici on peut élécharger le code source de cette leçon (dans une version delphi 7).

Leçon 3 pink05_back.gif  pink05_next.gif Leçon 5

retour vers le haut de la page