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


5ème leçon: Prévisualiser un flux vidéo


Pour suivre les cinquième et sixième leçons, 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 4). Nous allons travailler à capturer une séquence vidéo depuis une caméra vidéo DV et l'enregistrer sur le disque dur. Comme amorcé à la leçon 4, on s'attachera simplement à créer les fonctions de construction de graphe, logées dans un datamodule, et on laissera le lecteur compléter lui-même l'interface avec l'utilisateur. Dans la  présente leçon 5 on construira seulement les fonctions de prévisualisation. Dans la leçon 6 on complètera le graphe pour pouvoir enregistrer le fichier sur le disque.

1. Sélectionner la source de capture vidéo

Pour commencer, créez un nouveau projet delphi, ajoutez-lui un nouveau datamodule - qu'on appelera CaptureModule. Pour être tranquille, ajoutez dans la clause Uses du datamodule les trois unités fournies par Henri Gourvest, à savoir DSpack, DSutil et directshow9. Ajoutez aussi dans l'évènement destroy du datamodule, le code de fermeture de graphe comme vu dans les leçons précédentes. Dans la fenêtre du datamodule, faites glisser un composant filtergraph - qu'on appelera CaptureGraph dans sa propriété name- et un composant filtre - qu'on appellera CaptureFiltre. Comme vu lors des leçons précédentes, mettez la propriété filtergraph de CaptureFiltre à CaptureGraph.

On pourrait ensuite fixer à la main sa propriété BaseFilter. Pour une fois, il faudrait ne pas aller dans les filtres de directshow, mais chercher dans la catégorie Video Capture Sources, et y choisir l'une des sources de capture disponibles.

Dans cette catégorie de filtres, on trouve des choses assez différentes, comme on le voit chez moi. On peut trouver aussi bien des webcam, des cartes d'acquisition analogiques que des caméras numériques DV branchées sur une carte Firewire. A noter que les cartes d'acquisition analogiques (par exemple Pinnacle DCxx MJPEG Capture Filter) figurent ici même si rien n'est connecté dessus, alors que la caméra DV n'apparaît sous le titre Microsoft DV cameras and VCR que si elle est branchée et allumée. C'est la raison pour laquelle il est en fait peu judicieux de fixer la propriété BaseFilter dès la création de l'application, car vous ne savez pas si la caméra sera branchée au moment où elle sera lancée. Les applications de capture vidéo laissent en général l'utilisateur choisir la source de capture après avoir lancé le programme. Pour cela il faut énumérer dans une liste les sources de capture disponibles et ensuite fixer le BaseFilter de CaptureFiltre par programme, en fonction de la source qu'il aura choisie.

C'est l'objet des deux premières fonctions que nous implanterons dans le datamodule CaptureModule. D'une part GetCaptureSources qui liste les sources de capture disponibles au moment où on l'appelle, et d'autre part SetCaptureSource qui met la propriété Basefilter de notre filtre CaptureFiltre à la valeur choisie. Les deux fonctions renvoient True si tout a bien marché et False s'il y a eu une erreur.

 function TCaptureModule.GetCaptureSources(SourcesList:TStrings):Boolean;

 

{Notre fonction doit remplir une variable SourcesList de type TStrings avec tous les noms de sources de capture trouvées. Typiquement quand vous appelez cette fonction, vous lui fournissez comme paramètre les items d'une combobox de votre programme principal. De cette manière l'utilisateur pourra choisir sa source en la sélectionnant dans la combobox.}

    var

      i: integer;

      SysDev:TSysDevEnum;

{TSysDevEnum est une classe définie dans DSutil, qui permet d'énumérer une catégorie de filtres présents sur le système. Voyez dans l'aide de DSPack les propriétés et méthodes de TSysDevEnum.}

    begin

      result:=false;

    try

    try

      SourcesList.Clear;

      SysDev:= TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);

{Dans la méthode create de TSysDevEnum, on définit la catégorie des sources de capture à énumérer, ici par le paramètre CLSID_VideoInputDeviceCategory. Vous pouvez essayer avec d'autres catégories, comme par exemple CLSID_VideoCompressorCategory ou CLSID_AudioInputDeviceCategory etc... qui sont définies dans l'unité directshow9.}

 

      if SysDev.CountFilters > 0 then

      for i := 0 to SysDev.CountFilters - 1 do

        SourcesList.add(SysDev.Filters[i].FriendlyName);

      result:=true;

    except

    end;

    finally

      SysDev.Free;

    end;

    end;

Si la fonction GetCaptureSources a bien marché, la fonction SetCaptureSource permet de choisir la source qui deviendra active dans notre graphe, à travers notre filtre CaptureFiltre

    function TCaptureModule.SetCaptureSource(SourceName:string):Boolean;

{Cette fonction prend comme paramètre un nom de source de capture. C'est-à-dire typiquement l'un des noms chargés par la fonction GetCaptureSources dans une liste.}

    var

      i:integer;

      SysDev:TSysDevEnum;

    begin

      result:=False;

    try

    try

      CaptureGraph.active:=false;

      SysDev:= TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);

      for i:=0 to SysDev.CountFilters-1 do begin

      if SysDev.Filters[i].FriendlyName=SourceName then break;

      end;//for i

      if i=SysDev.CountFilters then exit;

{Elle recharge un représentant de la classe TSysDevEnum avec tous les noms des sources de capture présentes sur le système, et cherche dans la liste celui qui porte le nom souhaité.}

      CaptureFiltre.BaseFilter.Moniker := SysDev.GetMoniker(i);

{Elle fixe alors la propriété BaseFilter de notre CaptureFiltre, en ayant recours à la notion de Moniker, concept de la programmation COM qu'il serait trop long d'expliquer ici. Si cela vous intéresse, voir par exemple la théorie fournie par Microsoft ici.}

      CaptureGraph.Active:=true;

      result:=true;

    except

    end;//except

    finally

      SysDev.Free;

    end; //finally

    end;

 2. Construire un graphe de prévisualisation

A partir d'ici, on suppose que l'utilisateur a choisi un camescope numérique DV en sélectionnant Microsoft DV cameras and VCR ou son équivalent dans une autre langue. En Français on trouve parfois Caméra et magnétoscope numériques DV Microsoft comme me l'a signalé Frank Allimant. On dispose alors d'un filtre CaptureFiltre qui est inscrit dans le graphe CaptureGraph et dont le BaseFilter a été chargé pour qu'il indique la bonne source de capture.  Si on a activé la propriété GraphEdit du CaptureGraph, alors on peut voir le graphe suivant, (en tapant CTRL-G sur la fenêtre de GraphEdit):

Le filtre CaptureFiltre fournit en sortie deux broches (en Anglais pins). La seconde DV A/V Out donne exactement ce qui est entré, c'est-à-dire un flux de format interleaved comprenant aussi bien l'audio que la vidéo (voir la leçon 4 sur les DV de type I pour comprendre). La première DV Vid Out donne un flux de format video, qui ne comprend donc plus le son. Pour voir les propriétés d'une broche, cliquez dessus avec le bouton droit et regardez sous Pin Proprerties....

Pour obtenir une prévisualisation "économique", c'est-à-dire qui ne sollicite pas trop le processeur, vous pouvez "rendre" le flux de la broche DV Vid Out qui présente la vidéo capturée seule. Essayez de le faire vous-même, par exemple en sélectionnant la broche dans une pinlist, à l'instar de ce que nous avons vu dans la leçon 4, et en appliquant à ce pin une fonction render, fonction qui est dans l'interface IGraphBuilder de notre filtergraph. Le problème de cette méthode est que l'on ne traite pas le son. Cependant si l'enjeu est d'éviter de perdre des frames dans une configuration pas hyper-rapide, c'est la méthode la plus adaptée. Beaucoup de logiciels de capture ne donnent pas le son pendant la prévisualisation.

Nous allons cependant utiliser une fonction spécialisée des graphes de capture qui s'appelle renderstream et l'appliquer à la seconde broche, de manière à pouvoir traiter aussi bien l'image que le son. Commencez par regarder la référence de renderstream dans msdn. Pour pouvoir l'utiliser, il faut invoquer l'interface ICaptureGraphBuilder2 de CaptureGraph, ce qui suppose que vous ayiez ajusté une nouvelle propriété du graphe CaptureGraph qui est le mode, en le mettant non plus à gmNormal comme c'est le cas par défaut, mais à gmCapture.

Une fois cela fait, la fonction de construction de la prévisualisation peut être écrite comme suit:

function TCaptureModule.buildpreviewgraph(vw:IBaseFilter):Boolean;

 

{Cette fonction prend comme argument un IBaseFilter, qui est typiquement la videowindow dans laquelle la prévisualisation doit intervenir. Bien entendu, la propriété filtergraph de cette videowindow doit avoir été fixée à CaptureGraph.On appelera la fonction par une instruction du type:

    CaptureModule1.buildpreviewgraph(videowindow1 as IBaseFilter);

Le résultat de la fonction est True si tout s'est bien passé, et False sinon.}

 

    var hr:HResult;

{HResult est le type des codes d'erreur que fournit directshow. En général quand une fonction directshow a bien marché, elle renvoie le message d'erreur S_Ok. Ici on verra cependant que le résultat attendu est différent.}

    begin

      result:=false;

      try

      hr:=(CaptureGraph as ICaptureGraphBuilder2).RenderStream

        (@PIN_CATEGORY_PREVIEW,@MEDIATYPE_Interleaved,

        CaptureFiltre as IBaseFilter,nil,vw);

{Les paramètres de renderstream permettent d'abord d'indiquer à quelle catégorie de pins on veut s'adresser: ici on précise qu'on veut rendre un pin de catégorie preview. On verra dans la leçon 6 une autre catégorie, qui est celle de capture. Le second paramètre précise le type de Media que l'on veut rendre. On trouverait @MEDIATYPE_Video ou @MEDIATYPE_Audio sur une carte analogique. Mais ici on s'intéresse à la broche DV A/V Out qui donne vidéo et audio entrelacées. On choisit donc @MEDIATYPE_Interleaved.

Les trois paramètres suivants précisent le filtre de départ, ici bien évidemment c'est notre CaptureFiltre; puis un filtre intermédiaire à utiliser, ici nil pour n'en imposer aucun; et enfin le filtre d'arrivée, ici vw qui est le IBaseFilter fourni par notre fonction buildpreviewgraph.}

      if (hr<>VFW_S_NoPreviewpin) then exit;

{Après avoir appelé renderstream, le code d'erreur attendu est VFW_S_NoPreviewpin. En effet, renderstream part de notre CaptureFiltre et y cherche une broche de type preview. Ce type de broches existe sur les cartes analogiques, mais pas sur les sources DV. D'où un message d'erreur, qui ne traduit en fait pas une erreur. Bien mieux, n'en trouvant pas sur notre CaptureFiltre, renderstream se débrouille pour en créer un en ajoutant au graphe un filtre spécial qui s'appelle Smart Tee, et qui a pour seule fonction de dupliquer le flux initial en un flux de type preview et un flux de type capture. }

      CaptureGraph.Play;

      result:=true;

      except

      end;//except

    end;

Tout cela peut paraître assez obscur, mais s'éclaire si on utilise graphedit pour visualiser les choses. On aura obtenu après la fonction buildpreviewgraph, le graphe suivant - souvenez-vous qu'il faut mettre la propriété graphedit de CaptureGraph à true pour pouvoir visualiser le graphe de notre application delphi dans graphedit.

 

On voit ici que le filtre Smart tee a été automatiquement ajouté, et qu'il comporte deux broches de type Preview et capture. Le reste est maintenant bien connu: DV Splitter sépare les flux audio et video, DV Video Decoder décompresse la video dvsd et on l'envoie ensuite dans une videowindow qui s'appelle ici vw. La différence avec la première méthode qui partait de DV Vid Out est l'ajout de Smart Tee et de DV Splitter. Cela prend incontestablement un peu plus de temps et peut être rédhibitoire sur les petites configurations. Mais l'avantage est que cela va permettre d'ajouter un rendu du son. C'est la présence d'une broche AudOut00 dans le filtre DV Splitter qui va nous permettre de conclure sur ce point.

3. Rendre le son pendant la prévisualisation

L'idée est d'appliquer maintenant la fonction renderstream en partant du filtre DV Splitter, et en demandant un media de type @MEDIATYPE_Audio. Les autres paramètres de renderstream sont mis à nil de manière à laisser directshow faire au mieux. Il choisira notamment comme filtre d'arrivée le filtre par défaut de rendu du son. Le plus dur dans ce morceau de programme (qui a vocation à s'insérer dans le précédent) est de trouver par programme le filtre de départ DV Splitter. Voici comment je procède:

Dans les déclarations de variables de la fonction précédente, ajouter:

      fl:TFilterList;

      i:integer;

      fo:TFilterInfo;

Puis dans l'implémentation de notre fonction, avant CaptureGraph.play, ajouter le code suivant:

      fl:=TFilterList.create(CaptureGraph as IFiltergraph);

{TFilterList est une classe qui permet d'énumérer les filtres contenus dans un graphe. Il fonctionne un peu comme TPinlist vu dans la leçon précédente.}

      for i:=0 to fl.count-1 do begin

      fo:=fl.filterinfo[i];

{Filterinfo est une fonction qui s'applique à une TFilterList (voir l'aide de DSPack) et qui met dans fo différentes informations sur le filtre d'index i, en particulier une propriété achName qui n'est autre que le nom familier du filtre.}

      if fo.achName='DV Splitter' then break;

      end;//for

      if i<fl.Count then hr:=(CaptureGraph as ICaptureGraphBuilder2).

        RenderStream(nil,@MEDIATYPE_Audio,fl[i],nil,nil);

      fl.Free;

{ Une fois qu'on a trouvé le bon filtre, c''est-à-dire celui dont le nom est DV Splitter, on appelle renderstream, en laissant la catégorie du pin à nil, en précisant le format du media comme étant de l'audio, et en prenant bien sûr notre filtre DV Splitter comme point de départ, sans fixer de point d'arrivée qui est laissé à l'appréciation de directshow...}

Le graphe résultant de tout cela est maintenant le suivant:

ce qui était bien le résultat souhaité.

4. Prévisualisation économique

Maintenant que nous maîtrisons bien la fonction renderstream, on peut l'utiliser pour la prévisualisation "économique", qui n'utilise pas le son. Vous avez sûrement deviné comment l'on fait, et je vous livre le code sans commentaire:

    function TCaptureModule.economicpreviewgraph(vw:IBaseFilter):Boolean;

      var hr:HREsult;

    begin

      result:=false;

    try

      hr:=(CaptureGraph as ICaptureGraphBuilder2).RenderStream(nil,@MEDIATYPE_Video,CaptureFiltre as IBaseFilter,nil,vw);

      if hr<>S_OK then exit;

      CaptureGraph.Play;

      result:=true;

    except

    end;//try except

    end;

GraphEdit confirme le résultat, qui est effectivement plus économique en ressources du processeur que celui obtenu précédemment, mais qui ne comporte pas de son.

Si vous utilisez ces différents programmes pour faire une prévisualisation de ce que vous voulez enregistrer, vous remarquerez sans doute deux choses: D'une part, on peut prévisualiser aussi bien ce qui vient en Live d'un camescope DV que ce qui vient de la cassette (mode VCR). D'autre part l'image présente des zébrures d'entrelacement dans les mouvements rapides, sauf si votre fenêtre videowindow de prévisualisation ne dépasse pas 288 pixels de haut, ce qui explique sans doute que beaucoup de logiciels de capture ne comportent qu'une petite fenêtre de prévisualisation. Dans mon freeware CaptureFlux j'ai sensiblement amélioré les choses, permettant dans certains cas même une prévisualisation fluide en plein écran pour les heureux possesseurs d'une configuration musclée.

5. Conclusion

Au terme de cette leçon, vous avez appris à lister toutes les sources de capture présentes sur votre configuration, et à choisir celle qui vous intéresse pour ajouter le filtre correspondant à un filtergraph . Je vous ai donné les deux fonctions fondamentales que vous pourrez utiliser pour beaucoup d'usages de ce type, à condition de construire vous-même votre interface avec l'utilisateur. Vous avez ensuite vu deux manières de construire un graphe de prévisualisation pour une source de capture DV, l'une "économique" sans le son, l'autre permettant d'avoir le son prendant la prévisualisation. Pour construire ces graphes on a appris à passer un filtergraph en mode Capture, à invoquer son interface ICaptureGraphBuilder2, à utiliser la puissante fonction renderstream. On a aussi vu au passage comment lister les filtres d'un graphe, et obtenir leurs propriétés, notamment leur nom. On a développé quelques considérations sur les codes d'erreur de directshow, sur les broches de type Preview ou Capture, et sur le format des media qui passent à travers ces broches.

Dans la leçon suivante, on continuera à développer les questions de capture, en enregistrant maintenant dans un fichier sur disque le flux vidéo capturé. On peut télécharger ci-dessous le code source des fonctions utilisées dans cette leçon (dans une version delphi 7), avec une interface extrêmemnt sommaire que je vous laisse le soin d'améliorer.

 

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

Leçon 4 pink05_back.gif pink05_next.gif Leçon 6  

retour vers le haut de la page