16.12 Audio
Ein Spiel ohne Geräusche kann auf Dauer keinen Spaß machen. Man muss ja nicht gleich einen hitverdächtigen Song hinzufügen, aber einige kleine Soundeffekte können nicht schaden. Das Thema Sound & Audio wird in der Spieleprogrammierung recht selten behandelt. Ein Grund dafür ist, dass dieses Thema schon einiges an Grundverständnis über Musik & Computer abverlangt. So dürfte auch klar sein, dass diese Einführung hier nicht mehr ist als eine kurze oberflächliche Einführung, wie Sie den einen oder anderen Ton Ihrer Anwendung hinzufügen können. Den meisten unter Ihnen dürfte das aber in der Regel ausreichen (dem Autor auf jeden Fall, da dieser sich als außerordentlich unmusikalisch einstuft). Daher orientiert sich der Autor ausnahmsweise mehr oder weniger an der Dokumentation von SDL, in dem Vertrauen, dass die Autoren dabei schon wissen, wovon Sie reden.
Hinweis Da hier, abgesehen von der Funktion SDL_OpenAudio(), die Funktionen nicht genauer erklärt werden, sei hierzu die Dokumentation bzw. Manual Page für die Syntaxbeschreibung und die einzelnen Parameter empfohlen.
|
Vereinfacht: Musik auf dem Computer ist eine Übersetzung von Wellen, die Sie in einer Serie von Werten hören, wovon jeder Wert einer Schwingungsweite der Welle entspricht. Werden diese Werte in einen Stream der Soundkarte geschickt, kann eine Annäherung an den Originalwellen wieder erzeugt werden.
SDL unterstützt hierbei das Audio-Format 8 und 16 Bit mit einer Frequenz zwischen 11025 Hz und 44100 Hz. Diese Werte sind abhängig von der Hardware. Sofern die Hardware das Audioformat oder die Frequenz nicht unterstützt, kann diese auch emuliert werden. Gewöhnlich unterstützt aber jede gängige Hardware das Audio-Format 16 Bit und eine Frequenz von 22050 Hz.
Um etwas auf dem Lautsprecher auszugeben, müssen Sie zuerst das Audiogerät öffnen. Dies erledigen Sie mit der Funktion:
int SDL_OpenAudio( SDL_AudioSpec *desired,
SDL_AudioSpec *obtained);
Als Parameter müssen Sie hierbei mindestens die Angaben für desired vom Typ SDL_AudioSpec machen. SDL_AudioSpec ist eine Struktur, die wie folgt definiert ist:
typedef struct {
int freq;
Uint16 format;
Uint8 channels;
Uint8 silence;
Uint16 samples;
Uint32 size;
void (*callback)(void *userdata, Uint8 *stream, int len);
void *userdata;
} SDL_AudioSpec;
Um also ein Audiogerät zu öffnen, müssen Sie diese Struktur erst mit entsprechenden Werten füllen. Mit freq geben Sie die Anzahl der Samples an, die an das Soundgerät pro Sekunde gesendet werden. Je höher dieser Wert ist, umso besser. Gewöhnliche Werte sind hierbei 11025 Hz, 22050 Hz und 44100 Hz. Mit format geben Sie die Größe in Bits und den Typ des Samples an. Mögliche Werte und deren Bedeutung sind hierbei:
Tabelle 16.7
Mögliche Angaben für den Parameter format
Parameter format
|
Bedeutung
|
AUDIO_U8
|
Unsigned 8 Bit Samples
|
AUDIO_S8
|
Signed 8 Bit Samples
|
AUDIO_U16
AUDIO_U16LSB
|
Unsigned 16 Bit little-endian Samples
|
AUDIO_S16
AUDIO_S16LSB
|
Signed 16 Bit little-endian Samples
|
AUDIO_U16MSB
|
Unsigned 16 Bit big-endian Samples
|
AUDIO_S16MSB
|
Signed 16 Bit big-endian Samples
|
AUDIO_U16SYS
|
Entweder AUDIO_U16LSB oder AUDIO_U16MSB; abhängig von der Anordnung der Byte-Reihenfolge des Systems (little- oder big-endian)
|
AUDIO_S16SYS
|
Entweder AUDIO_S16LSB oder AUDIO_S16MSB; abhängig von der Anordnung der Byte-Reihenfolge des Systems (little- oder big-endian)
|
Mit channels geben Sie die Anzahl der Sound-Kanäle an. 1 steht hierbei für Mono (Single Channel) und 2 für Stereo (Dual Channel). silence ist ein Wert für einen Audio-Puffer, der berechnet wird.
Mit samples geben Sie die gewünschte Größe des Audio-Puffers eines Samples an. Dieser Wert sollte immer ein typischer Wert hoch 2 sein. Gewöhnliche Werte variieren zwischen 512 und 8192, was aber auch abhängig von der Geschwindigkeit der Anwendung und der CPU ist. Kleinere Werte bewirken eine schnellere Antwortzeit, können allerdings auch bewirken, dass der Puffer nicht in der geforderten Zeit gefüllt wird, wenn die Anwendung z. B. umfangreiche Berechnungen ausführt.
size ist wieder eine Audio-Puffergröße in Bytes, die berechnet wird. callback benötigen Sie, um die Adresse einer Callback-Funktion zu übergeben. Diese müssen Sie schreiben, da hiermit die Audio-Daten gemischt und in den Audio-Stream geschrieben werden. Erst danach können das Audio-Format und die Abtastrate ausgewählt und das Audio-Gerät geöffnet werden. userdata ist ein Zeiger auf den ersten Parameter der Callback-Funktion.
Bevor Sie jetzt den Sound abspielen können, muss zuvor noch die Funktion SDL_PauseAudio(0) aufgerufen werden. Dies ist nötig, da sonst sofort die Callback-Funktion angesprungen wird, bevor noch irgendetwas initialisiert werden konnte. Damit können Sie sicher die Callback-Funktion mit Daten initialisieren, nachdem ein Audiogerät geöffnet wurde.
Jetzt, wo Sie ein Audiogerät geöffnet haben, benötigen Sie als Nächstes die Funktion SDL_LoadWAV(), um die Audio-Daten zu laden. Dann müssen Sie eine Struktur vom Typ SDL_AudioCVT mit der Funktion SDL_Build_AudioCVT() mit Werten initialisieren, um eine Konvertierung durchzuführen. Dabei handelt es sich um die Werte des Quell- und Zielformats für die anschließende Konvertierung. Ebenfalls werden die Werte über die Anzahl der Kanäle (1 = Mono, 2 = Stereo) und die Frequenz (Samples pro Sekunde) jeweils vom Quell- und Zielformat benötigt. Die so mit Werten bestückte Struktur SDL_AudioCVT kann anschließend als Parameter für die Funktion SDL_ConvertAudio verwendet werden, um die Audio-Daten in das gewünschte Format zu konvertieren.
Anschließend sollten Sie mit SDL_LockAudio() die Callback-Funktion sperren, um so Initialisierungen der Daten, die Sie von der Konvertierung mit SDL_ConvertAudio() erhalten haben, durchzuführen. Nachdem Sie die Callback-Funktion mit SDL_UnlockAudio() wieder freigegeben haben, sollte die von Ihnen zur Verfügung gestellte Mixer-Funktion (Callback-Funktion) den Sound ausgeben. Am Ende sollten Sie das Audio-Gerät mit SDL_CloseAudio() wieder freigeben.
Hinweis Ich denke, es versteht sich fast schon von selbst, dass Sie beim SDL_Init() das Flag SDL_INIT_AUDIO mit angeben müssen.
|
16.12.1 Programmbeispiel – Audio
Das folgende Programmbeispiel demonstriert Ihnen die Verwendung von Audio-Daten (in diesem Beispiel einfache Wave-Dateien (*.wav)), die bei einem bestimmten Tastendruck vom Lautsprecher wiedergegeben werden sollten.
/* sdl10.c */
#include <SDL/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SOUNDS 5
struct sample {
Uint8 *daten;
Uint32 pos;
Uint32 len;
} sounds[SOUNDS];
static void
AudioMixing (void *nichtVerwendet, Uint8 * stream, int laenge) {
unsigned int i;
Uint32 menge;
for (i = 0; i < SOUNDS; ++i) {
menge = (sounds[i].len - sounds[i].pos);
if (menge > laenge) {
menge = laenge;
}
SDL_MixAudio(stream, &sounds[i].daten[sounds[i].pos],
menge, SDL_MIX_MAXVOLUME);
sounds[i].pos += menge;
}
}
static void
AudioPlay (const char *datei) {
int index;
SDL_AudioSpec wave;
Uint8 *daten;
Uint32 len;
SDL_AudioCVT cvt;
/* einen leeren Audio-Slot suchen */
for (index = 0; index < SOUNDS; ++index) {
if (sounds[index].pos == sounds[index].len) {
break;
}
}
if (index == SOUNDS)
return;
/* Audio-Datei laden und nach 16 Bit und 22 kHz wandeln */
if (SDL_LoadWAV (datei, &wave, &daten, &len) == NULL) {
fprintf (stderr, "Konnte '%s' nicht laden: %s\n",
datei, SDL_GetError ());
return;
}
SDL_BuildAudioCVT ( &cvt, wave.format, wave.channels,
wave.freq, AUDIO_S16, 2, 22050 );
cvt.buf = malloc (len * cvt.len_mult);
memcpy (cvt.buf, daten, len);
cvt.len = len;
SDL_ConvertAudio (&cvt);
SDL_FreeWAV (daten);
/* die Audiodaten in den Slot schreiben */
/* (Abspielen startet sofort) */
if (sounds[index].daten) {
free (sounds[index].daten);
}
SDL_LockAudio ();
sounds[index].daten = cvt.buf;
sounds[index].len = cvt.len_cvt;
sounds[index].pos = 0;
SDL_UnlockAudio ();
}
static int eventloop (void) {
SDL_Event event;
while (SDL_WaitEvent (&event)) {
SDL_keysym keysym;
switch (event.type) {
case SDL_KEYDOWN:
keysym = event.key.keysym;
if (keysym.sym == SDLK_ESCAPE) {
printf ("ESCAPE gedrückt.\n");
return EXIT_SUCCESS;
}
if (keysym.sym == SDLK_a) {
printf ("Alarm\n");
AudioPlay ("alarm.wav");
}
if (keysym.sym == SDLK_c) {
printf ("Cry in the hall\n");
AudioPlay ("cry 2.wav");
}
if (keysym.sym == SDLK_b) {
printf ("Blood\n");
AudioPlay ("bloodflo.wav");
}
if (keysym.sym == SDLK_m) {
printf ("Like a cat?\n");
AudioPlay ("meeeow.wav");
}
if (keysym.sym == SDLK_z) {
printf ("Are you angry?\n");
AudioPlay ("zorn.wav");
}
break;
case SDL_QUIT:
printf ("Quit event. Bye.\n");
return EXIT_SUCCESS;
}
}
return 1;
}
int main (void) {
SDL_Surface *screen;
int done = 1;
SDL_AudioSpec format;
if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
printf ("Unable to initialize SDL: %s\n", SDL_GetError ());
return EXIT_FAILURE;
}
atexit (SDL_Quit);
atexit (SDL_CloseAudio);
screen = SDL_SetVideoMode (256, 256, 16, 0);
if (screen == NULL) {
printf ("Unable to set video mode: %s\n", SDL_GetError ());
return EXIT_FAILURE;
}
/* Format: 16 Bit, stereo, 22 KHz */
format.freq = 22050;
format.format = AUDIO_S16;
format.channels = 2;
format.samples = 512;
format.callback = AudioMixing;
format.userdata = NULL;
/* das Audio-Gerät öffnen und das Abspielen beginnen */
if (SDL_OpenAudio (&format, NULL) < 0) {
printf ("Audio-Gerät konnte nicht geöffnet werden: %s\n",
SDL_GetError ());
exit (EXIT_FAILURE);
}
SDL_PauseAudio (0);
while (done) {
printf ("ESC für Ende\n A, C, Z, M und B für Sound "
" abspielen.\n");
done = eventloop ();
}
SDL_PauseAudio (1);
SDL_CloseAudio();
return EXIT_SUCCESS;
}
Das Programm übersetzen:
$ gcc `sdl-config --libs` `sdl-config --cflags` -o sdl10 sdl10.c
$ ./sdl10
|