OpenAL: Managing Playback Streams
This tutorial demonstrates how you can manage a playback stream using OpenAL.
For additional OpenAL code samples, see Example Code.
Warm-up
Become familiar with the OpenAL API basics by learning about:
-
Initializing OpenAL
Initialize OpenAL for use.
-
Requesting a Source and Audio Buffer
Request a source, and request and prepare an audio buffer.
-
Controlling Audio Stream Playback
Start and stop playback.
-
Using Buffer Queuing for Stream Playback
Queue one or more buffers to be used for a streamed audio source.
Initializing OpenAL
To initialize OpenAL for use:
-
To use the functions and data types of the OpenAL API (in mobile and wearable applications), include the <AL/al.h> and <AL/alc.h> header files in your application:
#include <AL/al.h> #include <AL/alc.h>
-
Retrieve the default device name, and open the default device.
The following example code verifies that a given extension is available, retrieves the names of all available devices and the name of the default device using the alcGetString() function, and opens the default device using the alcOpenDevice() function:
// Verify that a given extension is available for the current context enumeration = alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"); if (enumeration == AL_FALSE) { LOGI("[%s] enumeration extension not available", __func__); } // Retrieve a list of available devices // Each device name is separated by a single NULL character // and the list is terminated with 2 NULL characters deviceNameList = alcGetString(NULL, ALC_DEVICE_SPECIFIER)); // Retrieve the default device name defaultDeviceName = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); // Open the default device device = alcOpenDevice(defaultDeviceName); if (!device) { LOGI("[%s] unable to open default device", __func__); return; } LOGI("[%s] Device : %s ", __func__, alcGetString(device, ALC_DEVICE_SPECIFIER));
The alcOpenDevice() function opens the audio device through the pulseaudio layer.
-
If the device is opened successfully, create a context for the device using the alcCreateContext() function, and set the context as active using the alcMakeContextCurrent() function:
// Create context context = alcCreateContext(device, NULL); if (context == NULL) { alcCloseDevice(device); LOGI("[%s] failed to create context", __func__); return; } // Set active context if (!alcMakeContextCurrent(context)) { alcDestroyContext(context); alcCloseDevice(device); LOGI("[%s] failed to make default context", __func__); return; }
Once the device is associated with an active context, the AL commands are applied to that context.
Requesting a Source and Audio Buffer
Playback requires a source object for controlling the playback, and a buffer object for storing the audio data to be played.
To request a source and audio buffer:
-
Request the source using the alSources() function, and update the source attributes, such as the default gain and sound position:
// Request a source name alGenSources((ALuint)1, &source); // Set the default volume alSourcef(source, AL_GAIN, 1); // Set the default position of the sound alSource3f(source, AL_POSITION, 0, 0, 0);
-
Request the audio buffer, and specify the allocated PCM buffer and size:
// Request a buffer name alGenBuffers(1, &buffer); ALuint frequency = 22050; ALenum format = AL_FORMAT_MONO8; // Specify sample data using alBufferData alBufferData(buffer, format, _data_buffer, dataSize, frequency);
In the above example code, the _data_buffer parameter points to the audio sample data. The memory for the data has been allocated using the malloc() function. The dataSize parameter defines the amount of data to be buffered.
The following table lists the supported audio sample formats:
Table: Supported audio sample formats Format Description AL_FORMAT_MONO8 Unsigned 8-bit mono audio sample AL_FORMAT_MONO16 Unsigned 16-bit mono audio sample AL_FORMAT_STEREO8 Unsigned 8-bit stereo audio sample (interleaved) AL_FORMAT_STEREO16 Unsigned 16-bit stereo audio sample (interleaved)
Controlling Audio Stream Playback
To control the playback, use the following state transition functions:
- alSourcePlay(): Play, replay, or resume a source.
- alSourceStop(): Stop one or more sources.
- alSourceRewind(): Rewind a source (set the playback position to the beginning).
- alSourcePause(): Pause a source.
To start and stop playback:
-
To play the audio stream, implement the start event of the playback action (for example, a start button click).
In the following example code, the whole audio buffer is allocated and filled before the playback starts using the alSourcei() function. The second parameter specifies the source type as static. Start the playback after changing the state to play.
// Function: _on_click1() // Source specifies the current buffer object alSourcei(source, AL_BUFFER, buffer); // Change the state to play alSourcePlay(source);
-
When a stop event is triggered, change the playback state to stop to end the playback:
// Function: _on_click2() // Change the state to stop alSourceStop(source);
-
When the playback is finished, release the resources by cleaning up the source, buffer, context, and device:
alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); device = alcGetContextsDevice(context); alcMakeContextCurrent(NULL); alcDestroyContext(context); alcCloseDevice(device);
Using Buffer Queuing for Stream Playback
OpenAL provides a buffer queuing method for the streamed audio source, in which one or more buffers can be queued and dequeued after consumed.
To queue and play multiple buffers:
-
Submit one or more buffers before starting the playback:
#define DATA_CHUNK_SIZE (1024) // This example uses 4 buffers and 1 source static ALuint buffer[4], source; alGenSources((ALuint)1, &source); alGenBuffers(4, buffer); // Fill all the buffers with audio data from the wave file for (iLoop = 0; iLoop < 4; iLoop++) { alBufferData(buffer[iLoop], AL_FORMAT_MONO8, pData, DATA_CHUNK_SIZE, 22050); alSourceQueueBuffers(source, 1, &buffer[iLoop]); }
-
Start the playback stream, and push the buffer (for example, 1024 bytes) periodically on click events.
If a loop detects a consumed buffer (iBuffersProcessed) by querying AL_BUFFERS_PROCESSED, dequeue the consumed buffer using the alSourceUnqueueBuffers() function. To continue the playback, fill and queue the buffer again using the alSourceQueueBuffers() function. Run the loop in a thread separate from the application main thread.
// Start playing the streamed audio alSourcePlay(source); LOGI("[%s] alSourcePlay", __func__); // Buffer queuing loop must operate in a new thread iBuffersProcessed = 0; while (!thread_finish) { usleep(10 * 1000); // Sleep 10 msec periodically alGetSourcei(source, AL_BUFFERS_PROCESSED, &iBuffersProcessed); iTotalBuffersProcessed += iBuffersProcessed; ALOGI("Buffers Processed %d", iTotalBuffersProcessed); // For each processed buffer, remove it from the source queue, read the next chunk of // audio data from the file, fill the buffer with new data, and add it to the source queue while (iBuffersProcessed) { // Remove the buffer from the queue (uiBuffer contains the buffer ID for the dequeued buffer) uiBuffer = 0; alSourceUnqueueBuffers(source, 1, &uiBuffer); // Read more pData audio data (if there is any) // Copy audio data to buffer alBufferData(uiBuffer, AL_FORMAT_MONO8, pData, DATA_CHUNK_SIZE, 22050); // Insert the audio buffer to the source queue alSourceQueueBuffers(source, 1, &uiBuffer); iBuffersProcessed--; } }