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. |