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
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
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::
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:
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
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
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
Leçon 5
retour
vers le haut de la page
|