Obsah
1. Pohled pod kapotu JVM – přehrávání a mixování zvuků s využitím knihovny SDLJava
2. Podporované zvukové formáty
4. Utilita pro načtení a výpis základních informací o souborech typu RIFF/WAVE
5. Načtení souboru se zvukem do aplikace využívající knihovnu SDLJava
6. Demonstrační příklad SDLTest66 – načtení a přehrání zvuku
7. Demonstrační příklad SDLTest67 – vliv velikosti bufferu na vznik chyb při přehrávání zvuků
8. Demonstrační příklad SDLTest68 – využití osmi zvukových kanálů pro mixování
9. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
1. Pohled pod kapotu JVM – přehrávání a mixování zvuků s využitím knihovny SDLJava
V předchozí části seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme si na čtveřici demonstračních příkladů ukázali, jakým způsobem je možné v knihovně SDLJava přehrávat hudbu, a to s využitím třídy SDLMixer. Stejná třída, přesněji řečeno metody implementované v této třídě, se používají i při přehrávání a mixování zvuků, což je téma, kterému se budeme věnovat dnes. Přehrávání hudby bylo poměrně snadné, protože se – alespoň z hlediska aplikačního programátora – pro načtení a přehrání hudby použilo pouze několik metod. U zvuků může být situace paradoxně složitější, a to zejména z toho důvodu, že zvuky jsou v multimediálních aplikacích (především ve hrách) navázány na nějakou událost a zvolený zvuk se musí v ideálním případě začít přehrávat přesně ve chvíli, kdy k dané události dojde. Ovšem tento požadavek je poměrně problematické splnit, a to především kvůli existenci bufferů, do nichž se zvuková data vkládají, a absenci potřebných RT rozšíření operačních systémů.
2. Podporované zvukové formáty
Knihovna SDLJava podporuje několik zvukových formátů použitelných jako zdroj zvuků pro vytvářenou multimediální aplikaci. Tyto formáty jsou vypsány v tabulce umístěné pod tímto odstavcem:
# | Formát | Stručný popis |
---|---|---|
1 | .wav | Zvukové vzorky uložené ve formátu RIFF/WAVE |
2 | .aif, .aiff | Audio Interchange File Format odvozený od staršího formátu .iff (Amiga) |
3 | .voc | Zvukové vzorky uložené ve formátu VOC (použito v SW od firmy Creative Labs, výrobce SoundBlasterů) |
4 | .ogg | Zvukový/hudební formát Ogg Vorbis |
Povšimněte si mnoha rozdílů oproti podporovaným hudebním formátům (viz též předchozí část tohoto seriálu, v níž jsme se speciálně věnovali Amiga modulům i formátu General MIDI):
# | Typ (koncovka souboru) | Popis |
---|---|---|
1 | .wav | zvukové vzorky uložené ve formátu RIFF/WAVE |
2 | .voc | zvukové vzorky uložené ve formátu VOC (použito v SW od firmy Creative Labs, výrobce SoundBlasterů) |
3 | .mod | Amiga moduly a odvozené formáty |
4 | .xm | Amiga moduly a odvozené formáty |
5 | .s3m | Amiga moduly a odvozené formáty |
6 | .669, | Amiga moduly a odvozené formáty |
7 | .it | Amiga moduly a odvozené formáty |
8 | .midi | Hudební formát MIDI |
9 | .ogg | Zvukový/hudební formát Ogg Vorbis |
3. Formát souborů RIFF/WAVE
Velká část her postavených na (nativní) knihovně SDL používá pro ukládání zvuků formát RIFF/WAVE, tj. soubory, které mají většinou příponu .wav. Jedná se o obecný formát, který zpočátku umožňoval pouze ukládání nezkomprimované hudby s využitím PCM (pulzně kódové modulace), ovšem posléze byl rozšířen i o možnosti využití různých metod pro bezeztrátovou i ztrátovou komprimaci zvuků. Vzhledem k jednoduchosti a taktéž univerzálnosti formátu RIFF/WAVE se můžeme s tímto formátem setkat i v dalších oblastech, které přímo nesouvisí se zpracováním zvuku – do souborů s koncovkou .wav je totiž možné ukládat prakticky libovolné digitalizované jednorozměrné signály o teoretické maximální vzorkovací frekvenci až 16 GHz (232-1 Hz), přičemž (opět teoretický) počet kanálů může dosahovat až 65535. To znamená, že do .wav lze ukládat například signály získané z různých čidel, signály určené pro digitální osciloskopy atd. Právě z tohoto důvodu si základní formát souborů WAVE popíšeme podrobněji, protože se může programátorům v některých situacích hodit (a většinou nemá smysl znovuvynalézat kolo :-).
Soubory typu RIFF/WAVE se skládají z takzvaných chunků, přičemž každý chunk (budeme mu možná poněkud nepřesně říkat blok) na svém začátku obsahuje čtyři bajty s jednoznačným jménem, za nímž jsou uloženy další čtyři bajty, v nichž je uložena délka bloku. Jednotlivé bloky se mohou vnořovat, což například znamená, že „hlavní“ blok má délku rovnou velikosti celého souboru. Nás v tuto chvíli zajímají především ty soubory RIFF/WAVE, v nichž jsou uložena nezkomprimovaná zvuková data. Tyto soubory se skládají ze tří typů bloků, jejichž jména jsou vypsána v následující tabulce:
Jméno bloku | Význam | |
---|---|---|
„RIFF“ | blok obsahující hlavičku se základními informacemi o souboru | |
"fmt " | blok obsahující informace o formátu uložených zvukových dat | |
„data“ | vlastní zvuková data (ať již zkomprimovaná či nezkomprimovaná) |
Hlavní blok má název „RIFF“ a jeho hlavička má délku pouze dvanáct bajtů. V prvních čtyřech bajtech je uloženo jméno bloku, což musí být ASCII znaky „R“, „I“, „F“ a „F“ v uvedeném pořadí. Za těmito čtyřmi bajty následuje délka bloku – protože se jedná o hlavní blok, je jeho délka rovna délce celého souboru, ovšem do této délky není započteno prvních osm bajtů hlavičky. Samotná délka i další celočíselné informace používá pořadí ukládání bajtů little endian. Následují čtyři bajty obsahující informace o formátu souboru/dat; v případě zvukových dat jsou zde uloženy bajty obsahující ASCII znaky „W“, „A“, „V“ a „E“, pokud by však soubor obsahoval jiné multimediální informace, byla by zde uložena jiná čtveřice znaků (takový soubor by však neměl koncovku .wav):
Offset | Šířka | Jméno | Význam |
---|---|---|---|
0 | 4 bajty | ChunkID | musí obsahovat ASCII znaky „RIFF“ v uvedeném pořadí |
4 | 4 bajty | ChunkSize | délka souboru snížená o osm bajtů |
8 | 4 bajty | Format | musí obsahovat ASCII znaky „WAVE“ v uvedeném pořadí |
Ve zvukových souborech .wav následuje ihned za hlavním blokem „RIFF“ blok s názvem „fmt “ (i s mezerou na konci), který obsahuje, jak již název tohoto bloku naznačuje, informace o způsobu uložení zvukových dat. Po jménu tohoto bloku (čtveřice znaků tvořících řetězec „fmt “) je ve čtyřech bajtech uložena délka bloku, která je u většiny souborů konstantní: 16 bajtů. Na dalších dvou bajtech je uložena celočíselná konstanta reprezentující formát dat. Pro nezkomprimované zvukové soubory využívající PCM (pulzně kódovou modulaci) obsahuje tato dvojice bajtů hodnotu 1. V další dvojici bajtů je uložen celkový počet zvukových kanálů, což typicky bývá jeden kanál pro mono výstup popř. dva kanály pro stereo výstup, ovšem teoreticky je možné využít až 65535 zvukových kanálů.
Další čtyři bajty obsahují vzorkovací frekvenci, typicky 11025, 22050, 44100 či 48000 Hz, ovšem lze nalézt i zvukové soubory s odlišnými frekvencemi. Za touto hodnotou je v navazujících čtyřech bajtech uložena rychlost přenosu dat, která se dá vypočítat pomocí vzorce SampleRate*NumChannels*BitsPerSample/8. Dalším údajem je hodnota o zarovnání bloku, která se dá taktéž vypočítat, a to s využitím vzorce NumChannels*BitsPerSample/8. Poslední údaj je velmi důležitý – ve dvou bajtech je uložen počet bitů pro každý zvukový vzorek. U osmibitového samplování je zde uložena hodnota 8, u 16bitového samplování (CD) hodnota 16, ovšem některé zvukové soubory mohou používat i další hodnoty, například 24 apod.:
Offset | Šířka | Jméno | Význam |
---|---|---|---|
12 | 4 bajty | Subchunk1ID | musí obsahovat ASCII znaky "fmt " (i s mezerou na konci) |
16 | 4 bajty | Subchunk1Size | délka tohoto bloku, pro PCM je zde uložena hodnota 16 |
20 | 2 bajty | AudioFormat | pro PCM je zde uložena hodnota 1 |
22 | 2 bajty | NumChannels | počet zvukových kanálů, typicky 1 pro mono výstup a 2 pro stereo výstup |
24 | 4 bajty | SampleRate | vzorkovací frekvence, typicky 11025 (starší SW), 22050, 44100 (CD) či 48000 (DAT a další profi zařízení) atd. |
28 | 4 bajty | ByteRate | rychlost přenosu dat do zvukového subsystému (SampleRate*NumChannels*BitsPerSample/8) |
32 | 2 bajty | BlockAlign | zarovnání bloku (NumChannels*BitsPerSample/8) |
34 | 2 bajty | BitsPerSample | počet bitů na každý zvukový vzorek, typicky 8, 16 či 24 |
Vlastní zvuková data jsou uložena v bloku s názvem „data“. Tento blok opět začíná svým jménem (čtveřicí ASCII znaků), délkou odpovídající vzorci NumSamples*NumChannels*BitsPerSample/8 a samotnými samply, tj. zvukovými vzorky tvořícími digitální signál v jednotlivých zvukových kanálech. Při formátu PCM nedochází k žádné komprimaci, snad s výjimkou čtyřbitových samplů, kde je dvojice vzorků spojena do jednoho bajtu, ovšem dnes snad už nikdo čtyřbitové samplování nepoužívá :-)
Offset | Šířka | Jméno | Význam |
---|---|---|---|
36 | 4 bajty | Subchunk2ID | musí obsahovat ASCII znaky „data“ |
40 | 4 bajty | Subchunk2Size | délka datového bloku (==NumSamples*NumChannels*BitsPerSample/8) |
44 | proměnná délka | Data | vlastní zvuková data (za sebou uložené vzorky jednotlivých zvukových kanálů) |
4. Utilita pro načtení a výpis základních informací o souborech typu RIFF/WAVE
Podívejme se nyní na zdrojový kód jednoduché utility, která se pokusí načíst a následně zobrazit základní informace o souborech typu RIFF/WAVE. Tato utilita pracuje velmi jednoduše – postupně načítá jednotlivé prvky ze dvou nám již známých bloků „RIFF“ a „fmt “, přičemž je kód utility napsán takovým způsobem, aby pracoval korektně jak na architekturách s pořadím bajtů little endian, tak i na architekturách s pořadím bajtů big endian (nebo se smíšeným pořadím, což je však dnes spíše již historická záležitost). Tuto utilitu by mělo jít (doufejme) přeložit jakýmkoli překladačem ANSI C:
/* * Nastroj pro vypis zakladnich udaju prectenych z hlavicky souboru .wav * * Pavel Tisnovsky 2014 * * Preklad; * gcc -ansi -Wall -pedantic read_wav_header.c */ #include <stdlib.h> #include <stdio.h> #include <stdint.h> /* * Funkce zavolana v pripade, ze pri cteni ze souboru dojde k chybe. */ void read_error(FILE *fin) { printf("Chyba pri cteni bajtu na offsetu %ld\n", ftell(fin)); if (fclose(fin)) { puts("... a uzavreni souboru taky selhalo!"); } exit(1); } /* * Precteni jednoho bajtu ze souboru s kontrolou, zda nedoslo k chybe. */ uint8_t read_byte(FILE *fin) { int i = fgetc(fin); if (i == EOF) { read_error(fin); } return i; } /* * Precteni dvou bajtu ze souboru s kontrolou, zda nedoslo k chybe. * (format little endian) */ uint16_t read_two_bytes(FILE *fin) { int i1 = read_byte(fin); int i2 = read_byte(fin); return (i2 << 8) | (i1); } /* * Precteni ctyr bajtu ze souboru s kontrolou, zda nedoslo k chybe. * (format little endian) */ uint32_t read_four_bytes(FILE *fin) { int i1 = read_byte(fin); int i2 = read_byte(fin); int i3 = read_byte(fin); int i4 = read_byte(fin); return (i4 << 24) | (i3 << 16) | (i2 << 8) | (i1); } /* * Precteni identifikatoru hlavniho bloku z hlavicky souboru .wav */ void print_chunk_id(FILE *fin) { uint32_t chunkID = read_four_bytes(fin); /* identifikator musi byt slozen ze znaku "RIFF" */ if (chunkID == (('R' ) | ('I' << 8) | ('F' << 16) | ('F' << 24))) { puts("ChunkID = 'RIFF', ok"); } else { printf("Wrong chunkID %x\n", chunkID); exit(1); } } /* * Precteni velikosti hlavniho bloku z hlavicky souboru .wav */ void print_chunk_size(FILE *fin) { uint32_t chunk_size = read_four_bytes(fin); printf("Chunk size = %d bytes\n", chunk_size); printf("Total file size = %d bytes\n", chunk_size + 8); } /* * Precteni formatu souboru z hlavicky souboru .wav */ void print_format(FILE *fin) { uint32_t format = read_four_bytes(fin); /* identifikator musi byt slozen ze znaku "WAVE" */ if (format == (('W' ) | ('A' << 8) | ('V' << 16) | ('E' << 24))) { puts("Format = 'WAVE', ok"); } else { printf("Wrong format %x\n", format); exit(1); } } /* * Precteni identifikatoru prvniho subbloku z hlavicky souboru .wav */ void print_subchunk1_id(FILE *fin) { uint32_t chunkID = read_four_bytes(fin); /* identifikator musi byt slozen ze znaku "fmt " */ if (chunkID == (('f' ) | ('m' << 8) | ('t' << 16) | (' ' << 24))) { puts("Subchunk1ID = 'fmt ', ok"); } else { printf("Wrong Subchunk1ID %x\n", chunkID); exit(1); } } /* * Precteni velikosti prvniho subbloku z hlavicky souboru .wav */ void print_subchunk1_size(FILE *fin) { uint32_t chunk_size = read_four_bytes(fin); printf("Subchunk1 size = %d bytes\n", chunk_size); } /* * Precteni audio formatu z hlavicky souboru .wav */ void print_audio_format(FILE *fin) { uint16_t audio_format = read_two_bytes(fin); printf("Audio format = %s\n", audio_format == 1 ? "PCM" : "compressed"); } /* * Precteni poctu audio kanalu z hlavicky souboru .wav */ void print_num_channels(FILE *fin) { uint16_t num_channels = read_two_bytes(fin); printf("Audio channels = %d\n", num_channels); } /* * Precteni vzorkovaci frekvence z hlavicky souboru .wav */ void print_sample_rate(FILE *fin) { uint32_t sample_rate = read_four_bytes(fin); printf("Sample rate = %d samples/sec\n", sample_rate); } /* * Precteni rychlosti prenosu dat do audio subsystemu z hlavicky souboru .wav */ void print_byte_rate(FILE *fin) { uint32_t byte_rate = read_four_bytes(fin); printf("Byte rate = %d bytes/sec\n", byte_rate); } /* * Precteni poctu bitu na jeden vzorek z hlavicky souboru .wav */ void print_bits_per_sample(FILE *fin) { uint16_t bits_per_sample = read_two_bytes(fin); printf("Bits per sample = %d\n", bits_per_sample); } /* * Tisk zakladnich informaci ziskanych z hlavicky souboru .wav */ int print_wav_header(char *file_name) { FILE *fin; fin = fopen(file_name, "rb"); if (!fin) { perror("File open error"); return 1; } print_chunk_id(fin); print_chunk_size(fin); print_format(fin); print_subchunk1_id(fin); print_subchunk1_size(fin); print_audio_format(fin); print_num_channels(fin); print_sample_rate(fin); print_byte_rate(fin); read_two_bytes(fin); print_bits_per_sample(fin); fclose(fin); return 0; } /* * Zaciname... */ int main(int argc, char **argv) { if (argc != 2) { puts("Usage: read_wav_header test.wav"); return 1; } return print_wav_header(argv[1]); } /* * Finito */
Překlad této utility se provede jednoduše:
gcc -ansi -Wall -pedantic read_wav_header.c -o read_wav_header
Příklad použití si ukážeme na čtveřici demonstračních souborů .WAV:
8×8bitpcm.wav:
ChunkID = 'RIFF', ok Chunk size = 110524 bytes Total file size = 110532 bytes Format = 'WAVE', ok Subchunk1ID = 'fmt ', ok Subchunk1 size = 16 bytes Audio format = PCM Audio channels = 1 Sample rate = 8000 samples/sec Byte rate = 8000 bytes/sec Bits per sample = 8
11k8bitpcm.wav:
ChunkID = 'RIFF', ok Chunk size = 152304 bytes Total file size = 152312 bytes Format = 'WAVE', ok Subchunk1ID = 'fmt ', ok Subchunk1 size = 16 bytes Audio format = PCM Audio channels = 1 Sample rate = 11025 samples/sec Byte rate = 11025 bytes/sec Bits per sample = 8
11k16bitpcm.wav:
ChunkID = 'RIFF', ok Chunk size = 304570 bytes Total file size = 304578 bytes Format = 'WAVE', ok Subchunk1ID = 'fmt ', ok Subchunk1 size = 16 bytes Audio format = PCM Audio channels = 1 Sample rate = 11025 samples/sec Byte rate = 22050 bytes/sec Bits per sample = 16
Kitten.wav:
ChunkID = 'RIFF', ok Chunk size = 37412 bytes Total file size = 37420 bytes Format = 'WAVE', ok Subchunk1ID = 'fmt ', ok Subchunk1 size = 16 bytes Audio format = PCM Audio channels = 1 Sample rate = 22050 samples/sec Byte rate = 44100 bytes/sec Bits per sample = 16
5. Načtení souboru se zvukem do aplikace využívající knihovnu SDLJava
Pro načtení souboru se zvukem s využitím knihovny SDLJava nemusíme podrobně znát vnitřní formát zvukových souborů. Musíme pouze korektně inicializovat zvukový subsystém, nastavit hlasitost zvukových kanálů a zahájit přehrávání. Inicializace zvukového subsystému se provádí nám již známou metodou SDLMixer.openAudio(), které se předá frekvence přehrávaných samplů (na výstupu, nikoli nutně na vstupu), formát samplů, počet výstupních zvukových kanálů a velikost bufferu použitého při přehrávání a mixování:
SDLMixer.openAudio(44100, SDLMixer.AUDIO_S16, 1, 1024);
Frekvence přehrávaných samplů se u her většinou nastavuje na 22050, tj. na poloviční frekvenci, než je použita u Audio CD. Tato frekvence na jednu stranu zajistí poměrně kvalitní zvukový výstup, na stranu druhou není zbytečně zatěžován procesor v průběhu mixování. Jen pro zajímavost se podívejme na některé často používané samplovací/přehrávací frekvence:
# | Frekvence | Použito |
---|---|---|
1 | 8000 | některé wireless telefony a další zařízení zaměřené pouze na přenos lidského hlasu |
2 | 11025 | čtvrtina samplovací frekvence CD, použito například u některých MPEG nebo i v počítačových hrách pro hlas a zvuky (nikoli pro hudbu) |
3 | 16000 | použito ve VoIP (postačuje bez větších zkreslení pro přenos lidského hlasu) |
4 | 22050 | polovina samplovací frekvence CD, použito i v mnoha hrách (výhodný poměr kvalita/nároky na mixování) |
5 | 22254 | kdysi použito na počítačích Macintosh, proto součást mnoha především starších multimediálních aplikací |
6 | 32000 | digi rádio apod. (dosažitelný frekvenční rozsah odpovídá FM radiu) |
7 | 44056 | použito v systémech, které kombinují audio signál s NTSC videem (přesně 3 samply na každý video řádek) |
8 | 44100 | samplovací frekvence CD, ve hrách může způsobit problémy při mixování většího množství kanálů (někdy i nutnost zvětšení bufferu) |
9 | 48000 | většina profi audio zařízení |
10 | 88200 | většina profi zařízení, použito ve chvíli, kdy má být výsledek uložen na CD |
11 | 96000 | DVD-audio |
Načtení souboru se zvukem zajistí metoda SDLMixer.loadWAV():
MixChunk effect = SDLMixer.loadWAV(fileName);
Samotné přehrávání (i na všech kanálech) zajistí příkaz SDLMixer.playChannel(), kde lze i specifikovat, kolikrát se má zvuk přehrát (poslední parametr). Lze dokonce zvolit i neustálé opakování přehrávání (loop) zadáním záporné hodnoty –1:
SDLMixer.playChannel(ALL_SOUND_CHANNELS, effect, 0);
Test, zda se na určitém kanálu či na libovolném kanálu provádí přehrávání zvuku, provádí metoda SDLMixer.playing() vracející počet aktivních kanálů, popř. (při volbě jen jednoho kanálu) hodnotu 1 (přehrává se) či 0 (nepřehrává se):
while (SDLMixer.playing(ALL_SOUND_CHANNELS) > 0) { SDLTimer.delay(100); }
6. Demonstrační příklad SDLTest66 – načtení a přehrání zvuku
V dnešním prvním demonstračním příkladu pojmenovaném SDLTest66 je ukázán nejjednodušší způsob přehrávání zvuků s využitím knihovny SDLJava. Nejprve je provedena inicializace zvukového subsystému zavoláním metody SDLMixer.openAudio(44100, SDLMixer.AUDIO_S16, 1, 1024), v níž je specifikováno, že se má vytvořit jeden (mono) výstupní zvukový kanál, frekvence mixování a přehrávání samplů má být 44100 Hz, formát přehrávaných a mixovaných samplů odpovídá šestnáctibitovým celým číslům (integer) se znaménkem a velikost zvukového bufferu má být 1024 bajtů, což je při přehrávání zvuku v jednom kanálu dostačující. Následně je nastavena hlasitost všech zvukových kanálů metodou SDLMixer.volume(ALL_SOUND_CHANNELS, 100), zvuk je načten příkazem SDLMixer.loadWAV(fileName) a konečně se zahájí jeho přehrávání s využitím metody SDLMixer.playChannel(ALL_SOUND_CHANNELS, effect, 0). To, zda se stále provádí přehrávání zvuku, lze zjistit metodou SDLMixer.playing(ALL_SOUND_CHANNELS).
Následuje výpis zdrojového kódu tohoto demonstračního příkladu:
import sdljava.SDLMain; import sdljava.SDLException; import sdljava.SDLTimer; import sdljava.mixer.SDLMixer; import sdljava.mixer.MixChunk; /** * Sedesaty sesty demonstracni priklad vyuzivajici knihovnu SDLjava. * * Prehravani zvuku s vyuzitim knihovny SDLJava. * * @author Pavel Tisnovsky */ public class SDLTest66 { /** * Konstanta zastupujici vsechny zvukove kanaly. */ static final int ALL_SOUND_CHANNELS = -1; /** * Prehrani zvukoveho vzorku. */ public static void playSample(String fileName) throws SDLException, InterruptedException { // nastaveni vlastnosti zvukoveho subsystemu i vybranych zvukovych kanalu SDLMixer.openAudio(44100, SDLMixer.AUDIO_S16, 1, 1024); SDLMixer.volume(ALL_SOUND_CHANNELS, 100); // nacteni samplu z externiho souboru MixChunk effect = SDLMixer.loadWAV(fileName); // prehrani samplu SDLMixer.playChannel(ALL_SOUND_CHANNELS, effect, 0); // musime pockat na ukonceni prehravani while (SDLMixer.playing(ALL_SOUND_CHANNELS) > 0) { SDLTimer.delay(100); } } /** * Spusteni sedesateho sesteho demonstracniho prikladu. */ public static void main(String[] args) { try { // inicializace knihovny SDLJava SDLMain.init(SDLMain.SDL_INIT_AUDIO); if (args.length != 1) { System.out.println("Usage: java SDLTest66 filename.wav"); } // ziskat jmeno prehravaneho samplu final String fileName = args[0]; // prehrani zvukoveho vzorku playSample(fileName); } catch (Exception e) { e.printStackTrace(); } finally { SDLMain.quit(); } } }
Skript pro překlad tohoto demonstračního příkladu na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib javac -cp $SDL_JAVA_LIBS/sdljava.jar SDLTest66.java
Dávkový soubor pro překlad tohoto demonstračního příkladu na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib javac -cp %SDL_JAVA_LIBS%\sdljava.jar SDLTest66.java
Skript pro spuštění na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib java -cp .:$SDL_JAVA_LIBS/sdljava.jar -Djava.library.path=$SDL_JAVA_LIBS SDLTest66
Dávkový soubor pro spuštění na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib java -cp .;%SDL_JAVA_LIBS%\sdljava.jar -Djava.library.path=%SDL_JAVA_LIBS% SDLTest66
7. Demonstrační příklad SDLTest67 – vliv velikosti bufferu na vznik chyb při přehrávání zvuků
V dnešním druhém demonstračním příkladu pojmenovaném SDLTest67 je ukázáno, k jakým problémům může dojít, pokud se zvolí příliš malá velikost bufferu používaného v průběhu přehrávání i mixování zvuků. V předchozím příkladu SDLTest66 byla velikost tohoto bufferu nastavena na 1024 bajtů, což odpovídá 512 šestnáctibitovým samplům, tj. přibližně 12 ms přehrávání při použití frekvence 44100 Hz. To (poněkud zjednodušeně řečeno) znamená, že mikroprocesor měl v průběhu přehrávání zvuků i hudby k dispozici maximálně oněch 12 ms, aby dokázal postupně mixovat a doplňovat audio data do bufferu. V příkladu SDLTest67 je naproti tomu velikost bufferu snížena na pouhých 32 bajtů, což znamená, že při přehrávání bude prakticky na všech počítačích docházet k jasně slyšitelným chybám, což si ostatně lze snadno ověřit překladem a následným spuštěním tohoto demonstračního příkladu (velikost bufferu je možné ještě více snížit nebo naopak zvýšit – pro přehrávání hudby se například doporučuje hodnota 4096 bajtů).
import sdljava.SDLMain; import sdljava.SDLException; import sdljava.SDLTimer; import sdljava.mixer.SDLMixer; import sdljava.mixer.MixChunk; /** * Sedesaty sedmy demonstracni priklad vyuzivajici knihovnu SDLjava. * * Prehravani zvuku s vyuzitim knihovny SDLJava. * * @author Pavel Tisnovsky */ public class SDLTest67 { /** * Konstanta zastupujici vsechny zvukove kanaly. */ static final int ALL_SOUND_CHANNELS = -1; /** * Prehrani zvukoveho vzorku. */ public static void playSample(String fileName) throws SDLException, InterruptedException { // nastaveni vlastnosti zvukoveho subsystemu i vybranych zvukovych kanalu // velikost bufferu je velmi mala, coz povede k problemum pri prehravani zvuku SDLMixer.openAudio(44100, SDLMixer.AUDIO_S16, 1, 32); SDLMixer.volume(ALL_SOUND_CHANNELS, 100); // nacteni samplu z externiho souboru MixChunk effect = SDLMixer.loadWAV(fileName); // prehrani samplu SDLMixer.playChannel(ALL_SOUND_CHANNELS, effect, 0); // musime pockat na ukonceni prehravani while (SDLMixer.playing(ALL_SOUND_CHANNELS) > 0) { SDLTimer.delay(100); } } /** * Spusteni sedesateho sedmeho demonstracniho prikladu. */ public static void main(String[] args) { try { // inicializace knihovny SDLJava SDLMain.init(SDLMain.SDL_INIT_AUDIO); if (args.length != 1) { System.out.println("Usage: java SDLTest66 filename.wav"); } // ziskat jmeno prehravaneho samplu final String fileName = args[0]; // prehrani zvukoveho vzorku playSample(fileName); } catch (Exception e) { e.printStackTrace(); } finally { SDLMain.quit(); } } }
Skript pro překlad tohoto demonstračního příkladu na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib javac -cp $SDL_JAVA_LIBS/sdljava.jar SDLTest67.java
Dávkový soubor pro překlad tohoto demonstračního příkladu na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib javac -cp %SDL_JAVA_LIBS%\sdljava.jar SDLTest67.java
Skript pro spuštění na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib java -cp .:$SDL_JAVA_LIBS/sdljava.jar -Djava.library.path=$SDL_JAVA_LIBS SDLTest67
Dávkový soubor pro spuštění na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib java -cp .;%SDL_JAVA_LIBS%\sdljava.jar -Djava.library.path=%SDL_JAVA_LIBS% SDLTest67
8. Demonstrační příklad SDLTest68 – využití osmi zvukových kanálů pro mixování
V dnešním posledním demonstračním příkladu SDLTest68 je ukázáno, jakým způsobem lze zajistit mixování většího množství vstupních zvukových kanálů do jediného kanálu výstupního. Vzhledem k tomu, že se má mixovat a přehrávat osm zvukových kanálů, je velikost bufferu v metodě SDLMixer.openAudio(44100, SDLMixer.AUDIO_S16, 1, 2048) zvětšena na 2048 bajtů. Tato velikost je dostačující i na mém obstarožním deset let starém počítači. Následně je načten jediný zvuk metodou SDLMixer.loadWAV(fileName) a posléze začne být tento zvuk přehráván osmkrát, vždy se zpožděním přibližně 100 ms, čímž vznikne efekt několikanásobného echa:
// prehrani samplu na osmi kanalech for (int channel = 0; channel < CHANNELS; channel++) { SDLTimer.delay(100); SDLMixer.playChannel(channel, effect, 0); }
Test na ukončení přehrávání je stále prakticky totožný s testy, které jsme již viděli i v předchozích demonstračních příkladech:
int channels; while ((channels = (SDLMixer.playing(ALL_SOUND_CHANNELS))) > 0) { System.out.println(channels + " channel(s) are playing"); SDLTimer.delay(100); }
Zdrojový kód demonstračního příkladu SDLTest68 vypadá následovně:
import sdljava.SDLMain; import sdljava.SDLException; import sdljava.SDLTimer; import sdljava.mixer.SDLMixer; import sdljava.mixer.MixChunk; /** * Sedesaty osmy demonstracni priklad vyuzivajici knihovnu SDLjava. * * Prehravani a mixovani zvuku s vyuzitim knihovny SDLJava. * * @author Pavel Tisnovsky */ public class SDLTest68 { /** * Konstanta zastupujici vsechny zvukove kanaly. */ static final int ALL_SOUND_CHANNELS = -1; /** * Pocet pouzitych zvukovych kanalu. */ static final int CHANNELS = 8; /** * Prehrani zvukoveho vzorku. */ public static void playSample(String fileName) throws SDLException, InterruptedException { // nastaveni vlastnosti zvukoveho subsystemu i vybranych zvukovych kanalu SDLMixer.openAudio(44100, SDLMixer.AUDIO_S16, 1, 2048); // nastavit pocet zvukovych kanalu SDLMixer.allocateChannels(CHANNELS); // nastavit hlasitost vsech kanalu SDLMixer.volume(ALL_SOUND_CHANNELS, 100); // nacteni samplu z externiho souboru MixChunk effect = SDLMixer.loadWAV(fileName); // prehrani samplu na osmi kanalech for (int channel = 0; channel < CHANNELS; channel++) { SDLTimer.delay(100); SDLMixer.playChannel(channel, effect, 0); } // musime pockat na ukonceni prehravani int channels; while ((channels = (SDLMixer.playing(ALL_SOUND_CHANNELS))) > 0) { System.out.println(channels + " channel(s) are playing"); SDLTimer.delay(100); } } /** * Spusteni sedesateho osmeho demonstracniho prikladu. */ public static void main(String[] args) { try { // inicializace knihovny SDLJava SDLMain.init(SDLMain.SDL_INIT_AUDIO); if (args.length != 1) { System.out.println("Usage: java SDLTest66 filename.wav"); } // ziskat jmeno prehravaneho samplu final String fileName = args[0]; // prehrani zvukoveho vzorku playSample(fileName); } catch (Exception e) { e.printStackTrace(); } finally { SDLMain.quit(); } } }
Skript pro překlad tohoto demonstračního příkladu na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib javac -cp $SDL_JAVA_LIBS/sdljava.jar SDLTest68.java
Dávkový soubor pro překlad tohoto demonstračního příkladu na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib javac -cp %SDL_JAVA_LIBS%\sdljava.jar SDLTest68.java
Skript pro spuštění na Linuxu:
#!/bin/sh SDL_JAVA_LIBS=./sdljava-0.9.1/lib java -cp .:$SDL_JAVA_LIBS/sdljava.jar -Djava.library.path=$SDL_JAVA_LIBS SDLTest68
Dávkový soubor pro spuštění na Windows:
set SDL_JAVA_LIBS=.\sdljava-0.9.1\lib java -cp .;%SDL_JAVA_LIBS%\sdljava.jar -Djava.library.path=%SDL_JAVA_LIBS% SDLTest68
9. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
Všechny čtyři dnes popsané demonstrační příklady byly společně s podpůrnými skripty určenými pro jejich překlad a následné spuštění uloženy do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Podobně jako tomu bylo i v předchozích několika dílech tohoto seriálu, i ke dnešním příkladům jsou přiloženy skripty využitelné pro jejich překlad a spuštění. Navíc byly přidány i skripty využitelné ve Windows:
10. Odkazy na Internetu
- Audio File Formats.
http://sox.sourceforge.net/AudioFormats-11.html - TestSounds.com: pure digital sounds to test your audio
http://www.testsounds.com/ - Test Tones (20hz – 20khz)
http://mdf1.tripod.com/test-tones.html - WAV (Wikipedia)
http://en.wikipedia.org/wiki/WAV - WAVE PCM soundfile format
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ - Audio Interchange File Format
http://en.wikipedia.org/wiki/Aiff - Musical Instrument Digital Interface,
http://en.wikipedia.org/wiki/Musical_Instrument_Digital_Interface - A MIDI Pedalboard Encode,
http://www.pykett.org.uk/a_midi_pedalboard_encoder.htm - MIDI Note Number, Frequency Table,
http://tonalsoft.com/pub/news/pitch-bend.aspx - Note names, MIDI numbers and frequencies,
http://www.phys.unsw.edu.au/jw/notes.html - The MIDI Specification,
http://www.gweep.net/~prefect/eng/reference/protocol/midispec.html - Essentials of the MIDI protocol,
http://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html - General MIDI,
http://en.wikipedia.org/wiki/General_MIDI - Obecné MIDI (General MIDI),
http://www-kiv.zcu.cz/~herout/html_sbo/midi/5.html - Custom Chips: Paula
http://www.amiga-hardware.com/showhardware.cgi?HARDID=1460 - Big Book of Amiga Hardware
http://www.amiga-resistance.info/bboahfaq/ - Amiga Hardware Database
http://amiga.resource.cx/ - ExoticA
http://www.exotica.org.uk/wiki/Main_Page - The absolute basics of Amiga audio
http://www.sufo.estates.co.uk/amiga/amimus.html - Wikipedia: Tracker
http://en.wikipedia.org/wiki/Tracker - Wikipedia: Trackers
http://en.wikipedia.org/wiki/Trackers - Ultimate Soundtracker
http://en.wikipedia.org/wiki/Ultimate_Soundtracker - Protracker
http://en.wikipedia.org/wiki/ProTracker - Impulse Tracker
http://en.wikipedia.org/wiki/Impulse_Tracker - Scream Tracker
http://en.wikipedia.org/wiki/ScreamTracker - MikMod for Java
http://jmikmod.berlios.de/ - List of audio trackers
http://en.wikipedia.org/wiki/List_of_audio_trackers - Wikipedia: Module File
http://en.wikipedia.org/wiki/Module_file - Wikipedia: Chiptune
http://en.wikipedia.org/wiki/Chiptune - SDL_mixer 2.0
http://www.libsdl.org/projects/SDL_mixer/ - SDLJava: package sdljava.ttf
http://sdljava.sourceforge.net/docs/api/sdljava/ttf/package-summary.html#package_description - SDLJava: class sdljava.ttf.SDLTTF
http://sdljava.sourceforge.net/docs/api/sdljava/ttf/SDLTTF.html - SDLJava: class sdljava.ttf.SDLTrueTypeFont
http://sdljava.sourceforge.net/docs/api/sdljava/ttf/SDLTrueTypeFont.html - SDL_ttf Documentation
http://www.libsdl.org/projects/SDL_ttf/docs/ - SDL_ttf 2.0 (není prozatím součástí SDLJava)
http://www.libsdl.org/projects/SDL_ttf/ - SDL_ttf doc
http://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_frame.html - SDL 1.2 Documentation: SDL_Surface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsurface.html - SDL 1.2 Documentation: SDL_PixelFormat
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlpixelformat.html - SDL 1.2 Documentation: SDL_LockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdllocksurface.html - SDL 1.2 Documentation: SDL_UnlockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlunlocksurface.html - SDL 1.2 Documentation: SDL_LoadBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlloadbmp.html - SDL 1.2 Documentation: SDL_SaveBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsavebmp.html - SDL 1.2 Documentation: SDL_BlitSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlblitsurface.html - SDL 1.2 Documentation: SDL_VideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlvideoinfo.html - SDL 1.2 Documentation: SDL_GetVideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlgetvideoinfo.html - glDrawArrays
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawArrays.xml - glDrawElements
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawElements.xml - glDrawArraysInstanced
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawArraysInstanced.xml - glDrawElementsInstanced
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawElementsInstanced.xml - Root.cz: Seriál Grafická knihovna OpenGL
http://www.root.cz/serialy/graficka-knihovna-opengl/ - Root.cz: Seriál Tvorba přenositelných grafických aplikací využívajících knihovnu GLUT
http://www.root.cz/serialy/tvorba-prenositelnych-grafickych-aplikaci-vyuzivajicich-knihovnu-glut/ - Best Practices for Working with Vertex Data
https://developer.apple.com/library/ios/documentation/3ddrawing/conceptual/opengles_programmingguide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html - Class BufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/image/BufferStrategy.html - Class Graphics
http://docs.oracle.com/javase/1.5.0/docs/api/java/awt/Graphics.html - Double Buffering and Page Flipping
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html - BufferStrategy and BufferCapabilities
http://docs.oracle.com/javase/tutorial/extra/fullscreen/bufferstrategy.html - Java:Tutorials:Double Buffering
http://content.gpwiki.org/index.php/Java:Tutorials:Double_Buffering - Double buffer in standard Java AWT
http://www.codeproject.com/Articles/2136/Double-buffer-in-standard-Java-AWT - Java 2D: Hardware Accelerating – Part 1 – Volatile Images
http://www.javalobby.org/forums/thread.jspa?threadID=16840&tstart=0 - Java 2D: Hardware Accelerating – Part 2 – Buffer Strategies
http://www.javalobby.org/java/forums/t16867.html - How does paintComponent work?
http://stackoverflow.com/questions/15544549/how-does-paintcomponent-work - A Swing Architecture Overview
http://www.oracle.com/technetwork/java/architecture-142923.html - Class javax.swing.JComponent
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html - Class java.awt.Component
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.html - Class java.awt.Component.BltBufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.BltBufferStrategy.html - Class java.awt.Component.FlipBufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.FlipBufferStrategy.html - Metoda java.awt.Component.isDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.html#isDoubleBuffered() - Metoda javax.swing.JComponent.isDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html#isDoubleBuffered() - Metoda javax.swing.JComponent.setDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html#setDoubleBuffered(boolean) - Javadoc – třída GraphicsDevice
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsDevice.html - Javadoc – třída GraphicsEnvironment
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsEnvironment.html - Javadoc – třída GraphicsConfiguration
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsConfiguration.html - Javadoc – třída DisplayMode
http://docs.oracle.com/javase/7/docs/api/java/awt/DisplayMode.html - Lesson: Full-Screen Exclusive Mode API
http://docs.oracle.com/javase/tutorial/extra/fullscreen/ - Full-Screen Exclusive Mode
http://docs.oracle.com/javase/tutorial/extra/fullscreen/exclusivemode.html - Display Mode
http://docs.oracle.com/javase/tutorial/extra/fullscreen/displaymode.html - Using the Full-Screen Exclusive Mode API in Java
http://www.developer.com/java/other/article.php/3609776/Using-the-Full-Screen-Exclusive-Mode-API-in-Java.htm - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - MultiMedia eXtensions
http://softpixel.com/~cwright/programming/simd/mmx.phpi - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - Intel corporation: Extending the Worldr's Most Popular Processor Architecture
http://download.intel.com/technology/architecture/new-instructions-paper.pdf - SIMD architectures:
http://arstechnica.com/old/content/2000/03/simd.ars/ - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html