Programowanie Gier -> Wykład: Dźwięk 3D - OpenAL

Główna strona OpenALa ze specyfikacjami, linkami do skompilowanych bibliotek, i wieloma innymi informacjami.

1. Ogólnie

2. Podstawowe pojęcia OpenALa

openal_basics.png

Przykładowy trywialny program odtwarzający dźwięk używając OpenAL.

Demo: tremulous (na bazie ioquake3), może moje castle? Wszystko pod Linuxem z dźwiękiem 3D używa OpenALa!

3. Efekt Dopplera

Kiedy mija nas jadący szybko samochód, wysokość dźwięku (warkotu silnika) rośnie w miarę jak samochód jest coraz bliżej, i maleje kiedy samochód się oddala ("wziuuuum" :) ). To właśnie efekt Dopplera, stosunkowo oczywisty kiedy uświadomimy sobie że dźwięk to fala rozchodząca się w powietrzu. Kiedy źródło dźwięku porusza się względem słuchacza, fale odbierane są z większą lub mniejszą częstotliwością. OpenAL może to symulować.

Komendy OpenALa są tutaj naprawdę trywialne:

Demo: doppler_demo.

Omówienie na devmaster.net

4. Inne efekty: EFX, EAX

Krótka historia: (Michalis nie jest fanem wykładów o historii czegokolwiek :), ale kilka słów tutaj pomoże nam zorientować się w terminologii)

EAX powstał dawno temu jako zestaw efektów jaki można było przypisać dźwiękom w kartach Creative. Ten "zestaw efektów" był specyficzny dla kart Creative. (Chociaż mógł być później emulowany przez OpenALa Creative pod Windowsem, na dowolnych kartach dźwiękowych, kosztem CPU; chociaż kod źródłowy tej emulacji chyba nigdy nie został opublikowany.) Oryginalnie, Creative dodało kilka rozszerzeń do DirectSound3D aby EAX było dostępne. Później, dodano analogiczne rozszerzenia do OpenALa, tyle że API tych rozszerzeń wyglądało dość koszmarnie — np. nazwy stałych zostały przeniesione żywcem z DirectSound3D. W miarę jak DirectSound3D umiera a OpenAL rośnie, zrozumiano że API EAX trzeba wyrzucić do kosza. Zamiast niego powstało EFX: ładne, czyste API. OpenAL Soft implementuje je, więc jest to też API które działa cross-platform. Czyli, w skróćie: EAX is dead, long live EFX. Piszę o EAX tylko dlatego że możecie jeszcze znaleźć w sieci mnóstwo informacji i wzmianek o EAX.

EFX to eleganckie rozszerzenie alc (ALC_EXT_EFX). Dokumentacja znajduje się w OpenAL SDK dla Windowsa (szczęśliwie instalowalne pod Linuxem przed wine), w Effects Extension Guide.pdf (w docs/). Sama dokumentacja tego rozszerzenia jest dłuższa od specyfikacji OpenALa (144 strony vs 61), więc z konieczności zrobimy sobie tylko krótkie streszczenie poniżej.

EFX dodaje nowe pojęcia do OpenALa:

Dema: z SDK: EFXEnumerateWin32.exe, EFXReverbWin32.exe, moje proste efx_demo.

Notka: OpenAL Soft obsługuje obecnie tylko najbardziej bazowe funkcje: tylko efekt reverb, tylko filtr low-pass, tylko 1 aux slot. OpenAL w wersji Creative który działa na "Generic Sofware" umie bardzo niewiele więcej. Generalnie, trzeba przygotować się że efekty EFX będą naprawdę piękne tylko jeżeli gracz posiada faktycznie dobrą kartę dźwiękową, optymalnie — jeżeli ma implementację OpenAL dostarczoną przez producentów kart zoptymalizowaną pod dany hardware. To nie jest norma na obecnych komputerach, ale możemy mieć nadzieję że za kilka lat lepsze karty dźwiękowe będą powszechne, a razem z nimi efekty EFX staną się szerzej dostępne.

5. Menedżer dźwięków na priorytetach

O ile możemy stworzyć bardzo dużo buforów (dopóki dźwięki są krótkie i nie zajmują zbyt wiele pamięci), nie jest dobrze tworzyć zbyt wielu dźwięków (source). Miksowanie wielu dźwięków zajmuje dużo czasu, a użytkownik przecież nie rozróżni i tak 20 dźwięków na raz. Z tego powodu niektóre implementacje OpenALa (np. Generic Hardware pod Windowsem) mogą nawet odmawiać stworzenia zbyt wielu źródeł dźwięku. Implementacja OpenALa nie wybiera sama dźwięków, nie ma żadnego mechanizmu który automatyczne odrzucałby zbyt słabe (o małym GAIN, albo daleko od słuchacza) dźwięki. Nie ma nawet mechanizmu który gwarantowałby eliminowanie wyłączonych/paused dźwięków (bo przecież dźwięk paused może być w następnej sekundzie odtworzony). Idea jest taka że musimy zrobić to sami w naszym programie, znając zależności pomiędzy dźwiękami.

Proste demo dynamicznego przydzielania buforów do dźwięków.

W ogólnej sytuacji, typowe rozwiązanie to menedżer dźwięków który zarządza "zasobem" dźwięków OpenALa:

W zależności od dynamiki naszych dźwięków, inne strategie mogą mieć sens. Np. może być sens bardziej agresywnie usuwać stare dźwięki, albo na odwrót — starać się nie przerywać starych dźwięków (w przełożeniu na kod, oznaczałoby to że skalujemy priorytet nowego dźwięku na porzeby porównania np. razy 2 albo razy 0.5). Może być sens uznawać głośniejsze dźwięki za bardziej ważne (tutaj przyda nam się znajomość "głośności" dźwięku którą podajemy jako GAIN dla source OpenALa, patrz notki powyżej o głośności dźwięku w buforze).

Przykłady: np. sound menedżer OpenALa w OGRE. Możecie też zerknąć na mój TALSourceAllocator, powiązany z TGameSoundEngine.

Demo: test_al_source_allocator.

6. Strumieniowanie

Kiedy chcemy załadować duże pliki, może nie być rozsądnie marnować kilkadziesiąt MB pamięci na ścieżki dźwiękowe naszego levelu. Stąd pomysł na strumieniowanie: odkompresowujemy i "podajemy" OpenALowi zawartość dźwięków w miarę jak są potrzebne. Możemy po prostu czytać i dekompresować plik w locie, a możemy też wczytać do pamięci skompresowany plik z muzyką, i tylko odkompresowywać go na żądanie (wada: marnujemy te kilka MB, zaleta: nie "szuramy po dysku" w trakcie działania gry — to jest dobre, generalnie odczyt/zapis z dysku potrafi uszkodzić szybkość działania gry, OpenGLa etc.).

Sam odczyt (i ew. dekompresja) dźwięku — patrz inne biblioteki. Tak jak OpenGL nie ma funkcji do odczytu tekstur z pliku, tak samo OpenAL nie ma funkcji do odczytu dźwięków z pliku. Chociaż ALUT ma funkcję do odczytu prostych WAV, a niektóre implementacje OpenAL mają rozszerzenie AL_EXT_vorbis a nawet AL_EXT_mp3; ale rozszerzenia AL_EXT_vorbis/mp3 nie przyjęły się (bo developerzy twierdzą że jest zbyt łatwo zaimplementować to samemu :) ).

Popularny format to OggVorbis ze świetną i trywialną w obsłudze biblioteką vorbisfile, można znaleźć mnóstwo przykładów w Internecie (także w powiązaniu z OpenALem). Są też biblioteki radzące sobie z większą ilością formatów, jak libsndfile. Jako programiści gier, powinniśmy mieć przynajmniej dwa formaty dźwięków w swoim arsenale: WAV (prosty, nieskompresowany, idealna jakość dźwięku, dobry dla krótkich dźwięków, można zaimplementować samemu w kilkanaście linijek kodu) i OggVorbis lub MP3 (format mocno skompresowany, do dłuższych ścieżek dźwiękowych; Michalis mocno poleca OggVorbis — bez patentów, otwarta specyfikacja, otwarta i dobrze udokumentowana implementacja).

Szczegóły streaming w OpenAL:

OpenAL Lesson 8: OggVorbis Streaming Using The Source Queue (z devmaster.net).

Play Compressed Formats With OpenAL, przykładowe odtwarzanie plików ze strumieniami poprzez ffmpeg (przez autora OpenAL Soft, więc pewnie wie o czym pisze :) ).

Programming Linux Games — rozdział 5 poświecony jest programowaniu dźwięku. Zawiera omówienie podstaw i przykłady użycia OpenAL, libsndfile, vorbisfile, i wielu innych rzeczy. Mimo że książka ma swoje lata (omawia np. kilka przestarzałych rozszerzeń OpenALa z implementacji Loki), polecam!


Końcowe uwagi: pamiętajcie sprawdzać alGetError(), alcGetError(device). Mechanizm jest podobny jak w przypadku OpenGLa, tzn. funkcja która spowodowała błąd generalnie jest ignorowana i ustawia wewnętrzną flagę. Sprawdzenie alGetError() zwraca tą flagę i czyści ją. Poprawny program nigdy nie powinien generować błędów OpenALa (no, powiedzmy że AL_OUT_OF_MEMORY jest nie do uniknięcia w teorii).