Revenir au sommaire Tutoriaux

Comment utiliser le Mappeur Son:
Plouf
(plouf@win.be ou theplouf@yahoo.com)
     
1) A quoi ça sert?
Ben... à faire du bruit sous Windows. Il est très facile de jouer un fichier Wav en utilisant l'api sndPlaySound, mais dès qu'on veut sortir des sentiers battus, il faut pouvoir programmer sois-même ce que la carte va jouer.
Par exemple un player de module sous Windows (wééé, PMPlayer) ou de n'importe quoi que Windows ne connait pas (Winamp, RealPlayer...) doit se taper le décodage lui-même et doit donc savoir utiliser le mappeur son.
.
2) Ha, mais quel est le principe?
C'est relativement simple. Imaginez qu'au lieu de jouer un fichier wav contenant toute la musique (ou n'importe quoi d'autre) vous jouiez une succession de petits fichiers wav contenant uniquement une petite partie du gros fichier. Chacun de ces petits fichiers, vous les auriez créé vous-même (et donc vous auriez mis ce que vous vouliez dedans) puis envoyé à Windows pour qu'il les joue.
Evidement, il est clair que ces petits fichiers Wav se trouvent en mémoire, il est totalement inutile de chaque fois les sauvegarder sur le disque dur pour les rejouer tout de suite après.
L'avantage du système est clair : vous ne devez pas sauver tout un fichier Wav avant de pouvoir jouer ce que vous avez créé, et surtout vous pouvez jouer le tout au fur et à mesure que vous le créez, vu qu'on peut donner les petits bouts de fichiers wav à Windows au fur et à mesure qu'il les envoie à la carte son.
Evidement comme Windows est un système multitâche, il serait prudent de tout de même lui donner une certaine "avance", car rien ne vous dit que le système ne sera pas occupé à tout autre chose lorsque le bout de fichier actuellement joué se termine. Il faudra donc adopter une certain notion de "playlist", et plus cette playlist sera longue, plus vous serez en sécurité, mais plus vous aurez besoin de mémoire pour stocker tous ces bouts de fichiers. Et puis bien sur, une fois ces fichiers envoyés, il vous sera impossible de changer d'avis, ce qui fait que si vous voulez faire des modifications dans la manière de créer votre son, un certain retard se fera entendre.
.
3) En pratique
En pratique, un petit bout de fichier s'apelle un buffer, il se trouve dans la mémoire de votre application vu que c'est à vous qu'il incombe de les créer.
Lorsque vous direz à Windows "tiens, joue ce buffer", il ne le jouera pas tout de suite, il terminera d'abord la playlist courante. Donc, en fait, il AJOUTERA ce buffer à la playlist. Et il faudra continuellement vérifier que cette playlist ne se vide pas totalement car alors Windows n'aurait plus rien à jouer et le son s'arreterait!
La phrase n'est donc pas "joue ce buffer" mais "ajoute ce buffer à la playlist". Il faut également signaler que quand un buffer est joué, il est retiré de cette playlist et vous pouvez donc le réutiliser, le "réemplir" pour le rajouter ensuite à la playlist. Cela fait qu'il vous suffit d'un nombre limité de buffer pour jouer tout ce que vous voulez.
Au final on constate donc qu'il suffit d'un nombre fixe de buffers qu'on réutilise au fur et à mesure qu'ils sont joués. C'est tellement vrai que Windows demande, avant toute autre chose, de DECLARER les buffers que nous comptons utiliser, afin de préparer le terrain.
.
Pour utiliser le mappeur son il faut :
a) Ouvrir un périphérique son : c'est obligatoire car Windows doit savoir où nous comptons jouer. Après tout il n'est pas impossible que l'ordinateur soit équipé de plusieurs dispositifs de sortie du son. Inutile de vous inquieter à ce sujet, nous prendrons celui que Windows trouve le plus approprié.
b) Créer les buffers : il s'agit tout simplement de réserver l'espace en mémoire pour chacun de ces buffers
c) Déclarer ces buffers à Windows : comme signalé plus haut, c'est impératif.
.
Une fois ces étapes réalisées, Windows est pret à recevoir les premiers buffers dans sa playlist. Il faut donc à présent continuellement faire les opérations suivantes :
a) Chercher un buffer qui n'est pas dans la playlist, donc qui a déjà été joué (ou n'a jamais servi)
b) Remplir ce buffer avec les données que nous souhaitons envoyer à la carte son, donc notre morceau de fichier Wav
c) Ajouter ce buffer à la playlist de Windows
.
Et quand nous voulons terminer ces opérations :
a) Signaler à Windows qu'il est temps de terminer (Le son s'arrête immédiatement et donc Windows ne termine pas la playlist)
b) Libérer la mémoire allouée pour chacun de nos buffers
c) Signaler à Windows qu'il peut "oublier" nos buffers
d) Définitivement fermer le périphérique que nous avons ouvert.
.
4) Techniquement
Techniquement, nous appelerons les api de MMSystem. Il vous faudra inclure les en-tête de cette DLL dans votre application, cela dépendra de votre compilateur. En C++, il vous faudra sans doute faire une sorte de #INCLUDE , et en Delphi ça sera Uses MMSystem.
Commençons donc par ouvrir un périphérique : il s'agit de l'API waveOutOpen :
function waveOutOpen(lphWaveOut: PHWaveOut; uDeviceID: UINT; lpFormat: PWaveFormatEx; dwCallback, dwInstance, dwFlags: DWORD): MMRESULT; stdcall;
.
Quel bidule! Rassurez-vous ça n'est pas aussi lourd que ça en a l'air. Tout d'abord, il faut lui donner un pointeur vers une variable qui contiendra un handle, lequel nous servira par après à identifier le périphérique que nous ouvrons. Un peu comme un handle lors de l'ouverture d'un fichier.
Ensuite, nous devons lui donner l'indice du périphérique que nous désirons ouvrir. Ici nous nous contentons de lui dire "trouve tout seul" : WAVE_MAPPER
S'en suit ensuite un pointeur vers une structure contenant les informations dont Windows à besoin pour jouer le son. Est-ce du son 16Bit? Quelle est la fréquence de mixage, etc...
Les autres champs devront être nuls.
La structure est définie comme ceci :
tWAVEFORMATEX = packed record
wFormatTag: Word;
nChannels: Word;
nSamplesPerSec: DWORD;
nAvgBytesPerSec: DWORD;
nBlockAlign: Word;
wBitsPerSample: Word;
cbSize: Word;
end;
.
wFormatTag : normalement vous jouez un son normal : il vaudra donc WAVE_FORMAT_PCM
nChannels : 1 ou 2 selon que vous jouez en mono ou en stéréo
nSamplesPerSec : la fréquence de mixage (22050, 44100, ...)
nAvgBytesPerSec : le nombre d'octets par seconde : la fréquence fois le nombre de canaux, le tout multiplié par deux si le son est en 16bit
nBloclAlign : le nombre de données par sample : litteralement nAvgBytesPerSec/nSamplesPerSec
wBitsPerSample : 8 ou 16 si vous jouez en 8bit ou en 16bit
cbSize reste obscur : tapez le à 0 :)
waveOutOpen(@Device,WAVE_MAPPER,@Format,0,0,0);
.
Ouf, vous avez ouvert un canal sonnore, il vous faut encore créer vos buffers. Ensuite il faudra signaler ces buffers à Windows. Nous regroupons ces deux points en un seul.
Il s'agit en fait d'appeler l'API suivante :
function waveOutPrepareHeader(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr; uSize: UINT): MMRESULT;
stdcall;
.
Il lui faut tout d'abord le handle du canal que nous avons ouvert, ensuite un pointeur vers une structure contenant tout ce qu'il a besoin de savoir sur ce buffer, et ensuite la taille de cette structure (pour des raisons de sécurité, on s'en fiche)
Cette structure est définie comme suit :
TWaveHdr = record
lpData: PChar;
dwBufferLength: DWORD;
dwBytesRecorded: DWORD;
dwUser: DWORD;
dwFlags: DWORD;
dwLoops: DWORD;
lpNext:PWaveHdr;
reserved: DWORD;
end;
.
ldData : un pointeur vers la zone mémoire réservée pour ce buffer.
dwBufferLength : la longueur de la zone mémoire réservée
dwFlags : les attributs de ce buffer. Juste après sa création, nous activerons le flag WHDR_DONE afin de le marquer comme étant lu.
Les autres champs ne nous interessent pas. Au final cela nous donne :
Au final cela nous donne :
.
For I:=0 To BufferCount-1 Do Begin
//On réserve la zone mémoire disponible pour ce buffer
GetMem(Buffer[I].lpData,BufferSize);
//La taille de ce buffer
Buffer[I].dwBufferLength:=BufferSize;
//Le signaler à MMSystem
waveOutPrepareHeader(Device,@Buffer[I],SizeOf(Buffer[I]));
//Le marquer comme étant déjà joué
Buffer[I].dwFlags:=Buffer[I].dwFlags OR WHDR_DONE;
End;
.
ATTENTION : pour Windows, un buffer n'est pas QUE la zone de mémoire mais bien toute la structure. Il vous faut donc autant de variables de ce type que de buffers déclarés. Par la suite, Windows ira directement modifier des champs dans ces variables (par exemple les Flags). Vous DEVEZ donc créer un tableau (ou toute autre liste) dont les éléments sont des variables de type TWaveHdr.
Nous sommes à présent prêts pour ajouter les buffers à la playlist.
Cet envoi ce fait en trois temps : Il faut tout d'abord trouver un buffer ayant été joué. Au début, tous les buffers sont marqués comme étant joués, par la suite cela dépendra de l'avancement du son.
Ensuite, une fois que l'on a trouvé un buffer qui a déjà été joué, il faut modifier le contenu de sa zone mémoire associée et y mettre les nouvelles données. Cela vous regarde, c'est vous qui savez quoi jouer, pas Windows...
.
Et ensuite, il faut dire à Windows de rajouter ce buffer dans la playlist avec l'API
function waveOutWrite(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr; uSize: UINT): MMRESULT;
stdcall;
.
Cette api vous demande d'abord le handle du canal ouvert, ensuite un buffer (donc une variable de type TWaveHdr) et puis la taille de la structure (toujours par sécurité), ce qui nous donne :
For I:=0 To BufferCount-1 Do Begin
//Le buffer est-il joué?
If Buffer[I].dwFlags AND WHDR_DONE<>0 Then Begin
//Remplissons sa zone de donnée
Remplir_Buffer(Buffer[I].lpData);
//Et renvoyons le buffers à MMSystem
waveOutWrite(Device,@Buffer[I],SizeOf(Buffer[I]));
End;
End;
.
Cette boucle doit être constamment exécutée afin de continuellement remplir la playlist.
.
Et une fois que vous en avez assez, vous n'avez plus qu'à couper le son avec l'API
function waveOutReset(hWaveOut: HWAVEOUT): MMRESULT; stdcall;
Qui prend comme on pouvait s'y attendre le handle du périphérique son.
.
Il faut ensuite désinitialiser nos buffers à l'aide de function
waveOutUnprepareHeader(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr; uSize: UINT): MMRESULT; stdcall;
.
Pour ensuite terminer avec un
function waveOutClose(hWaveOut: HWAVEOUT): MMRESULT; stdcall;
Qui prend, pour la dernière fois, le handle du canal pour fermer celui-ci.
.
//Reset du device
waveOutReset(Device);
For I:=0 To BufferCount-1 Do Begin
//Libération de l'espace réservé
FreeMem(Buffer[I].lpData);
//Libération du buffer en lui-même
waveOutUnPrepareHeader(Device,@Buffer[I],SizeOf(Buffer[I]));
End;
//Fermture du device
waveOutClose(Device);
.
5) FeedBack
Ca fait la seconde fois que j'essaie d'écrire un tutoriel compréhensible sur le mappeur son. L'ennui c'est que ce texte n'est pas complet, si je devais tout expliquer du début à la fin, il me faudrait plus que ce bête fichier texte.

Revenir au sommaire Tutoriaux

Hit-Parade