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 mercredi 9 juin 2004

 

      film.gif


8ème leçon: Extraire le son d'une vidéo


L'objectif de cette leçon est de pouvoir extraire le son d'une vidéo. On essaiera d'en faire d'abord un fichier WAV (non comprimé) puis de comprimer le son pour en faire par exemple du mp3 ou du wma. Pour suivre cette leçon, il est recommandé d'avoir étudié les précédentes, en tous cas d'avoir des une version de delphi avec dspack 2.3. et d'avoir assimilé les premières notions au moins des leçons 1 à 4.

 


1. Récupérer le son non compressé sur une broche

A la leçon 2 on a vu comment ouvrir un fichier multimedia pour le lire dans graphedit ou dans dspack. En langage directshow on appelle cela rendre un fichier, en Anglais render a file. Il est souvent intéressant de commecer par là, car c'est l'intelligence de directshow qui se charge de rouver les filtres utiles, et on s'aperçoit alors souvent qu'il sait traiter beaucoup plus de cas que ce qu'on pourrait prévoir soi-même. Ici notre programme traitera toutes sortes de fichiers avi, mais aussi mpeg ou wmv.

On part donc d'un fichier video appelé fichier que l'utilisateur aura saisi, et que l'on suppose donné par une widestring. On suppose aussi donné un composant Tfiltergraph qui s'appelle fg, et qui est actif (propriété active:=True). Pour rendre le fichier video, on appelle simplement la fonction hr:=fg.renderfile(fichier) et on testera avant d'aller plus loin si hr indique un succès (voir leçon 7).

Si c'est un succès, alors un graphe a été construit, et en principe il restitue le son via un filtre Default DirectSound Device. Voici par exemple un graphe très classique pour un fichier DV avi.

On voit ici que si l'on veut trouver le son, il faudra se brancher sur la broche Stream 01 de Avi splitter juste avant le Default DirectSound Device qu'il faudra enlever, se débarasser aussi de tout ce qui est en aval de Stream 00 car cela concerne la video qu'on ne veut ici pas perdre de temps à traiter.

Je propose donc d'abord une fonction qui permet de chercher tous les filtres de rendu dans un graphe.

 

La première section porte le nom FILTERS, et décrit la liste des filtres qui sont dans le graphe. Pour chacun d'eux on trouve les indications suivantes:

    • numéro du filtre: donné avec 4 chiffres, par exemple 0001
    • GUID du filtre: c'est-à-dire un code du type {1B544C20-FD0B-11CE-8C63-00AA0044B51E}. Ce code est stocké dans le registre pour tous les filtres directshow installés sur votre système, et peut servir à retrouver le filtre. Faites une recherche avec regedit pour le GUID ci-dessus, et vous verrez que c'est le GUID de avi splitter.

     

      • éventuellement si c'est un Source Filter qui charge un fichier, apparaît ensuite:
      • SOURCE nom du fichier: le fichier qui est chargé dans le filtre source, donné par son nom (avec éventuellement le chemin complet), par exemple SOURCE "C:\SomeFile.avi"

       

        • éventuellement si c'est un Sink Filter qui écrit dans un fichier, apparaît ensuite:
      • SINK nom du fichier: le fichier qui est ouvert en mode WRITE pour recueillir le flux, par exemple SINK "D:\Fichier.wav"

       

      • data: des données binaires, dont la longueur est précisée, la plupart du temps dix 0, quand il n'y a pas de données spéciales, donc 0000000000

    La deuxième section, sous le titre CONNECTIONS, comprend la liste des connexions qui ont été établies, chacune donnée sous la forme:

      • filtre d'origine: indiqué par son numéro dans le liste des filtres précédente (4 caractères), par exemple 0002
      • broche d'origine: indiquée par son nom entre guillemets, par exemple "Stream 00".
      • filtre de destination: indiqué de nouveau par son numéro dans la liste des filtres, par exemple 0003
      • broche de destination:indiquée par son nom entre guillemets, par exemple "In".
      • taille des samples: 10 chiffres, par exemple 0000000376
      • major type du media: donné par son GUID, par exemple {73646976-0000-0010-8000-00AA00389B71} pour Vidéo
      • subtype du media: donné par son GUID, par exemple {64737664-0000-0010-8000-00AA00389B71} pour dvsd
      • flags: deux indicateurs qui valent 0 ou 1 (False ou True) selon que les samples sont de taille fixe ou non, puis selon que le media utilise de la compression temporelle ou non
      • format: des données de format, comprenant notamment le GUID du format, par exemple {05589F80-C356-11CE-BF01-00AA0055595A} pour le format dvsd 720x576,24bits

    Si le nombre de sections indiqué avant FILTERS est 0002, alors le fichier ne comporte pas la section suivante.S'il est 0003, alors on rencontre une section introduite par CLOCK, qui définit les paramètres d'horloge pour synchroniser les flux du graphe, sous la forme:

      • required: un indicateur qui vaut 0 ou 1 (False ou True) selon que la synchronisation est requise ou non
      • clockID: un numéro à 4 chiffres, souvent 0000 ou encore un GUID.

    Le fichier est terminé par la chaîne END

    Je me suis amusé à faire un petit programme qui analyse les fichiers GRF en tentant d'en retirer les informations ci-dessus. L'utilité principale est de retrouver les GUID des filtres ou des medias utilisés, lorsque le friendlyname n'est pas suffisamment explicite. Téléchargez-le  ici en version 1.1 en Français (292Ko), sans aucune garantie de ma part, et utilisez-le à vos risques et périls.

    retour vers le haut de la page


    2. Sauver un graphe depuis delphi

    La méthode pour espionner une application directshow, c'est de lancer Graphedit, puis de taper CTRL+G pour ouvrir l'un des graphes actifs. Cela suppose que le graphe de votre application directshow s'est enregistré dans le système, comme on le fait dans delphi en mettant la propriété graphedit du filtergraph à true. Vous pouvez ainsi voir quel est le graphe l'application, et vérifier s'il correspond à vos attentes. Le problème est que cette méthode n'est pas très souple et en particulier ne permet pas de visualiser des graphes intermédiaires pour comprendre comment le graphe se construit.

    La fonction SaveGRF répond à cette préoccupation. En l'appelant n'importe où dans votre programme delphi, vous enregistrez un fichier GRF représentant le graphe, et vous pouvez ensuite (éventuellement en différé) l'ouvrir dans GraphEdit pour voir l'état du graphe au moment où vous l'avez souhaité. Je la donne ici sans commentaires, car elle repose sur des concepts de programmation COM qui dépassent l'objet de ce tutorial. Ceux-ci sont définis dans l'unité ActiveX.dcu de delphi7. Rajoutez donc ActiveX dans la clause uses de votre unité.

    La fonction SaveGRF prend comme argument d'abord un nom de fichier (avec son chemin) sous lequel sera sauvé le graphe. Il a en principe une extension *.grf. Le second est un composant Tfiltergraph, celui dont on veut sauver le graphe. Donc un appel typique de la fonction sera du type:

      saveGRF('d:\Mes Documents\graphe1.grf',filtergraph1);

    En sortie, la fonction retourne True si tout s'est déroulé sans erreur, False dans le cas contraire, et vous pouvez alors ouvrir le fichier obtenu dans GraphEdit ou dans GRFshow.

    {--------------------------------------------------------------------------}

      function saveGRF(fichier:widestring;fg:TFiltergraph):Boolean;

      var  hr:Hresult;

        pStorage:IStorage;

        pStream:IStream;

        pPersist:IPersistStream;

      begin

      result:=false;

      try

      try

      hr:= StgCreateDocfile(pwidechar(fichier),STGM_CREATE or STGM_TRANSACTED or STGM_READWRITE or STGM_SHARE_EXCLUSIVE,0, pStorage);

      if FAILED(hr) then exit;

      hr:= pStorage.CreateStream('ActiveMovieGraph',STGM_WRITE or STGM_CREATE or STGM_SHARE_EXCLUSIVE,0, 0, pStream);

      if FAILED(hr) then exit;

      fg.QueryInterface(IPersistStream,pPersist);

      hr:= pPersist.Save(pStream, TRUE);

      if FAILED(hr) then exit;

      pStorage.Commit(STGC_DEFAULT);

      if FAILED(hr) then exit;

      result:=true;

      except

      end;

      finally

        pStorage:=nil;

        pStream:=nil;

        pPersist:=nil;

      end;

      end;

       

    {--------------------------------------------------------------------------}

    retour vers le haut de la page


    3. Charger un graphe dans delphi

    L'opération inverse consiste à charger un fichier GRF dans un Tfiltergraph implanté dans une application delphi. Cette opération est parfois très intéressante. Quand vous avez manuellement mis au point un graphe dans GraphEdit et bichonné toutes ses liaisons, vous le sauvez sous un format GRF et pouvez le charger dans votre application, sans avoir besoin de programmer à la main toutes les connexions. C'est la fonction loadGRF qui permet cela. Là encore je la donne sans commentaires sur les concepts de la programmation COM, si ce n'est de rappeler qu'il faut mettre ActiveX dans la clause uses de votre unité.

    En entrée la fonction prend comme arguments le nom d'un fichier GRF (avec son chemin), puis le nom du composant TFiltergraph que l'on veut charger. En sortie, si tout s'est bien déroulé elle retourne True, sinon False. Dans le cas True le graphe est en principe construit et peut éventuellement être joué par Play, par exemple. Un appel typique sera donc de la forme suivante:

      if loadGRF('graphe1.grf,filtergraph1) then filtergraph1.play;

    Vous voyez donc que c'est le chaînon manquant entre GraphEdit et delphi. Toutes les applications directshow sous delphi pourraient ainsi être écrites en trois temps: 1) construire le graphe avec GraphEdit, 2) construire l'interface utilisateur dans delphi en la testant grâce à loadGRF, 3) programmer dans delphi la construction du graphe pour ne pas être obligé de distribuer l'application avec un fichier GRF (et éventuellement utiliser des méthodes de construction de graphe qui ne préjugent pas des filtres installés sur le système de l'utilisateur).

    {--------------------------------------------------------------------------}

      function loadGRF(fichier:widestring;fg:TFiltergraph):Boolean;

      var  hr:HResult;

        pStorage:IStorage;

        pPersist:IPersiststream;

        pStream:IStream;

      begin

      result:=false;

      try

      try

      if StgIsStorageFile(pwidechar(fichier))<>S_Ok then exit;

      hr:= StgOpenStorage(pwidechar(fichier), nil, STGM_TRANSACTED or STGM_READ or STGM_SHARE_DENY_WRITE,0, 0, pStorage);

      if FAILED(hr) then exit;

      hr:=fg.QueryInterface(IPersistStream,pPersist);

      if FAILED(hr) then exit;

      hr:= pStorage.OpenStream('ActiveMovieGraph', 0, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, pStream);

      if FAILED(hr) then exit;

      hr:= pPersist.Load(pStream);

      if FAILED(hr) then exit;

      result:=true;

      except

      end;

      finally

        pStorage:=nil;

        pStream:=nil;

        pPersist:=nil;

      end;

      end;

    {--------------------------------------------------------------------------}

    retour vers le haut de la page


    4. Les erreurs de directshow

    Dans les fonctions ci-dessus, vous avez vu apparaître la variable hr de type Hresult. C'est l'occasion de commenter la gestion des erreurs dans directshow. En régle générale une fonction directshow renvoie un code d'erreur de type Hresult. En fait HResult est un entier (de type Longint). En hexadecimal, si vous l'écrivez sous la forme inttohex(hr,8), cela donne une représentation du type $80040209, par exemple pour la pénible erreur qui dit "L'opération ne peut pas être effectuée car les broches ne sont pas connectées ($80040209)".  

    Les erreurs spécifiques de directshow sont consultables ici. Plusieurs fonctions sont fournies pour les traiter; les plus utiles sont FAILED dont vous voyez des utilisations ci-dessus et SUCCEEDED . Elles permettent de classer les codes HResult en deux catégories selon que le code correspond à un succès ou un échec. En effet certains codes d'erreurs correspondent à un succès, par exemple ceux qui commencent par VFW_S_ et bien entendu S_Ok qui est le code de succès par excellence.

    Il est possible de traduire en langage courant la plupart de ces codes d'erreurs grâce à la petite fonction que je vous donne ci-dessous. Typiquement on pourrait l'utiliser dans une boite de dialogue, par exemple:

      if failed(hr) then messagedlg(geterrortext(hr),mtError,[mbOk],0);

    Elle devrait être incluse dans tout programme directshow sous delphi qui se respecte.

    {--------------------------------------------------------------------------}

      function geterrortext(hr:Hresult):string;

      var buf:array[0..255]of char;

      begin

      AMGetErrortextA(hr,@buf,255);

      result:=buf;

      end;

     

    {--------------------------------------------------------------------------}

    retour vers le haut de la page


    5. Conclusion

    Au terme de cette 7ème leçon, vous connaissez mieux les fichiers GRF de Graphedit, vous savez sauver par programme un graphe depuis delphi, et vous savez aussi charger par programme un graphe vers delphi. Vous avez appris à récupérer les erreurs de directshow, leur code et leur traduction en texte. Comme d'habitude, vous pouvez télécharger ci-dessous une application rudimentaire qui met en oeuvre ces notions. Elle comprend une unité grf.pas où vous avez les trois fonctions données dans cette leçon. Ces fonctions ont de plus été sécurisées grâce à geterrortext. Le programme principal vous propose un bouton load qui charge un fichier GRF et essaie de le faire jouer. Le bouton save charge un fichier multimedia, construit le graphe pour le rendre, et ensuite sauve le graphe en question au format GRF. Ce n'est évidemment qu'un aperçu très anecdotique de l'intérêt de ces trois fonctions... A vous de trouver mieux.

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

    Leçon 6 pink05_back.gif   

    retour vers le haut de la page