-
Notifications
You must be signed in to change notification settings - Fork 36
2.1. OpenAL
OpenAL is a cross-platform audio API for which LWJGL includes bindings. The API bears great similarities to OpenGL, and should thus be relatively easy to learn if you are familiar with OpenGL. While the goal of this guide is to show how to use OpenAL in LWJGL it also includes a basic explanation of some of the core concepts of OpenAL.
It is also worth mentioning that unlike OpenGL, OpenAL is split into to separate
parts: Core AL (All functions that start with al
) which contains functions
related to audio playback, listener positioning and effects. ALC, the Audio
Library Context (All functions that start with alc
) deals with audio devices
and resource management. When writing your game you will mostly be using the
core AL functions.
Throughout this guide various functions from a variety of packages will be used. Most IDEs are able to automatically add the correct imports to your code. In case you are not using an IDE you can find a list of all imports in the complete code listing at the bottom of this document. Furthermore, you can reffer to the javadoc for a list of classes and their members.
OpenAL and stb (for loading .ogg
files) are required. Both of these are
included in all presets, so if you are already running LWJGL you should have
them.
The first step in setting up OpenAL is choosing which sound device to use. Because LWJGL uses OpenAL SOFT, a software implementation of OpenAL, we will use the virtual device provided by it. Device creation is done like so:
String defaultDeviceName = alcGetString(0, ALC_DEFAULT_DEVICE_SPECIFIER);
long device = alcOpenDevice(defaultDeviceName);
Now that we have a device we need to create a context and tell OpenAL that we want to use this context. The function used to create a context takes a list of attributes. We can simply pass in an array containing only a 0 to indicate that we are not passing in any attributes:
int[] attributes = {0};
long context = alcCreateContext(device, attributes);
alcMakeContextCurrent(context);
Finally we need to create an ALCCapabilities
object for ALC and a
ALCapabilities
object for core AL. The objects are used internally for
making function calls. Aditionally, they provide information about which
features are available.
ALCCapabilities alcCapabilities = ALC.createCapabilities(device);
ALCapabilities alCapabilities = AL.createCapabilities(alcCapabilities);
Checks for which features are available can be done like so (As allways, reffer to the javadoc for a full list of things you can check for):
if(alCapabilities.OpenAL10) {
//OpenAL 1.0 is supported
}
When we are done using OpenAL we can destroy the context and close the device:
alcDestroyContext(context);
alcCloseDevice(device);
OpenAL splits playing a sound into two parts: Sources and buffers. A buffer contains the data for a single sound. In your application you will have one buffer per sound file. A source contains information about how to play a sound, including but not limited to position, gain, and pitch. This separation allows you to play a single sound multiple times while loading it into memory only once.
At this point you will need a simple sound clip that you can load and play,
to test if everything is working correctly. Online tools such as
Chiptone and bfxr
can be used to easily create simple sound effects for games, and have presets
for some common effects. Note that these tools export .wav
files, this guide
only shows how to load .ogg
vorbis-encoded files. You can use a command
line tool such as ffmpeg
to convert a .wav
to a .ogg
.
In order to use a sound with OpenAL we need its raw data in memory. Luckily, STB which is included in LWJGL has a simple utility to load an ogg-vorbis file. Note that this code uses some LWJGL utilities to allocate memory on the stack which is used for storing extra information returned by STB. For a more in depth guide on memory management in LWJGL you might want check out section 3 and 4 of these examples.
String fileName = "sound.ogg";
//Allocate space to store return information from the function
stackPush();
IntBuffer channelsBuffer = stackMallocInt(1);
stackPush();
IntBuffer sampleRateBuffer = stackMallocInt(1);
ShortBuffer rawAudioBuffer = stb_vorbis_decode_filename(fileName, channelsBuffer, sampleRateBuffer);
//Retreive the extra information that was stored in the buffers by the function
int channels = channelsBuffer.get();
int sampleRate = sampleRateBuffer.get();
//Free the space we allocated earlier
stackPop();
stackPop();
Next, we need to send the data to an OpenAL buffer, so it can be used. This is where the similarities to OpenGL start to apear. Just like in GL, we to request a buffer and then store data in it. In addition to giving OpenAL the raw data we also have to tell it in which format the data is.
//Find the correct OpenAL format
int format = -1;
if(channels == 1) {
format = AL_FORMAT_MONO16;
} else if(channels == 2) {
format = AL_FORMAT_STEREO16;
}
//Request space for the buffer
int bufferPointer = alGenBuffers();
//Send the data to OpenAL
alBufferData(bufferPointer, format, rawAudioBuffer, sampleRate);
//Free the memory allocated by STB
free(rawAudioBuffer);
We now have a sound loaded into memory. Before we move on it is worth going over how to delete a buffer. You should always delete your buffers when you no longer need them. Note that trying to delete a buffer while it is still bound to a source will cause an error.
alDeleteBuffers(bufferPointer);
In order to play a sound we need a source. This is sort of like a speaker: We put it somewhere, we set a volume, and give it something to play. The source is created just like the buffer:
int sourcePointer = alGenSources();
//Assign our buffer to the source
alSourcei(sourcePointer, AL_BUFFER, bufferPointer);
Now the source can be played using alPlaySource
:
alSourcePlay(sourcePointer);
This function will not block until the sound has finished playing. If you
are not playing the sound as part of a game loop you might want to tell your
program to wait for a second so the sound has time to ring out before moving
on. This can be done rudimentarily with Thread.sleep(1000)
.
When you are done using a source you can delete it like so:
alDeleteSources(sourcePointer);
OpenAL reports any errors it encounters to you using the alGetError
method.
When calling this method it returns a code from a list of error codes. All
these error codes are defined as constants. You should call this method
frequently within your sound-related code while debugging in order to
discover any mistakes you might have made. Below is a list of all error
codes and an example showing how to use the method. By reading the
documentation
you can find a list of errors which each function can cause.
int error = alGetError();
if(error != AL_NO_ERROR) {
//Handle the error
}
Error codes:
AL_NO_ERROR
AL_INVALID_NAME
AL_INVALID_ENUM
AL_INVALID_VALUE
AL_INVALID_OPERATION
AL_OUT_OF_MEMORY
In this simple demo we only play a single static sound. OpenAL is however designed with games in mind, and this short guide barely scratches the surface of what is possible. For further reference, the javadoc contains documentation for all OpenAL functions, and by skimming through it you can get a good idea of what tools you have at your hands. Also, the OpenAL Programmers Guide is worth giving a look at if you are interested in a more in-depth explanation of how different components work.
Note that this sample needs LWJGL with OpenAL and stb to compile. The file
sound.ogg
is not included.
import org.lwjgl.openal.*;
import org.lwjgl.system.*;
import java.nio.*;
import static org.lwjgl.openal.AL10.*;
import static org.lwjgl.openal.ALC10.*;
import static org.lwjgl.stb.STBVorbis.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.libc.LibCStdlib.*;
...
//Initialization
String defaultDeviceName = alcGetString(0, ALC_DEFAULT_DEVICE_SPECIFIER);
long device = alcOpenDevice(defaultDeviceName);
int[] attributes = {0};
long context = alcCreateContext(device, attributes);
alcMakeContextCurrent(context);
ALCCapabilities alcCapabilities = ALC.createCapabilities(device);
ALCapabilities alCapabilities = AL.createCapabilities(alcCapabilities);
ShortBuffer rawAudioBuffer;
int channels;
int sampleRate;
try (MemoryStack stack = stackPush()) {
//Allocate space to store return information from the function
IntBuffer channelsBuffer = stack.mallocInt(1);
IntBuffer sampleRateBuffer = stack.mallocInt(1);
rawAudioBuffer = stb_vorbis_decode_filename("sound.ogg", channelsBuffer, sampleRateBuffer);
//Retreive the extra information that was stored in the buffers by the function
channels = channelsBuffer.get(0);
sampleRate = sampleRateBuffer.get(0);
}
//Find the correct OpenAL format
int format = -1;
if (channels == 1) {
format = AL_FORMAT_MONO16;
} else if (channels == 2) {
format = AL_FORMAT_STEREO16;
}
//Request space for the buffer
int bufferPointer = alGenBuffers();
//Send the data to OpenAL
alBufferData(bufferPointer, format, rawAudioBuffer, sampleRate);
//Free the memory allocated by STB
free(rawAudioBuffer);
//Request a source
int sourcePointer = alGenSources();
//Assign the sound we just loaded to the source
alSourcei(sourcePointer, AL_BUFFER, bufferPointer);
//Play the sound
alSourcePlay(sourcePointer);
try {
//Wait for a second
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
//Terminate OpenAL
alDeleteSources(sourcePointer);
alDeleteBuffers(bufferPointer);
alcDestroyContext(context);
alcCloseDevice(device);