Gestion des fichiers WMV avec DirectShow et DSPACK
SOMMAIRE
Laisser un commentaire I INTRODUCTIONII PRELIMINAIRES : FONCTIONS UTILES
III LECTURE D’UN FICHIER WMV
IV CONCLUSION
I INTRODUCTION
Dans ce tutoriel, vous allez pouvoir gérer des fichiers *.wmv. Ce tutoriel va être divisé en deux parties, nous allons voir comment lire ce type de fichier qui est un format assez délicat à prendre en charge. Nous verrons ensuite comment extraire le son d’une vidéo et de le transcoder en wav. Nous verrons tout d’abord deux petites fonctions bien utiles qui vont vous faciliter le développement de votre application multimédia.II PRELIMINAIRES : FONCTIONS UTILES
1. La fonction d’ajout de filtres
Je vous propose cette fonction qui va nous permettre d’ajouter plus rapidement des filtres à notre graphe.
function TfrmMediaPlayer.AjoutFiltre(CategoryName:string; FilterName:string;
grph : IGraphBuilder):IBaseFilter;
var SysDev, FilterEnum : TSysDevEnum;
G, CLSID_DestFilter, CLSID_IntermediateFilter : TGUID;
i, j:integer;
hr : HRESULT;
filter : IBaseFilter;
begin
SysDev:= TSysDevEnum.Create;
for i:= 0 to SysDev.CountCategories-1 do
begin
if SysDev.Categories[i].FriendlyName = string(CategoryName) then
begin
G:= SysDev.Categories[i].CLSID;
FilterEnum:= TSysDevEnum.Create(G);
for j:= 0 to FilterEnum.CountFilters-1 do
begin
if FilterEnum.Filters[j].FriendlyName = string(FilterName) then
begin
CLSID_IntermediateFilter:= FilterEnum.Filters[j].CLSID;
break;
end;
end;
end;à
end;
SysDev.Free;
FilterEnum.Free;
hr := CoCreateInstance(CLSID_IntermediateFilter, nil,
CLSCTX_INPROC_SERVER, IID_IBAseFilter, Filter);
grph.AddFilter(filter, StringToOleStr(FilterName)); result:= Filter;
end;
Cette fonction prend en argument :
- le nom de la catégorie correspondant au filtre,
- le nom du filtre,
- ainsi que le graphe sur lequel nous allons ajouter notre filtre.
2. Fonction d’ajout d’un objet DMO
Un objet DMO (DirectX Media Object) est un composant de type COM, sa fonction est de traiter des flux de données multimédia (Multimedia Stream) à partir d’un tampon alloué. Cet objet COM est représenté par une dll enregistrée dans le système. Nous allons utiliser ce type d’objet pour la lecture de notre fichier WMV. Je vais vous présenter la fonction qui permet d’ajouter un objet DMO à un graphe.
Déclarez en premier lieu les constantes suivantes :
const WMVideoDecoderDMO : TGUID = '{82d353df-90bd-4382-8bc2-3f6192b76e34}';
const WMAudioDecoderDMO : TGUID = '{2eeb4adf-4578-4d10-bca7-bb955f56320a}';
Voilà la fonction :
function TfrmMediaPlayer.AjoutDMO(wmdecoder_guid : TGUID; DMOName : String) : IBaseFilter;
var iFilter : IBaseFilter;
hr :HResult;
DMOWrapper : IDMOWrapperFilter;
iWMDecoder : IMediaObject;
begin
hr := CoCreateInstance(wmdecoder_guid,
nil,
CLSCTX_INPROC_SERVER,
IMediaObject,
iWMDecoder);
hr := CoCreateInstance(CLSID_DMOWrapperFilter,
nil,
CLSCTX_INPROC_SERVER,
IBaseFilter,
iFilter);
if succeeded(hr) then
begin
hr := iFilter.QueryInterface(IDMOWrapperFilter,
DMOWrapper);
if Succeeded(hr) then
begin
if DMOName = 'WMVideo Decoder DMO' then
hr := DMOWrapper.Init(wmdecoder_guid,
DMOCATEGORY_VIDEO_DECODER)
else hr := DMOWrapper.Init(wmdecoder_guid,
DMOCATEGORY_AUDIO_DECODER);
DMOWrapper := nil;
if Succeeded(hr) then
result := iFilter; end;
end;
end;
Cette fonction prend en argument :
- La clé guid du décodeur (audio ou vidéo),
- le nom de l’objet DMO à introduire dans le graphe,
3. FONCTION DE CONNECTION DE FILTRES
Voilà la fonction que l’on utilisera à cet effet :
procedure TfrmMediaPlayer.ConnectionFiltres(pinName1, pinName2 : PWideChar;
filter1, filter2 : IBaseFilter);
begin
filter1.FindPin(pinName1,POutIntermediateFilter);
filter2.FindPin(pinName2,PInIntermediateFilter);
grph.Connect(POutIntermediateFilter, PInIntermediateFilter);
end;
Cette fonction prend en argument :
- le nom de la broche de sortie du premier filtre,
- le nom de la broche d’entrée du second filtre,
- la référence au premier filtre,
- et pour finir la référence au deuxième filtre.
Cette fonction ne fait que connecter deux filtres entre eux en connaissant la broche de sortie du premier filtre et la broche d’entrée du second. La fonction se charge dans un premier temps de rechercher les broches décrites avant, en utilisant la fonction FindPin de l’interface IBaseFilter. Les filtres sont connectés par l’intermédiaire de la fonction Connect.
Bon voilà pour les préliminaires nous allons maintenant passer à la gestion des fichiers wmv. Nous allons voir dans un premier temps le cas de la lecture d’une séquence vidéo au format wmv.
III LECTURE D’UN FICHIER WMV
1. Lecture via un objet TDSVideoWindowEx2
a. Les préliminaires
Dan ce cas présent il n’y a pas besoin de grand-chose, encore une fois Delphi va prendre en charge la gestion de graphe. Pour réaliser cette fonction, il va falloir poser sur votre fiche les objets suivants :
- Un objet TFilterGraph dont la propriété Name sera fgWMVRender par exemple, sa propriété mode doit avoir gmNormal comme valeur.
- Un objet TDSTrackBar dont la propriété Name aura pour valeur DStbRender par exemple, et sa propriété FilterGraph doit avoir la valeur fgWMVRender.
- Un objet TDSVideoWindowEx2 dont la propriété FilterGraph aura pour valeur fgWMVRender.
b. Fonction de lecture
Voici la fonction qui permet de lire un fichier wmv via un objet TDSVideoWindowEx2.
procedure TfrmMediaPlayer.lectureWMVViaEcran;
var fileSourceFilter :IFileSourceFilter;
wChar:pwidechar;
buffer1 :array[0..255]of widechar;
begin
if not fgWMVRender.Active then
begin
DSVideoWindowEx21.FilterGraph := fgWMVRender;
fgWMVRender.ClearGraph;
fgWMVRender.Active := True;
DStbRender.FilterGraph := fgWMVRender;
WMSFilter.QueryInterface(IID_IFileSOurceFilter, fileSourceFilter);
wChar:=stringtowidechar(OpenDialog1.FileName,buffer1,255);
fileSourceFilter.load(string2wchar, nil);
fgWMVrender.renderFile(OpenDialog1.FileName)
end;
fgWMVRender.Play;
end;
Tout d’abord on vérifie si notre graphe est actif, pour ne pas avoir à recréer une deuxième fois notre filtre. Puis, comme le montre la procédure on va créer un filtre qui va nous permettre de charger le fichier wmv à lire. Ceci se fait par l’intermédiaire d’une boÖte de dialogue d’ouverture de fichier dans mon exemple, mais libre à vous d’utiliser un autre moyen. Donc après avoir créé ce filtre spécifique, par l’intermédiaire de l’interface IID_FileSourceFilter. On lui adjoint le nom du fichier à lire via la fonction load. Pour mettre en place la lecture on a recours à la fonction RenderFile. Cette fonction est indispensable car elle vous permet de prendre en compte tous les paramètres du fichier vidéo à lire et d’effectuer les réglages des autres composants. Pour lancer la lecture il ne vous reste plus qu’à appeler la fonction Play. Comme nous venons de la voir il est fort aisé de réaliser une fonction de lecture des fichiers wmv par l’intermédiaire du pack de composants DSPACK 234. Nous allons voir maintenant comment réaliser un graphe " à la main " qui va nous permettre de diffuser ce type de fichier via un périphérique de sortie vidéo (carte Decklink).
2. Fonction de diffusion d’un fichier vidéo wmv via Decklink
a. Le graphe associé
Nous allons voir comment réaliser la diffusion simultanée à la fois via un objet TDSVideoWindowEx2 et via la carte Decklink.
Nous pouvons voir que le filtre permettant de charger un fichier wmv est assez spécial, en effet ce n’est pas un filtre dont le nom est File Source, mais un filtre de type WM ASF Reader. D’autre part, comme vous pouvez le constater sur le graphe, nous voyons apparaître les objets DMO qui vont nous permettre de décoder le flux de données vidéo et audio. GraphEdit les fait apparaître visuellement en vert pour les différencier des filtres classiques. Le premier graphe ne va pas nous poser de problème à mettre en œuvre puisque c’est celui qui nous permet de lire le fichier via le composant TDSVideoWindowEx2, c’est exactement la fonction que nous venons d’écrire précédemment. Par contre la fonction permettant de diffuser la lecture via la carte Decklink va attirer toute notre attention.
b. Fonction de diffusion
Donc il va nous falloir créer le deuxième graphe à la main. Les fonctions que j’ai introduites au début du tutoriel vont nous aider dans ce périple !!! Nous allons morceler cette procédure, pour pouvoir la détailler au fur et mesure car celle-ci est assez longue. Voici la partie déclaration de la procédure et des variables utilisées localement
procedure TfrmMediaPlayer.lectureWMVExterne;
var fileSourceFilter :IFileSourceFilter; //Filtre permettant de charger le fichier de type wmv
wchar:pwidechar; //comme prÄcÄdemment permet d’obtenir une chaéne de type pwidechar
buffer1 :array[0..255]of widechar;
hr : HRESULT; //permet de sauvegarder le code retour d’une fonction
nCount : integer;
ROTid: Integer;
pEnumPins : IEnumPins; // permet l’ÄnumÄration des broches d’un filtre
pPin, pDMOInPin, pDMOOutPin : Ipin; // dÄtermine les broches de sortie et d’entrÄe d’un objet DMO
Fetched : cardinal;
pd : TPINDIRECTION;
Les commentaires étant assez explicites je ne m’attarderai pas là-dessus. Maintenant passons à la dÄfinition des objets créés à partir des interfaces ainsi que l’initialisation des valeurs filtergraph de nos composants DSPACK.
begin
if not bPaused then
begin
fgWMVRender.ClearGraph;
fgWMVRender.Active := True;
DSVideoWindowEx21.FilterGraph := fgWMVRender;
DStbRender.FilterGraph := fgWMVRender;
grph := fgWMVRender as IGraphBuilder;
grph.QueryInterface(IID_IMediaControl,mc);
grph.QueryInterface(IID_IMediaEvent, mEvent);
La fonction suivante va nous permettre de pouvoir visualiser le graphe créé sous GraphEdit.
AddGraphToRot((fgWMVRender as IFiltergraph),ROTId);
Ajoutons maintenant le filtre permettant de gérer la lecture des fichiers WMV, le WM ASF Reader. Nous allons pour cela appeler l’interface IFileSourceFilter. A noter qu’il faut faire une conversion de type (string vers PWideChar) pour charger le fichier source par l’intermédiaire du WM ASF Reader.
SourceFilter := AjoutFiltre('DirectShow Filters', 'WM ASF Reader', grph);
SourceFilter.QueryInterFace(IID_IFileSOurceFilter, fileSourceFilter);
wchar:=stringtowidechar(frmRenderer.OpenDialog1.FileName,buffer1,255);
fileSourceFilter.load(2wchar, nil);
Ajoutons maintenant le DMO qui se nomme WMVideo Decoder DMO. Pour ce faire nous allons utiliser la fonction que nous avons écrite au chapitre précédent, c’est-à-dire AjoutDMO. La fonction AddFilter va nous permettre de l’intégrer physiquement au graphe de la même manière qu’un filtre classique.
IIntermediateVideoFilter := AjoutDMO(WMVideoDecoderDMO, 'WMVideo Decoder DMO');
grph.AddFilter(IIntermediateVideoFilter, 'WMVideo Decoder DMO');
La connexion d’un objet DMO avec un filtre classique est cependant un peu plus délicate. En effet il faut tout d’abord énumérer les broches de celui-ci, via la fonction EnumPins. Ensuite on doit déterminer le type de broche en fonction de la direction de celle-ci, c’est-à- dire si elle correspond à une entrée ou à une sortie. Cela se fair par l’intermédiaire de la fonction QueryDirection. Cette fonction renvoie un code (PINDIR_INPUT ou PINDIR_OUTPUT) qui va nous permettre de créer nos broches d’entée et de sortie (pDMOInPin et pDMOOutPin).
if (failed(IIntermediateVideoFilter.EnumPins(pEnumPins))) then Begin
ShowMessage('Error EnumPins on DMO filter');
grph := Nil;
Exit;
End;
while (pEnumPins.Next(1, pPin, @Fetched) = S_OK) and (Fetched = 1) do
Begin
If Failed(pPin.QueryDirection(pd)) then Begin
ShowMessage('Error Getting Direction on Pin');
grph := Nil;
Exit;
End;
Case pd of
PINDIR_INPUT :
If pDMOInPin = Nil then pDMOInPin := pPin;
PINDIR_OUTPUT :
If pDMOOutPin = Nil then pDMOOutPin := pPin;
End;
End;
Maintenant il va falloir connecter le filtre source et le DMO. Pour commencer on cherche la broche de sortie du filtre source qui se nomme " Raw Video 1 ". Sourcefilter.FindPin('Raw Video 1', POutIntermediateFilter); Ensuite on effectue la connexion entre les deux filtres par l’intermédiaire de leur broche respective.
grph.connect(POutIntermediateFilter, PDMOInPin);
Nous allons maintenant intégrer le filtre de sortie, c’est-à-dire celui qui va nous permettre d’effectuer le rendu. Nous allons pour cela utiliser la fonction AjoutFiltre que l’on a créé dans le chapitre précédent.
IVideoRenderFilter := AjoutFiltre(frmRenderer.sVideoRenderCategory,
frmRenderer.sVideoRender,
(fgWMVRender as IGraphBuilder));
Il suffit ensuite de connecter la broche de sortie du DMO avec la broche d’entrée du filtre de rendu vidéo.
IVideorenderFilter.FindPin('In', PInIntermediateFilter);
grph.connect(PDMOOutPin, PInIntermediateFilter);
Nous allons maintenant nous occuper de la chaÖne audio du graphe... Nous allons insérer dans le graphe le DMO permettant de décoder la partie Audio du fichier wmv. Cela se fait de la même manière que pour le WMVideo Decoder DMO.
IIntermediateFilter := AjoutDMO(WMAudioDecoderDMO, 'WMAudio Decoder DMO');
grph.AddFilter(IIntermediateFilter, 'WMAudio Decoder DMO');
if (failed(IIntermediateFilter.EnumPins(pEnumPins))) then
Begin
ShowMessage('Error EnumPins on DMO filter'); grph := Nil; Exit;
End;
while (pEnumPins.Next(1, pPin, @Fetched) = S_OK) and (Fetched = 1) do
Begin
If Failed(pPin.QueryDirection(pd)) then
Begin
ShowMessage('Error Getting Direction on Pin');
grph := Nil; Exit;
End;
Case pd of
PINDIR_INPUT : pDMOInPin := pPin;
PINDIR_OUTPUT : pDMOOutPin := pPin;
End;
end;
Ensuite on effectue la connexion entre le filtre source et la broche d’entrée du DMO. Pour ce faire on cherche la broche de sortie du WM ASF reader (notre filtre source). Puis ensuite on effectue la jonction via la fonction connect.
Sourcefilter.FindPin('Raw Audio 0', POutIntermediateFilter);
grph.connect(POutIntermediateFilter, PDMOInPin);
Nous allons maintenant ajouter un filtre intermédiaire nous permettant de décoder des données audio PCM ou IEEE. Ensuite on connecte la broche de sortie du DMO et la broche d’entrée du filtre DC DSP.
IIntermediateFilter2 := AjoutFiltre('DirectShow Filters', 'DC-DSP Filter', grph);
IIntermediateFilter2.FindPin('Input',PInIntermediateFilter);
grph.Connect(pDMOOutPin, PInIntermediateFilter);
Nous allons maintenant ajouter le filtre de rendu audio. On utilise toujours notre fonction AjoutFiltre. Pour la connexion du filtre DC DSP et de filtre de rendu on utilise la fonction que nous avons écrite dans le précédent chapitre : ConnectionFiltres.
IAudioRenderFilter := AjoutFiltre(frmRenderer.sAudioRenderCategory,
frmRenderer.sAudioRender,
(fgWMVRender as IGraphBuilder));
ConnectionFiltres('Output', 'In', IIntermediateFilter2, IAudioRenderFilter);
La connexion des filtres est maintenant terminée. Il faut s’occuper maintenant de la diffusion du clip vidéo. Nous allons créer un objet ICapGraph par l’intermédiaire de l’interface IID_ICaptureGraphBuilder.
hr := CoCreateInstance(CLSID_CaptureGraphBuilder2,
nil,
CLSCTX_INPROC_SERVER,
IID_ICaptureGraphBuilder2,
ICapGraph);
Comme d’habitude, on lui adjoint l’objet fgWMVRender comme graphe d’origine. ICapGraph.SetFilterGraph(fgWMVRender as IGraphBuilder); A partir de notre nouvel objet on va effectuer le rendu de la vidéo. On procède de la faäon suivante :
- On sélectionne le filtre de rendu vidéo, pour diffuser la vidéo,
- On sélectionne ensuite le filtre de rendu audio afin de diffuser la partie audio.
hr := ICapGraph.RenderStream(nil,nil, SourceFilter, nil, IVideoRenderFilter);
hr := ICapGraph.RenderStream(nil,nil, SourceFilter, nil, IAudioRenderFilter);
La prochaine étape consiste à visualiser notre vidéo sur un objet TDSVideoWindow2, afin d’assurer le contrãle de la diffusion de la vidéo. Nous avons étudier cette fonction dans la partie précédente, je vais donc passer à la suite.
WMSFilter.QueryInterface(IID_IFileSOurceFilter, fileSourceFilter);
string2wchar:=stringtowidechar(frmRenderer.OpenDialog1.FileName,buffer1,255);
fileSourceFilter.load(string2wchar, nil); fgWMVRender.renderFile(string2wchar);
On va maintenant arréter la diffusion et la visualisation du clip vidéo. Cela permet de rester sur la première image.
mc.Run;
mc.stop;
fgWMVRender.Stop;
end
else
begin
Une fois que l’utilisateur a appuyé sur le bouton de lecture nous redémarrons la lecture et la diffusion.
mc.Run;
fgWMVRender.Play;
Nous allons mettre un système de timer permettant de gérer la diffusion de notre clip vidéo. La fonction WaitForCompletion bloque tous les autres messages issus de la fenétre, il est ainsi impossible par exemple de stopper la lecture. Nous pallions à ce problème en ajoutant la fonction ProcessMessages, en complément du timer (objet TTimer).
fElapsed := Now;
nCount := 0;
RenderTimer.Enabled :=True;
while nCount = 0 do
begin
if mEvent <> nil then
begin
mEvent.WaitForCompletion(fTime, nCount);
Application.ProcessMessages;
end
else
break;
end;
Une fois la diffusion terminée on va libérer l’espace mémoire utilise par les objets créés précédemment.
if mc <>nil then mc.Stop;
RenderTimer.Enabled := false;
//mMediaSeeking := nil;
fgWMVRender.Active := false;
fgRender.Active := false;
mEvent := nil;
mc := nil;
SourceFilter := nil;
IVideoRenderFilter := nil;
IIntermediateFilter := nil;
IIntermediateFilter2 := nil;
IIntermediateVideoFilter := nil;
IIntermediateVideoFilter2 := nil;
IAudioRenderFilter := nil; ICapGraph:= nil;
grph := nil;
end;
end;
IV Conclusion
Dans cette première partie nous avons vu comment gérer la lecture d’un clip vidéo au format wmv, par l’intermédiaire d’un objet TDSVideoWindow2. Nous avons vu ensuite comment gérer la diffusion via une carte vidéo type DECKLINK ou encore PINNACLE, tout en combinant l’affichage de la vidéo sur l’application. Nous verrons dans un prochain tutoriel comment réaliser la conversion de l’audio vers le format WAV.