Přehrávání hudby ve formátu Ogg Vorbis

Z CHWiki

Přejít na: navigace, hledání

Obsah

[editovat] Úvod

Hudba je důležitou součástí každé hry protože hodně pomáhá s tvorbou atmosféry a hra, která je tichá, působí tak trochu ochuzeně. Proto se v tomto článku podíváme, jak naprogramovat přehrávání hudby ve formátu Ogg Vorbis.

Vorbis je opensource algoritmus pro kompresi hudby, principem podobný formátu MP3. Většinou dosahuje ještě lepších výsledků v poměru kvalita/velikost než má MP3. Ogg je formát souboru, do kterého se takto komprimovaná hudba obvykle ukládá. S hudbou v tomto formátu bez problému pracují všechny softwarové přehrávače a i některé kapesní. Domovská stránka projektu je na adrese http://xiph.org/vorbis/ . Hudba v tomto formátu je například ve hrách Unreal Tournament 2004 nebo UFO: Extraterrestrials.

http://xiph.org/images/logos/fish_xiph_org.png

[editovat] Instalace knihoven

Abychom mohli Ogg Vorbis použít, stáhneme si ze stránky http://xiph.org/downloads/ zdrojové kódy knihoven libogg a libvorbis. Obě tyto knihovny mají licenci ve stylu BSD, takže není problém je použít i v komerčních aplikacích. Obě knihovny si přidáme do projektu, nastavíme cesty pro hlavičkové soubory a knihovny a zkompilujeme.

[editovat] Detailní návod pro Visual Studio 2005

Zdrojové kódy knihoven rozbalíme tak, aby adresářová struktura vypadala takto:

+ adresář projektu
   +- libvorbis
       +- doc
       +- include
       +- ...
   +- libogg
       +- doc
       +- include
       +- ...

Do solution si přidáme tyto projektové soubory:

  • libogg\win32\VS2003\libogg\libogg.vcproj
  • libvorbis\win32\VS2005\libvorbis\libvorbis.vcproj
  • libvorbis\win32\VS2005\libvorbisfile\libvorbisfile.vcproj

U všech nastavíme General\Output directory na $(SolutionDir)$(ConfigurationName) a General\Intermediate Directory na $(ConfigurationName) abychom měli všechny soubory pohromadě. Všechny projekty nyní zkompilujeme, musíme ale dávat pozor na to, že libvorbis vyžaduje libogg a libvorbisfile obě zbývající knihovny. Výchozí nastavení je na vytvoření dynamických knihoven, takže musíme přilinkovat vytvořené .lib soubory.

Nakonec přidáme do našeho projektu cesty k adresářům s hlavičkovými soubory a nastavíme přilinkování .lib souborů. Dále ověříme, že .dll soubory se zapisují do adresáře se spustitelným souborem našeho projektu.

Dále bude možná nutné do souboru libvorbisfile.def dopsat ov_fopen, nějak na tuto funkci vývojáři zapomněli.

[editovat] Použití

S pomocí API vorbisfile je přehrávání souborů Ogg Vorbis velmi snadné. Soubor můžeme otevřít pomocí některé z následujících funkcí:

  • ov_fopen otevře soubor na disku.
  • ov_open načte soubor otevřený pomocí standardní C knihovny (pomocí fopen)
  • ov_open_callbacks načte soubor pomocí funkcí, které sami dodáme. To se využije, pokud máme vlastní balíkovací systém a soubory nečteme přímo.

ov_open má následující parametry. Pamatujte, že kvůli problémům s kompatibilitou s CRT pod Windows není doporučeno na Windows tuto funkci používat.

FILE *f
Identifikátor otevřeného souboru, který jsme získali voláním fopen.
OggVorbis_File *vf 
Ukazatel na strukturu OggVorbis_File, kterou funkce naplní a kterou budeme používat později při čtení.
char *initial 
Pokud už jsme nějaká data ze souboru přečetli a nemůžeme se vrátit zpátky, předáme v tomto parametru ukazatel na buffer, kde ta data jsou. Tento parametr typicky nepoužíváme.
long ibytes 
Velikost přečtených dat v initial. Používá se jen v kombinaci s initial.

Dále máme funkci ov_open_callbacks, která otevře soubor pomocí našich vlastních čtecích funkcí. To využijeme, pokud soubory čteme z balíku. Jeji parametry jsou:

void *datasource 
Tento parametr funkce předá naším funkcím, typicky obsahuje identifikátor zdroje dat.
OggVorbis_File *vf 
Ukazatel na strukturu OggVorbis_File, kterou funkce naplní a kterou budeme používat později při čtení.
char *initial 
Stejný význam jako u předchozí funkce.
long ibytes 
Stejný význam jako u předchozí funkce.
ov_callbacks callbacks 
Struktura obsahující ukazatele na naše funkce.

Definice ov_callbacks vypadá takto:

typedef struct {
 size_t (*read_func)  (void *ptr, size_t size, size_t nmemb, void *datasource);
 int    (*seek_func)  (void *datasource, ogg_int64_t offset, int whence);
 int    (*close_func) (void *datasource);
 long   (*tell_func)  (void *datasource);
} ov_callbacks;

Významy parametrů jsou stejné jako u funkcí ftell, fread apod. z CRT. Pokud zdroj dat nepodporuje libovolné přesouvání čtecí pozice, můžeme nastavit seek_func a tell_func na NULL. To pro běžné hraní stačí.


Všechny funkce nám předají ukazatel na strukturu OggVorbis_File, která identifikuje náš otevřený soubor. Tuto strukturu předáváme dalším funkcím.

Pro jednoduché přehrávání nás bude zajímat prakticky jen funkce ov_read. Její parametry jsou

OggVorbis_File *vf 
Identifikátor otevřeného souboru.
char *buffer 
Ukazatel na buffer v paměti, který jsme naalokovali a kam dostaneme dekódovaná zvuková data.
int length 
Velikost bufferu, kolik dat maximálně chceme. Typické velikosti jsou kolem 4096 bajtů.
int bigendianp 
Formát dat, zda chceme Little Endian (nastavíme na 0) nebo Big Endian (nastavíme na 1). Typicky použijeme hodnotu 0.
int word 
Nastavte na 2 pro 16bitové vzorkování nebo na 1 pro 8bitové. Typicky se používá 16 bitů.
int sgned 
Nastavte na 1 pro formát dat se znaménkem (kladné/záporné) nebo na 0 pro data bez znaménka. Typicky se používá 1.
int *bitstream 
Ukazatel na proměnnou, kam dostaneme číslo aktuální logické sekce souboru.

Funkce vrací hodnotu typu int, která může mít následující hodnoty:

OV_HOLE 
Problém v dekódování, který není kritický.
OV_EBADLINK nebo OV_EINVAL 
Špatné parametry nebo problém při čtení.
Došlo se na konec souboru, dekódování končí.
kladné číslo 
Kolik bajtů dekódovaného audia jsme dostali do bufferu. Toto číslo bude typicky menší než velikost bufferu předaná v length.

Tuto funkci budeme pravidelně volat, abychom mohli kontinuálně předávat data zvukovému API. Ve většině případů funguje zvukové API tak, že nás o data samo požádá, neboli zavolá naši funkci, kterou jsme pro tento účel vyhradili.


[editovat] Příklad pro přehrávání souboru pomocí SDL_Audio

Zvukových API existuje celá řada. SDL je široce používané, proto jsem je vybral. Příklad je velmi jednoduchý, neřeší přehrávání více zvuků zaráz (na to by se použil SDL_mixer). Také používá globální proměnné, což je fuj ;).

   #include <vorbis\codec.h>
   #include <vorbis\vorbisfile.h>

   #include <SDL.h>

   const int SDL_BUFFER_SIZE = 4*1024;

   SDL_AudioSpec *obtained = NULL;
   OggVorbis_File vorbis_file;

   // Tuto funkci vola SDL_Audio z druheho vlakna, aby nacetla dalsi kus dat k prehravani.
   void audio_callback(void *userdata, Uint8 *stream, int len) {
       int current_section;
       int read_bytes = 0;
       int total_read_bytes = 0;
       
       // Nacteme 'len' dat z vorbisu do 'stream'. Musime to udelat v cyklu, protoze ov_read nemusi nutne
       // nacist vsechno najednou.
       while (total_read_bytes < len) {

           // Pouziti reinterpret_cast je zde nutne kvuli tomu, ze SDL a vorbis pouzivaji ruzne typy pro buffer v pameti.
           // Nicmene oba jsou osmibitove a znamenko si obe strany hlidaji, takze to funguje.
           read_bytes = ov_read(
               &vorbis_file, 
               reinterpret_cast<char*>(stream) + total_read_bytes,
               len - total_read_bytes, 
               0,
               2,
               1, 
               &current_section
           );
           if (read_bytes < 0)		// pokud se vyskytl nejaky problem, nepokracujeme v plneni
               break;

           total_read_bytes += read_bytes;
       }
   }

   void init_sdl_audio() {
       // Nastavime SDL_Audio na stejny format zvukovych dat jako dostavame z libvorbis.
       SDL_AudioSpec *desired = new SDL_AudioSpec();
       desired->callback = audio_callback;
       desired->freq = 44100;
       desired->channels = 2;
       desired->format = AUDIO_S16;
       desired->padding = 0;
       desired->samples = SDL_BUFFER_SIZE;
       desired->userdata = NULL;
       
       obtained = new SDL_AudioSpec();
       if (SDL_OpenAudio(desired, obtained) < 0) {
           printf("Nepodarilo se spustit zvuk.");
           exit(-1);
       }

       delete desired;
   }

   void close_sdl_audio() {
       SDL_CloseAudio();
   }

   void open_vorbis_music_file(char *file_name) {
       ov_fopen(file_name, &vorbis_file);
   }

   void close_vorbis_music_file() {
       ov_clear(&vorbis_file);
   }

   int _tmain(int argc, _TCHAR* argv[]) {
       
       init_sdl_audio();
       open_vorbis_music_file("music.ogg");

       SDL_PauseAudio(0);

       // Pockame na stisknuti nejake klavesy. 
       // SDL zatim ve druhem vlakne prehrava hudbu.
       _getch();


       SDL_PauseAudio(1);
       close_vorbis_music_file();
       close_sdl_audio();

       return 0;
   }