Accueil ⇒ Informatique ⇒ Bibliothèques ⇒ SDL ⇒ Leçon 5 : Audio

Leçon 5 : Audio

Si vous n'utilisez pas FMod, la couche audio de SDL est une alternative intéressante mais je vous préviens d'entrée : elle est beaucoup plus complexe et vous apprendrez ici à maîtriser des notions sonores qui ne sont pas nécessaires avec FMod. SDL_Audio demeure tout de même plus simple que directMusic, donc si vous êtes toujours là, allons-y!

Initialisation

Pour commencer nous allons initialiser la couche audio de SDL. SDL vous demande de renseigner les propriétés d'un objet SDL_AudioSpec et qui concernent le format du son, sa fréquence, le nombre de channels utilisés etc... Avant de voir à quoi cela correspond, nous allons créer une fonction d'initialisation de la couche sonore dans un premier exemple.

SDL_AudioSpec audioSortie;
void audioCallback(void *udata, Uint8 *stream, int len);

int audioInit(void)
{
/* Définition des propriétés audio */
audioSortie.freq = 22050;
audioSortie.format = AUDIO_S16;
audioSortie.channels = 2;
audioSortie.samples = 1024;
audioSortie.callback = audioCallback;
audioSortie.userdata = NULL;

/* Initialisation de la couche audio */
if (SDL_OpenAudio(&audio, NULL) < 0)
{
fprintf(stderr, "Erreur d'ouverture audio: %s\n", SDL_GetError());
return (-1);
}

return 0;
}

On commence par déclarer l'objet SDL_AudioSpec qui servira de conteneur à toutes les propriétés utilisées pour le son. On déclare également une fonction dite "callback" (nous verrons plus bas à quoi cela correspond), après quoi on attaque la fonction d'initialisation. Dans cette fonction on se contente de définir les propriétés audio de l'objet audioSortie, puis d'initialiser la couche audio avec SDL_OpenAudio(...) qui prend en argument les spécifications audio désirées et une adresses vers celles réellement allouées (nous n'utiliserons pas ce dernier argument ici) : voyons les différentes propriétés de audioSortie possibles : tout d'abord la fréquence qui indique la fréquence en échantillons par seconde (par exemple, 44100 pour une qualité CD).

Vient ensuite le format des données audio. Le plus commun est AUDIO_S16. Il s'agit de la taille et du type de chaque samples exprimée en bits (signés ou pas, comme les variables entières), pas besoin de s'attarder sur des détails complexes et inutiles : en général vous utiliserez AUDIO_S16. Le nombre de channels correspond au nombre de signaux séparés émis en même temps : 1 pour mono, 2 pour stéréo. La taille du buffer audio est bien souvent calculée par les fonctions que nous utiliserons, je ne l'aborderai donc pas en détail, de même que les données utilisateur que nous n'utiliserons pas. Par contre la fonction de callback est très importante : il s'agit de la fonction qui sera utilisée pour jouer nos séquences audio.

Chargement de fichiers

Maintenant que la couche sonore est bien initialisée et que, malgré la relative complexité du système vous avez compris le principe, nous allons attaquer les choses sérieuses : comment charger un fichier sonore, puis le jouer. Il faut tout d'abord charger un échantillon sonore. Pour cela nous allons utiliser la fonction SDL_AudioSpec *SDL_LoadWAV(const char *file, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len);, où file correspond au fichier à jouer, spec correspond aux propriétés sonores, audio_buf l'adresse du tampon et len sa longueur. Mais qu'est-ce que le tampon ? Le tampon est une zone mémoire qui contient nos données sonores. Dans notre code nous utiliserons un tampon Uint8 *audioBuffer; et sa taille Uint32 audioBufferLen;. Il faut savoir que chaque fichier sonore utilise sa propre structure de propriétés SDL_AUdioSpec qui correspond au format du fichier. Pour charger un fichier sonore on utilise le code ci-dessous.

SDL_AudioSpec audioBufferSpec;
Uint8 *audioBuffer;
Uint32 audioBufferLen;

/* A inclure dans votre code pour charger
un fichier sonore dans audioBuffer */
if(!SDL_LoadWAV("essai.wav", &audioBufferSpec,
&audioBuffer, &audioBufferLen))
{
printf("Erreur lors du chargement du fichier WAV.\n");
return 1;
}

La fonction retourne un NULL dans audioBuffer en cas d'erreur. Par la suite, vous pourrez libérer les ressources allouées pour le fichier WAV via la fonction SDL_FreeWAV(); qui prend en argument l'objet (audioBuffer). Nous avons ici un fichier sonore chargé dans diverses variables, il nous reste à le lire (on aurait pu les réunir dans une structure ou une classe pour en faciliter la compréhension).

Jouer un échantillon

La fonction callback, que nous avons décrite plus haut, est la fonction qui est appellée à chaque cycle de votre programme pour remplir le buffer audio. En résumé, elle contient toutes les instructions à effectuer à chaque passage dans votre code pour gérer le son. Une fois le son initialisé avec le code plus haut, et un fichier WAV chargé dans le buffer audio, nous allons utiliser le code ci-dessous pour lire le fichier.

Uint32 audioLen, audioPos;

void audioCallBack(void *udata, Uint8 *stream, int len)
{
/* On ne lit que s'il reste des données à jouer */
if ( audioLen == 0 )
return;

/* Remise à zéro du tampon de sortie */
memset(stream, 0, len);

/* Lecture du buffer audio */
if (audioPos < audioBufferSpec.len) {
if (audioPos+len > audioBufferSpec.len)
len = audioBufferSpec.len = audioPos;
SDL_MixAudio(stream, audioBuffer + audioPos,
len, SDL_MIX_MAXVOLUME);
audioPos += len;
}

/* Décrémentation de ce qu'il reste à lire */
audioLen -= len;
}

A savoir tout d'abord, la fonction SDL_MixAudio(...) est la fonction qui permet de lire vos buffers audios. Ici, audioLen correspond à la longueur de piste restante à jouer (si il ne reste rien à jouer on quitte). stream correspond au tampon audio de sortie (ce qui sera envoyé à la carte son en gros) et len la taille du tampon : on remet ce tampon à zéro avant de le remplir avec les données sonores à jouer. En effet, à chaque cycle de votre programme seul un octet (Uint8) est ici envoyé au matériel audio, et la suite de ces octets joués par la carte formera le son entendu par l'utilisateur. Ici donc, si audioLen est inférieure à la longueur du buffer à jouer on envoie le son (si ce n'est pas le cas il y a problème), et ce avec la fonction SDL_MixAudio().

Cette fonction prend en argument le tampon de sortie, l'octet à envoyer (audioBuffer est un pointeur sur un Uint8, décalé de audioPos pour avoir l'octet actuel à jouer), la longueur du buffer et le volume. Ce dernier est compris entre 0 et SDL_MIX_MAXVOLUME, et vous pourrez le faire varier, par exemple, en pourcentage (75% du volume = (0.75 * SDL_MIX_MAXVOLUME)). Une fois le son joué on incrémente la position dans le buffer audio de la taille de la séquence jouée (qui est celle du tampon). Tout ceci est assez complexe c'est vrai mais dites vous que ça reste plus simple que du directMusic. Une fois que le buffer audio est lu, on décrémente la longueur d'échantillon à jouer de la longueur jouée dernièrement. Tout cela est bien joli, on a maintenant un module de son initialisé, un fichier WAV chargé et la fonction adéquate pour lire ce fichier, reste, reste ? A lancer la lecture!

/* Lancement de la lecture */
SDL_PauseAudio(0);

/* Attendre que la lecture du son soit terminée */
while ( audioLen > 0 )
SDL_Delay(100);

/* Fermeture du module */
SDL_CloseAudio();

SDL_PaudeAudio(int) est la fonction servant à mettre ou à enlever la pause : en effet, de base le son est sur pause, passer 0 en argument à cette fonction relance la lecture, 1 réenclenchant la pause. N'oubliez pas de terminer le processus audio par SDL_CloseAudio(); à la fin de votre code. Au final, voici un code source d'un programme complet utilisé pour lire des fichiers sons.

#include <SDL.h>

/* Objets globaux */
SDL_AudioSpec audioSortie, audioBufferSpec;
Uint8 *audioBuffer;
Uint32 audioLen, audioPos, audioBufferLen;

/* Prototypes des fonctions */
void audioCallback(void *udata, Uint8 *stream, int len);
int audioInit(void);

int audioInit(void)
{
/* Définition des propriétés audio */
audioSortie.freq = 22050; // Fréquence en samples/sec
audioSortie.format = AUDIO_S16; // Format des données audio
audioSortie.channels = 2; // Nombre de "channels" (2=stéréo)
audioSortie.samples = 1024; // Taille du buffer audio
audioSortie.callback = audioCallback; // Fonction callback
audioSortie.userdata = NULL; // Données utilisateur (nulles)

/* Initialisation de la couche audio */
if (SDL_OpenAudio(&audio, NULL) < 0)
{
fprintf(stderr, "Erreur d'ouverture audio: %s\n", SDL_GetError());
return(-1);
}
return 0;
}

void audioCallBack(void *udata, Uint8 *stream, int len)
{
/* On ne lit que s'il reste des données à jouer */
if ( audioLen == 0 )
return;

/* Remise à zéro du tampon de sortie */
memset(stream, 0, len);

/* Lecture du buffer audio */
if (audioPos < audioBufferSpec.len) {
if (audioPos+len > audioBufferSpec.len)
len = audioBufferSpec.len = audioPos;
SDL_MixAudio(stream, audioBuffer + audioPos,
len, SDL_MIX_MAXVOLUME);
audioPos += len;
}

/* Décrémentation de ce qu'il reste à lire */
audioLen -= len;
}

int main(void)
{
/* Chargement du fichier WAV */
if(!SDL_LoadWAV("essai.wav", &audioBufferSpec,
&audioBuffer, &audioBufferLen))
{
printf("Erreur lors du chargement du fichier WAV.\n");
return 1;
}


/* Initialisation de SDL_Audio */
audioInit();

/* Lecture du fichier son */
SDL_PauseAudio(0);

/* Attendre que la lecture soit terminée */
while ( audioLen > 0 )
SDL_Delay(100);

/* Fermeture du module */
SDL_CloseAudio();
}

Conclusion

J'espère que ce tutorial vous aura permis de comprendre et d'utiliser SDL_Audio. En premier lieu les exemples de codes vous seront peut-être plus parlants comme ça a été le cas pour moi mais par la suite vous aurez besoin de maîtriser correctement les notions abordées dans ce tutorial (je ne les y ai pas incluses pour rien :p). En tout cas, bon code !