SDL: Hry nejen pro Linux (17)

23. 6. 2005
Doba čtení: 8 minut

Sdílet

V dnešním dílu o knihovně SDL začneme nový tematický celek, budou jím zvuky a hudba, které přinesou konec všem tichým aplikacím. Na první pohled by se mohlo zdát, že si musí naprostou většinu funkčnosti napsat programátor sám, nicméně je možné používat již hotový mixer v podobě rozšiřující knihovny SDL_mixer, který odstraní většinu námahy.

Zvuky v počítači

Asi to už budete znát, ale pro jistotu uvedu alespoň velice stručný úvod do zpracování audia počítačem. Zvuk je ve své podstatě vlnění – analogová veličina, která se na digitální signál převádí tzv. vzorkováním. Vždy po uplynutí předem stanoveného časového intervalu se odebere „vzorek“ zvuku, což je číslo udávající hodnotu signálu v daném okamžiku, a uloží se do paměti.

Při posílání posloupnosti vzorků na zvukovou kartu jsou vytvářeny aproximace původních zvukových vln. Výslednou kvalitu tedy ovlivňuje jak frekvence, se kterou byly vzorky odebrány, tak jejich velikost. Běžně podporovaným audio formátem je 16bitový vzorek na 22 kHz, což je kompromis mezi výslednou kvalitou a velikostí paměti potřebné pro uložení.

SDL a audio

Knihovna SDL poskytuje nízkoúrovňový přístup pro práci s audiem navržený jako základ pro implementaci softwarového zvukového mixeru. Na první pohled by se mohlo zdát, že si musí naprostou většinu funkčnosti napsat programátor sám, nicméně je možné používat již hotový mixer v podobě rozšiřující knihovny SDL_mixer, který odstraní většinu námahy.

Pokud tedy z nějakého důvodu potřebujete nízkoúrovňový přístup, zvolte si samotné SDL, a pokud preferujete jednoduše volatelné funkce, vždy můžete jít cestou SDL_mixeru. My se budeme věnovat oběma způsobům. Začneme funkcemi, které poskytuje SDL.

Podobně jako u grafiky lze i u zvuků zvolit ovladač, který bude zprostředkovávat přehrávání. Jejich dostupnost je závislá především na nainstalování v systému a pak také na konfiguračních volbách při kompilaci SDL.

Zvolení konkrétního ovladače se dá provést přiřazením jména požadovaného ovladače do systémové proměnné SDL_AUDIODRIVER, o všechny podrobnosti se postará SDL. V Linuxu jsou dostupné například dsp, dma, esd, artsc, ve Win32 dsound a waveout.

V běžící aplikaci se dá jméno aktuálně používaného driveru zjistit voláním funkce SDL_AudioDriver­Name(). Předává se jí alokovaná paměť, do které se má informace uložit, a proti přetečení také její velikost.

char *SDL_AudioDriverName(char *namebuf, int maxlen); 

Předpokladem pro to, aby šly v SDL aplikaci zvuky vůbec používat, je předání symbolické konstanty SDL_INIT_AUDIO do inicializační funkce SDL_Init(). Pak je samozřejmě nutné nahrát do aplikace nějaké zvuky a případně je zkonvertovat do požadovaného formátu. Teprve potom se může přistoupit k otevření audio zařízení a k samotnému přehrávání.

Menší zvláštností oproti jiným knihovnám je, že se musí definovat tzv. callback funkce, kterou bude SDL volat pokaždé, když zvukové kartě dojdou data a bude nutné poslat do streamu nová data.

Struktura SDL_AudioSpec

Tato struktura se používá ke specifikaci formátu audia, používá se především při otevírání zařízení a také při nahrávání zvuků a jejich konverzích na požadovaný formát.

typedef struct
{
    int freq;
    Uint16 format;
    Uint8 channels;
    Uint16 samples;
    Uint8 silence;
    Uint32 size;
    void (*callback)(void *userdata, Uint8 *stream, int len);
    void *userdata;
} SDL_AudioSpec; 

Atribut freq udává počet vzorků za sekundu (čili frekvenci vzorkování), běžnými hodnotami jsou 11025, 22050 a 44100. Format specifikuje formát audio dat, může nabývat hodnot několika symbolických konstant (viz SDL manuál), které určují, zda je hodnota vzorků osmi- nebo šestnáctibitová, znaménková/bez­znaménková apod. Channels udává počet oddělených zvukových kanálů, jednička označuje mono a dvojka stereo.

Proměnná samples ukládá velikost bufferu v počtu vzorků. Toto číslo by mělo být mocninou dvou a může být audio ovladačem upraveno na hodnotu pro hardware vhodnější. Většinou se volí z rozmezí od 512 do 8192 v závislosti na rychlosti procesoru. Nižší hodnoty mají kratší reakční čas, ale mohou způsobit podtečení v případě, že zaneprázdněná aplikace nestíhá plnit buffer. Stereo vzorek se skládá z obou kanálů v pořadí levý-pravý a jejich počet je vztažen k času podle vzorce ms = (počet vzorků * 1000) / frekvence.

Silence a size jsou definovány automaticky. V prvním případě se jedná o hodnotu uloženou v bufferu, která reprezentuje ticho, a ve druhém o jeho velikost v bytech.

Callback představuje ukazatel na funkci, kterou SDL volá, když je audio zařízení připraveno přijmout nová data. Předává se jí ukazatel na buffer/stream, jehož délka se rovná len. Userdata je libovolný ukazatel na dodatečná data.

void callback(void *userdata, Uint8 *stream, int len); 

Vzhledem k tomu, že plnění bufferu většinou běží v odděleném vláknu, měl by být přístup k datovým strukturám chráněn pomocí dvojice SDL_LockAudio() a SDL_UnlockAudio(). Tyto funkce zaručují, že po locknutí až do unlocku nebude callback funkce volána, a v žádném případě by naopak neměly být použity v callback funkci.

void SDL_LockAudio(void);
void SDL_UnlockAudio(void); 

Otevření audio zařízení

Audio zařízení se otevírá pomocí SDL_OpenAudio(), kterému se v prvním parametru předává požadovaný formát. Funkce se pokusí tuto konfiguraci najít a výsledek hledání uloží do obtained, které se tak stává pracovní konfigurací. Ta může být následně použita například pro konverzi všech zvuků do hardwarového formátu. V případě úspěchu je vrácena nula, jinak –1.

int SDL_OpenAudio(SDL_AudioSpec *desired,
        SDL_AudioSpec *obtained); 

Bylo-li obtained volajícím nastaveno na NULL, budou se, v případě nedostupnosti požadovaného formátu, provádět automatické realtimové konverze do formátu podporovaného hardwarem.

Aby se mohly bezpečně inicializovat data pro callback funkci plnící buffer, je po otevření audio implicitině pozastaveno. Spuštění zvukového výstupu se docílí zavoláním SDL_PauseAudio(0).

void SDL_PauseAudio(int pause_on); 

Na aktuální stav se lze dotázat funkcí SDL_GetAudioS­tatus(), která vrací výčtový typ obsahující SDL_AUDIO_STOPPED, SDL_AUDIO_PAUSED nebo SDL_AUDIO_PLAYING.

SDL_audiostatus SDL_GetAudioStatus(void); 

Po skončení práce s audiem by se nemělo zapomenout na jeho deinicializaci.

void SDL_CloseAudio(void); 

Otevření audio zařízení by mohlo vypadat například takto.

// Prototyp callback funkce
void AudioCallback(void *userdata, Uint8 *stream, int len);

// Inicializace
SDL_AudioSpec desired, obtained;

desired.freq = 22050;       // FM Rádio kvalita
desired.format = AUDIO_S16LSB;  // 16-bit signed audio
desired.channels = 1;       // Mono
desired.samples = 8192;     // Velikost bufferu
desired.callback = AudioCallback;// Ukazatel na callback
desired.userdata = NULL;    // Žádná uživatelská data

// Otevře audio zařízení
if(SDL_OpenAudio(&desired, &obtained) == -1)
{
    fprintf(stderr, "Unable to open audio: %s\n",
            SDL_GetError());
    return false;
}

// Příprava callbacku na hraní
// Například loading všech zvuků, jejich konverze apod.
// Obtained obsahuje aktuální konfiguraci

// Spustí zvukový výstup
SDL_PauseAudio(0);

// Konec aplikace
SDL_CloseAudio(); 

Nahrávání zvuků

Samotné SDL umí nahrávat pouze .WAV formát souborů, který vzhledem ke své velikosti není pro praktické použití zrovna vhodný. V praxi se proto pro loading zvuků používají rozšiřující knihovny. Například SDL_sound nebo SDL_mixer si umí poradit i s .MID, .MP3, .OGG a dalšími běžně používanými formáty.

SDL_AudioSpec *SDL_LoadWAV(const char *file,
        SDL_AudioSpec *spec,
        Uint8 **audio_buf,
        Uint32 *audio_len); 

První parametr funkce SDL_LoadWAV() představuje diskovou cestu k souboru se zvukem, na adresu spec bude uložen formát nahraných audio dat. Samotná data se uloží do automaticky alokované paměti, jejíž adresa bude spolu s délkou předána zpět v posledních dvou parametrech.

Funkce vrátí NULL, pokud nelze soubor otevřít, používá nepodporovaný formát nebo je poškozen. Typ chyby se dá zjistit následným SDL_GetError().

Po skončení práce se zvukem je vždy nutné pomocí SDL_FreeWAV() uvolnit alokovaná data.

void SDL_FreeWAV(Uint8 *audio_buf); 

Příklad nahrání zvuku ze souboru…

// Globální proměnné
Uint8 *g_sound_data;    // Ukazatel na data
Uint32 g_sound_len; // Délka dat

// Např. inicializace
SDL_AudioSpec spec; // Formát dat

if(SDL_LoadWAV("test.wav", &spec, &g_sound_data,
        &g_sound_len) == NULL)
{
    fprintf(stderr, "Unable to load test.wav: %s\n",
            SDL_GetError());
    return false;
}

// Úklid
SDL_FreeWAV(g_sound_data); 

Mixování zvuků

SDL poskytuje funkci SDL_MixAudio(), která umí smixovat dva zvuky se stejným formátem. První dva parametry představují cílový a zdrojový buffer, len je délka v bytech a volume označuje hlasitost. Ta může nabývat hodnot od nuly do SDL_MIX_MAXVOLUME (definováno jako 128) a je vhodné ji nastavit na maximum.

void SDL_MixAudio(Uint8 *dst, Uint8 *src, Uint32 len, int volume); 

Plnění audio bufferu

Jak už bylo zmíněno na začátku, SDL je při práci se zvuky velmi nízkoúrovňové, a proto je programátor nucen napsat si i vlastní plnění audio bufferu. Ukazatel na tuto funkci se předává do SDL při otevírání audio zařízení a volání je v podstatě automatické.

Kód převezmeme z ukázkového programu, který se dodává společně se SDL (adresář test). Jeho výsledkem bude zvuk přehrávaný v nekonečné smyčce.

bitcoin školení listopad 24

Uint8   *g_sound_data;  // Ukazatel na data zvuku
Uint32  g_sound_len;    // Délka dat
int g_sound_pos;    // Pozice při přehrávání

void AudioCallback(void *userdata, Uint8 *stream, int len)
{
    // Ukazatel na část, kde se má začít přehrávat
    Uint8 *wave_ptr = g_sound_data + g_sound_pos;

    // Délka zvuku do konce
    int wave_left = g_sound_len - g_sound_pos;

    // Zbývající délka je menší než požadovaná
    // Cyklus, protože celý zvuk muže být kratší
    while(wave_left <= len)
    {
        // Pošle data na zvukovou kartu
        SDL_MixAudio(stream, wave_ptr, wave_left,
                SDL_MIX_MAXVOLUME);

        // Posune se o právě zapsaná data
        stream += wave_left;
        len -= wave_left;

        // Od začátku zvuku
        wave_ptr = g_sound_data;
        wave_left = g_sound_len;
        g_sound_pos = 0;
    }

    // Zbývající část zvuku je delší než požadovaná
    SDL_MixAudio(stream, wave_ptr, len,
            SDL_MIX_MAXVOLUME);
    g_sound_pos += len;
} 

Ukázkové programy

SDL a audio

Větší část dnešního programu se objevila už v článku, jedná se tedy o aplikaci přehrávající v nekonečné smyčce zvuk nahraný z .wav souboru. Mezerníkem je možné zvukový výstup dočasně pozastavit a pomocí +/- se dá měnit hlasitost. (Zdrojový kód se zvýrazněním syntaxe.)

Download

Pokračování

V příštím dílu probereme funkce, které SDL poskytuje pro konverzi zvuků do požadovaného formátu, nahrávání zvuků pomocí knihovny SDL_sound i z jiných typů souborů, než je .WAV, a pokud zbude trochu místa, zkusíme nakousnout knihovnu SDL_mixer.

Autor článku

Backend programátor ve společnosti Avast, kde vyvíjí a spravuje BigData systém pro příjem a analýzu událostí odesílaných z klientských aplikací založený na Apache Kafka a Apache Hadoop.