aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt20
-rw-r--r--examples/alffmpeg.c610
-rw-r--r--examples/alffmpeg.h66
-rw-r--r--examples/alhelpers.c226
-rw-r--r--examples/alhelpers.h95
-rw-r--r--examples/alstream.c331
6 files changed, 1348 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 25fd546d..8e0f9dae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,7 @@ INCLUDE(CheckSymbolExists)
INCLUDE(CheckCCompilerFlag)
INCLUDE(CheckCSourceCompiles)
INCLUDE(CheckTypeSize)
+INCLUDE(FindPkgConfig)
PROJECT(OpenAL C)
@@ -56,6 +57,8 @@ OPTION(WERROR "Treat compile warnings as errors" OFF)
OPTION(UTILS "Build and install utility programs" ON)
+OPTION(EXAMPLES "Build and install example programs" ON)
+
OPTION(ALSOFT_CONFIG "Install alsoft.conf configuration file" OFF)
@@ -730,3 +733,20 @@ IF(UTILS)
MESSAGE(STATUS "Building utility programs")
MESSAGE(STATUS "")
ENDIF()
+
+IF(EXAMPLES)
+ PKG_CHECK_MODULES(FFMPEG libavcodec libavformat)
+ IF(FFMPEG_FOUND)
+ ADD_EXECUTABLE(alstream examples/alhelpers.c examples/alffmpeg.c examples/alstream.c)
+ TARGET_LINK_LIBRARIES(alstream ${FFMPEG_LIBRARIES} ${LIBNAME})
+ SET_TARGET_PROPERTIES(alstream PROPERTIES COMPILE_FLAGS "${FFMPEG_CFLAGS}")
+ INSTALL(TARGETS alstream
+ RUNTIME DESTINATION bin
+ LIBRARY DESTINATION "lib${LIB_SUFFIX}"
+ ARCHIVE DESTINATION "lib${LIB_SUFFIX}"
+ )
+
+ MESSAGE(STATUS "Building ffmpeg example programs")
+ MESSAGE(STATUS "")
+ ENDIF()
+ENDIF()
diff --git a/examples/alffmpeg.c b/examples/alffmpeg.c
new file mode 100644
index 00000000..13cc0efe
--- /dev/null
+++ b/examples/alffmpeg.c
@@ -0,0 +1,610 @@
+/*
+ * FFmpeg Decoder Helpers
+ *
+ * Copyright (c) 2011 by Chris Robinson <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* This file contains routines for helping to decode audio using libavformat
+ * and libavcodec (ffmpeg). There's very little OpenAL-specific code here. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <assert.h>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "alhelpers.h"
+#include "alffmpeg.h"
+
+
+static size_t NextPowerOf2(size_t value)
+{
+ size_t powerOf2 = 1;
+
+ if(value)
+ {
+ value--;
+ while(value)
+ {
+ value >>= 1;
+ powerOf2 <<= 1;
+ }
+ }
+ return powerOf2;
+}
+
+
+struct MemData {
+ char *buffer;
+ size_t length;
+ size_t pos;
+};
+
+static int MemData_read(void *opaque, uint8_t *buf, int buf_size)
+{
+ struct MemData *membuf = (struct MemData*)opaque;
+ int rem = membuf->length - membuf->pos;
+
+ if(rem > buf_size)
+ rem = buf_size;
+
+ memcpy(buf, &membuf->buffer[membuf->pos], rem);
+ membuf->pos += rem;
+
+ return rem;
+}
+
+static int MemData_write(void *opaque, uint8_t *buf, int buf_size)
+{
+ struct MemData *membuf = (struct MemData*)opaque;
+ int rem = membuf->length - membuf->pos;
+
+ if(rem > buf_size)
+ rem = buf_size;
+
+ memcpy(&membuf->buffer[membuf->pos], buf, rem);
+ membuf->pos += rem;
+
+ return rem;
+}
+
+static int64_t MemData_seek(void *opaque, int64_t offset, int whence)
+{
+ struct MemData *membuf = (struct MemData*)opaque;
+
+ switch(whence)
+ {
+ case SEEK_SET:
+ if(offset < 0 || offset > membuf->length)
+ return -1;
+ membuf->pos = offset;
+ break;
+
+ case SEEK_CUR:
+ if((offset >= 0 && offset > membuf->length-membuf->pos) ||
+ (offset < 0 && offset < -membuf->pos))
+ return -1;
+ membuf->pos += offset;
+ break;
+
+ case SEEK_END:
+ if(offset > 0 || offset < -membuf->length)
+ return -1;
+ membuf->pos = membuf->length + offset;
+ break;
+
+ default:
+ return -1;
+ }
+
+ return membuf->pos;
+}
+
+
+struct PacketList {
+ AVPacket pkt;
+ struct PacketList *next;
+};
+
+struct MyStream {
+ AVCodecContext *CodecCtx;
+ int StreamIdx;
+
+ struct PacketList *Packets;
+
+ char *DecodedData;
+ size_t DecodedDataSize;
+
+ FilePtr parent;
+};
+
+struct MyFile {
+ AVFormatContext *FmtCtx;
+
+ StreamPtr *Streams;
+ size_t StreamsSize;
+
+ struct MemData membuf;
+};
+
+
+static int done_init = 0;
+
+FilePtr openAVFile(const char *fname)
+{
+ FilePtr file;
+
+ /* We need to make sure ffmpeg is initialized. Optionally silence warning
+ * output from the lib */
+ if(!done_init) {av_register_all();
+ av_log_set_level(AV_LOG_ERROR);
+ done_init = 1;}
+
+ file = (FilePtr)calloc(1, sizeof(*file));
+ if(file && avformat_open_input(&file->FmtCtx, fname, NULL, NULL) == 0)
+ {
+ /* After opening, we must search for the stream information because not
+ * all formats will have it in stream headers */
+ if(av_find_stream_info(file->FmtCtx) >= 0)
+ return file;
+ av_close_input_file(file->FmtCtx);
+ }
+
+ free(file);
+ return NULL;
+}
+
+FilePtr openAVData(const char *name, char *buffer, size_t buffer_len)
+{
+ FilePtr file;
+
+ if(!done_init) {av_register_all();
+ av_log_set_level(AV_LOG_ERROR);
+ done_init = 1;}
+
+ if(!name)
+ name = "";
+
+ file = (FilePtr)calloc(1, sizeof(*file));
+ if(file && (file->FmtCtx=avformat_alloc_context()) != NULL)
+ {
+ static const int buflen = 4096;
+
+ file->membuf.buffer = buffer;
+ file->membuf.length = buffer_len;
+ file->membuf.pos = 0;
+
+ file->FmtCtx->pb = avio_alloc_context(av_malloc(buflen), buflen, 1, &file->membuf,
+ MemData_read, MemData_write, MemData_seek);
+ if(file->FmtCtx->pb && avformat_open_input(&file->FmtCtx, name, NULL, NULL) == 0)
+ {
+ if(av_find_stream_info(file->FmtCtx) >= 0)
+ return file;
+ }
+ av_close_input_file(file->FmtCtx);
+ }
+
+ free(file);
+ return NULL;
+}
+
+FilePtr openAVCustom(const char *name, void *user_data,
+ int (*read_packet)(void *user_data, uint8_t *buf, int buf_size),
+ int (*write_packet)(void *user_data, uint8_t *buf, int buf_size),
+ int64_t (*seek)(void *user_data, int64_t offset, int whence))
+{
+ FilePtr file;
+
+ if(!done_init) {av_register_all();
+ av_log_set_level(AV_LOG_ERROR);
+ done_init = 1;}
+
+ if(!name)
+ name = "";
+
+ file = (FilePtr)calloc(1, sizeof(*file));
+ if(file && (file->FmtCtx=avformat_alloc_context()) != NULL)
+ {
+ static const int buflen = 4096;
+
+ file->FmtCtx->pb = avio_alloc_context(av_malloc(buflen), buflen, 1, user_data,
+ read_packet, write_packet, seek);
+ if(file->FmtCtx->pb && avformat_open_input(&file->FmtCtx, name, NULL, NULL) == 0)
+ {
+ if(av_find_stream_info(file->FmtCtx) >= 0)
+ return file;
+ }
+ av_close_input_file(file->FmtCtx);
+ }
+
+ free(file);
+ return NULL;
+}
+
+
+void closeAVFile(FilePtr file)
+{
+ size_t i;
+
+ if(!file) return;
+
+ for(i = 0;i < file->StreamsSize;i++)
+ {
+ StreamPtr stream = file->Streams[i];
+
+ while(stream->Packets)
+ {
+ struct PacketList *self;
+
+ self = stream->Packets;
+ stream->Packets = self->next;
+
+ av_free_packet(&self->pkt);
+ av_free(self);
+ }
+
+ avcodec_close(stream->CodecCtx);
+ av_free(stream->DecodedData);
+ free(stream);
+ }
+ free(file->Streams);
+
+ av_close_input_file(file->FmtCtx);
+ free(file);
+}
+
+
+int getAVFileInfo(FilePtr file, int *numaudiostreams)
+{
+ unsigned int i;
+ int audiocount = 0;
+
+ if(!file) return 1;
+ for(i = 0;i < file->FmtCtx->nb_streams;i++)
+ {
+ if(file->FmtCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO)
+ audiocount++;
+ }
+ *numaudiostreams = audiocount;
+ return 0;
+}
+
+StreamPtr getAVAudioStream(FilePtr file, int streamnum)
+{
+ unsigned int i;
+ if(!file) return NULL;
+ for(i = 0;i < file->FmtCtx->nb_streams;i++)
+ {
+ if(file->FmtCtx->streams[i]->codec->codec_type != CODEC_TYPE_AUDIO)
+ continue;
+
+ if(streamnum == 0)
+ {
+ StreamPtr stream;
+ AVCodec *codec;
+ void *temp;
+ size_t j;
+
+ /* Found the requested stream. Check if a handle to this stream
+ * already exists and return it if it does */
+ for(j = 0;j < file->StreamsSize;j++)
+ {
+ if(file->Streams[j]->StreamIdx == (int)i)
+ return file->Streams[j];
+ }
+
+ /* Doesn't yet exist. Now allocate a new stream object and fill in
+ * its info */
+ stream = (StreamPtr)calloc(1, sizeof(*stream));
+ if(!stream) return NULL;
+
+ stream->parent = file;
+ stream->CodecCtx = file->FmtCtx->streams[i]->codec;
+ stream->StreamIdx = i;
+
+ /* Try to find the codec for the given codec ID, and open it */
+ codec = avcodec_find_decoder(stream->CodecCtx->codec_id);
+ if(!codec || avcodec_open(stream->CodecCtx, codec) < 0)
+ {
+ free(stream);
+ return NULL;
+ }
+
+ /* Allocate space for the decoded data to be stored in before it
+ * gets passed to the app */
+ stream->DecodedData = av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
+ if(!stream->DecodedData)
+ {
+ avcodec_close(stream->CodecCtx);
+ free(stream);
+ return NULL;
+ }
+
+ /* Append the new stream object to the stream list. The original
+ * pointer will remain valid if realloc fails, so we need to use
+ * another pointer to watch for errors and not leak memory */
+ temp = realloc(file->Streams, (file->StreamsSize+1) *
+ sizeof(*file->Streams));
+ if(!temp)
+ {
+ avcodec_close(stream->CodecCtx);
+ av_free(stream->DecodedData);
+ free(stream);
+ return NULL;
+ }
+ file->Streams = (StreamPtr*)temp;
+ file->Streams[file->StreamsSize++] = stream;
+ return stream;
+ }
+ streamnum--;
+ }
+ return NULL;
+}
+
+int getAVAudioInfo(StreamPtr stream, ALuint *rate, ALenum *channels, ALenum *type)
+{
+ if(!stream || stream->CodecCtx->codec_type != CODEC_TYPE_AUDIO)
+ return 1;
+
+ /* Get the sample type for OpenAL given the format detected by ffmpeg. */
+ if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_U8)
+ *type = AL_UNSIGNED_BYTE;
+ else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S16)
+ *type = AL_SHORT;
+ else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S32)
+ *type = AL_INT;
+ else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT)
+ *type = AL_FLOAT;
+ else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_DBL)
+ *type = AL_DOUBLE;
+ else
+ return 1;
+
+ /* Get the OpenAL channel configuration using the channel layout detected
+ * by ffmpeg. NOTE: some file types may not specify a channel layout. In
+ * that case, one must be guessed based on the channel count. */
+ if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_MONO)
+ *channels = AL_MONO;
+ else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_STEREO)
+ *channels = AL_STEREO;
+ else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_QUAD)
+ *channels = AL_QUAD;
+ else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1)
+ *channels = AL_5POINT1;
+ else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1)
+ *channels = AL_7POINT1;
+ else if(stream->CodecCtx->channel_layout == 0)
+ {
+ /* Unknown channel layout. Try to guess. */
+ if(stream->CodecCtx->channels == 1)
+ *channels = AL_MONO;
+ else if(stream->CodecCtx->channels == 2)
+ *channels = AL_STEREO;
+ else
+ return 1;
+ }
+ else
+ return 1;
+
+ *rate = stream->CodecCtx->sample_rate;
+
+ return 0;
+}
+
+
+/* Used by getAV*Data to search for more compressed data, and buffer it in the
+ * correct stream. It won't buffer data for streams that the app doesn't have a
+ * handle for. */
+static int getNextPacket(FilePtr file, int streamidx)
+{
+ struct PacketList *packet;
+
+ packet = (struct PacketList*)av_malloc(sizeof(*packet));
+ packet->next = NULL;
+
+next_packet:
+ while(av_read_frame(file->FmtCtx, &packet->pkt) >= 0)
+ {
+ StreamPtr *iter = file->Streams;
+ StreamPtr *iter_end = iter + file->StreamsSize;
+
+ /* Check each stream the user has a handle for, looking for the one
+ * this packet belongs to */
+ while(iter != iter_end)
+ {
+ if((*iter)->StreamIdx == packet->pkt.stream_index)
+ {
+ struct PacketList **last;
+
+ last = &(*iter)->Packets;
+ while(*last != NULL)
+ last = &(*last)->next;
+
+ *last = packet;
+ if((*iter)->StreamIdx == streamidx)
+ return 1;
+
+ packet = (struct PacketList*)av_malloc(sizeof(*packet));
+ packet->next = NULL;
+ goto next_packet;
+ }
+ iter++;
+ }
+ /* Free the packet and look for another */
+ av_free_packet(&packet->pkt);
+ }
+
+ av_free(packet);
+ return 0;
+}
+
+void *getAVAudioData(StreamPtr stream, size_t *length)
+{
+ int size;
+ int len;
+
+ if(length) *length = 0;
+
+ if(!stream || stream->CodecCtx->codec_type != CODEC_TYPE_AUDIO)
+ return NULL;
+
+ stream->DecodedDataSize = 0;
+
+next_packet:
+ if(!stream->Packets && !getNextPacket(stream->parent, stream->StreamIdx))
+ return NULL;
+
+ /* Decode some data, and check for errors */
+ size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+ while((len=avcodec_decode_audio3(stream->CodecCtx,
+ (int16_t*)stream->DecodedData, &size,
+ &stream->Packets->pkt)) == 0)
+ {
+ struct PacketList *self;
+
+ if(size > 0)
+ break;
+
+ /* Packet went unread and no data was given? Drop it and try the next,
+ * I guess... */
+ self = stream->Packets;
+ stream->Packets = self->next;
+
+ av_free_packet(&self->pkt);
+ av_free(self);
+
+ if(!stream->Packets)
+ goto next_packet;
+
+ size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+ }
+
+ if(len < 0)
+ return NULL;
+
+ if(len < stream->Packets->pkt.size)
+ {
+ /* Move the unread data to the front and clear the end bits */
+ int remaining = stream->Packets->pkt.size - len;
+ memmove(stream->Packets->pkt.data, &stream->Packets->pkt.data[len],
+ remaining);
+ memset(&stream->Packets->pkt.data[remaining], 0,
+ stream->Packets->pkt.size - remaining);
+ stream->Packets->pkt.size -= len;
+ }
+ else
+ {
+ struct PacketList *self;
+
+ self = stream->Packets;
+ stream->Packets = self->next;
+
+ av_free_packet(&self->pkt);
+ av_free(self);
+ }
+
+ if(size == 0)
+ goto next_packet;
+
+ /* Set the output buffer size */
+ stream->DecodedDataSize = size;
+ if(length) *length = stream->DecodedDataSize;
+
+ return stream->DecodedData;
+}
+
+size_t readAVAudioData(StreamPtr stream, void *data, size_t length)
+{
+ size_t dec = 0;
+
+ if(!stream || stream->CodecCtx->codec_type != CODEC_TYPE_AUDIO)
+ return 0;
+
+ while(dec < length)
+ {
+ /* If there's no decoded data, find some */
+ if(stream->DecodedDataSize == 0)
+ {
+ if(getAVAudioData(stream, NULL) == NULL)
+ break;
+ }
+
+ if(stream->DecodedDataSize > 0)
+ {
+ /* Get the amount of bytes remaining to be written, and clamp to
+ * the amount of decoded data we have */
+ size_t rem = length-dec;
+ if(rem > stream->DecodedDataSize)
+ rem = stream->DecodedDataSize;
+
+ /* Copy the data to the app's buffer and increment */
+ if(data != NULL)
+ {
+ memcpy(data, stream->DecodedData, rem);
+ data = (char*)data + rem;
+ }
+ dec += rem;
+
+ /* If there's any decoded data left, move it to the front of the
+ * buffer for next time */
+ if(rem < stream->DecodedDataSize)
+ memmove(stream->DecodedData, &stream->DecodedData[rem],
+ stream->DecodedDataSize - rem);
+ stream->DecodedDataSize -= rem;
+ }
+ }
+
+ /* Return the number of bytes we were able to get */
+ return dec;
+}
+
+void *decodeAVAudioStream(StreamPtr stream, size_t *length)
+{
+ char *outbuf = NULL;
+ size_t buflen = 0;
+ void *inbuf;
+ size_t got;
+
+ *length = 0;
+ if(!stream || stream->CodecCtx->codec_type != CODEC_TYPE_AUDIO)
+ return NULL;
+
+ while((inbuf=getAVAudioData(stream, &got)) != NULL && got > 0)
+ {
+ void *ptr;
+
+ ptr = realloc(outbuf, NextPowerOf2(buflen+got));
+ if(ptr == NULL)
+ break;
+ outbuf = ptr;
+
+ memcpy(&outbuf[buflen], inbuf, got);
+ buflen += got;
+ }
+ outbuf = realloc(outbuf, buflen);
+
+ *length = buflen;
+ return outbuf;
+}
diff --git a/examples/alffmpeg.h b/examples/alffmpeg.h
new file mode 100644
index 00000000..7fa88e65
--- /dev/null
+++ b/examples/alffmpeg.h
@@ -0,0 +1,66 @@
+#ifndef ALFFMPEG_H
+#define ALFFMPEG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+/* Opaque handles to files and streams. Apps don't need to concern themselves
+ * with the internals */
+typedef struct MyFile *FilePtr;
+typedef struct MyStream *StreamPtr;
+
+/* Opens a file with ffmpeg and sets up the streams' information */
+FilePtr openAVFile(const char *fname);
+
+/* Opens a named file image with ffmpeg and sets up the streams' information */
+FilePtr openAVData(const char *name, char *buffer, size_t buffer_len);
+
+/* Opens a named data stream with ffmpeg, using the specified data pointer and
+ * callbacks, and sets up the streams' information */
+FilePtr openAVCustom(const char *name, void *user_data,
+ int (*read_packet)(void *user_data, uint8_t *buf, int buf_size),
+ int (*write_packet)(void *user_data, uint8_t *buf, int buf_size),
+ int64_t (*seek)(void *user_data, int64_t offset, int whence));
+
+/* Closes/frees an opened file and any of its streams */
+void closeAVFile(FilePtr file);
+
+/* Reports certain information from the file, eg, the number of audio
+ * streams. Returns 0 on success. */
+int getAVFileInfo(FilePtr file, int *numaudiostreams);
+
+/* Retrieves a handle for the given audio stream number (generally 0, but some
+ * files can have multiple audio streams in one file). */
+StreamPtr getAVAudioStream(FilePtr file, int streamnum);
+
+/* Returns information about the given audio stream. Returns 0 on success. */
+int getAVAudioInfo(StreamPtr stream, ALuint *rate, ALenum *channels, ALenum *type);
+
+/* Returns a pointer to the next available packet of decoded audio. Any data
+ * from a previously-decoded packet is dropped. The size (in bytes) of the
+ * returned data buffer is stored in 'length', and the returned pointer is only
+ * valid until the next call to getAVAudioData or readAVAudioData. */
+void *getAVAudioData(StreamPtr stream, size_t *length);
+
+/* The "meat" function. Decodes audio and writes, at most, length bytes into
+ * the provided data buffer. Will only return less for end-of-stream or error
+ * conditions. Returns the number of bytes written. */
+size_t readAVAudioData(StreamPtr stream, void *data, size_t length);
+
+/* Decodes all remaining data from the stream and returns a buffer containing
+ * the audio data, with the size stored in 'length'. The returned pointer must
+ * be freed with a call to free(). Note that since this decodes the whole
+ * stream, using it on lengthy streams (eg, music) will use a lot of memory.
+ * Such streams are better handled using getAVAudioData or readAVAudioData to
+ * keep smaller chunks in memory at any given time. */
+void *decodeAVAudioStream(StreamPtr stream, size_t *length);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ALFFMPEG_H */
diff --git a/examples/alhelpers.c b/examples/alhelpers.c
new file mode 100644
index 00000000..ba23cabd
--- /dev/null
+++ b/examples/alhelpers.c
@@ -0,0 +1,226 @@
+/*
+ * OpenAL Helpers
+ *
+ * Copyright (c) 2011 by Chris Robinson <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* This file contains routines to help with some menial OpenAL-related tasks,
+ * such as opening a device and setting up a context, closing the device and
+ * destroying its context, converting between frame counts and byte lengths,
+ * finding an appropriate buffer format, and getting readable strings for
+ * channel configs and sample types. */
+
+#include <stdio.h>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "alhelpers.h"
+
+
+const char *ChannelsName(ALenum chans)
+{
+ switch(chans)
+ {
+ case AL_MONO: return "Mono";
+ case AL_STEREO: return "Stereo";
+ case AL_REAR: return "Rear";
+ case AL_QUAD: return "Quadraphonic";
+ case AL_5POINT1: return "5.1 Surround";
+ case AL_6POINT1: return "6.1 Surround";
+ case AL_7POINT1: return "7.1 Surround";
+ }
+ return "Unknown Channels";
+}
+
+const char *TypeName(ALenum type)
+{
+ switch(type)
+ {
+ case AL_BYTE: return "S8";
+ case AL_UNSIGNED_BYTE: return "U8";
+ case AL_SHORT: return "S16";
+ case AL_UNSIGNED_SHORT: return "U16";
+ case AL_INT: return "S32";
+ case AL_UNSIGNED_INT: return "U32";
+ case AL_FLOAT: return "Float32";
+ case AL_DOUBLE: return "Float64";
+ }
+ return "Unknown Type";
+}
+
+
+ALsizei FramesToBytes(ALsizei size, ALenum channels, ALenum type)
+{
+ switch(channels)
+ {
+ case AL_MONO: size *= 1; break;
+ case AL_STEREO: size *= 2; break;
+ case AL_REAR: size *= 2; break;
+ case AL_QUAD: size *= 4; break;
+ case AL_5POINT1: size *= 6; break;
+ case AL_6POINT1: size *= 7; break;
+ case AL_7POINT1: size *= 8; break;
+ }
+
+ switch(type)
+ {
+ case AL_BYTE: size *= sizeof(ALbyte); break;
+ case AL_UNSIGNED_BYTE: size *= sizeof(ALubyte); break;
+ case AL_SHORT: size *= sizeof(ALshort); break;
+ case AL_UNSIGNED_SHORT: size *= sizeof(ALushort); break;
+ case AL_INT: size *= sizeof(ALint); break;
+ case AL_UNSIGNED_INT: size *= sizeof(ALuint); break;
+ case AL_FLOAT: size *= sizeof(ALfloat); break;
+ case AL_DOUBLE: size *= sizeof(ALdouble); break;
+ }
+
+ return size;
+}
+
+ALsizei BytesToFrames(ALsizei size, ALenum channels, ALenum type)
+{
+ return size / FramesToBytes(1, channels, type);
+}
+
+
+ALenum GetFormat(ALenum channels, ALenum type)
+{
+ ALenum format = 0;
+
+ /* We use the AL_EXT_MCFORMATS extension to provide output of Quad, 5.1,
+ * and 7.1 channel configs, AL_EXT_FLOAT32 for 32-bit float samples, and
+ * AL_EXT_DOUBLE for 64-bit float samples. */
+ if(type == AL_UNSIGNED_BYTE)
+ {
+ if(channels == AL_MONO)
+ format = AL_FORMAT_MONO8;
+ else if(channels == AL_STEREO)
+ format = AL_FORMAT_STEREO8;
+ else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
+ {
+ if(channels == AL_QUAD)
+ format = alGetEnumValue("AL_FORMAT_QUAD8");
+ else if(channels == AL_5POINT1)
+ format = alGetEnumValue("AL_FORMAT_51CHN8");
+ else if(channels == AL_6POINT1)
+ format = alGetEnumValue("AL_FORMAT_61CHN8");
+ else if(channels == AL_7POINT1)
+ format = alGetEnumValue("AL_FORMAT_71CHN8");
+ }
+ }
+ else if(type == AL_SHORT)
+ {
+ if(channels == AL_MONO)
+ format = AL_FORMAT_MONO16;
+ else if(channels == AL_STEREO)
+ format = AL_FORMAT_STEREO16;
+ else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
+ {
+ if(channels == AL_QUAD)
+ format = alGetEnumValue("AL_FORMAT_QUAD16");
+ else if(channels == AL_5POINT1)
+ format = alGetEnumValue("AL_FORMAT_51CHN16");
+ else if(channels == AL_6POINT1)
+ format = alGetEnumValue("AL_FORMAT_61CHN16");
+ else if(channels == AL_7POINT1)
+ format = alGetEnumValue("AL_FORMAT_71CHN16");
+ }
+ }
+ else if(type == AL_FLOAT && alIsExtensionPresent("AL_EXT_FLOAT32"))
+ {
+ if(channels == AL_MONO)
+ format = alGetEnumValue("AL_FORMAT_MONO_FLOAT32");
+ else if(channels == AL_STEREO)
+ format = alGetEnumValue("AL_FORMAT_STEREO_FLOAT32");
+ else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
+ {
+ if(channels == AL_QUAD)
+ format = alGetEnumValue("AL_FORMAT_QUAD32");
+ else if(channels == AL_5POINT1)
+ format = alGetEnumValue("AL_FORMAT_51CHN32");
+ else if(channels == AL_6POINT1)
+ format = alGetEnumValue("AL_FORMAT_61CHN32");
+ else if(channels == AL_7POINT1)
+ format = alGetEnumValue("AL_FORMAT_71CHN32");
+ }
+ }
+ else if(type == AL_DOUBLE && alIsExtensionPresent("AL_EXT_DOUBLE"))
+ {
+ if(channels == AL_MONO)
+ format = alGetEnumValue("AL_FORMAT_MONO_DOUBLE");
+ else if(channels == AL_STEREO)
+ format = alGetEnumValue("AL_FORMAT_STEREO_DOUBLE");
+ }
+
+ /* NOTE: It seems OSX returns -1 from alGetEnumValue for unknown enums, as
+ * opposed to 0. Correct it. */
+ if(format == -1)
+ format = 0;
+
+ return format;
+}
+
+
+int InitAL(void)
+{
+ ALCdevice *device;
+ ALCcontext *ctx;
+
+ /* Open and initialize a device with default settings */
+ device = alcOpenDevice(NULL);
+ if(!device)
+ {
+ fprintf(stderr, "Could not open a device!\n");
+ return 1;
+ }
+
+ ctx = alcCreateContext(device, NULL);
+ if(ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE)
+ {
+ if(ctx != NULL)
+ alcDestroyContext(ctx);
+ alcCloseDevice(device);
+ fprintf(stderr, "Could not set a context!\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+void CloseAL(void)
+{
+ ALCdevice *device;
+ ALCcontext *ctx;
+
+ /* Close the device belonging to the current context, and destroy the
+ * context. */
+ ctx = alcGetCurrentContext();
+ if(ctx == NULL)
+ return;
+
+ device = alcGetContextsDevice(ctx);
+
+ alcMakeContextCurrent(NULL);
+ alcDestroyContext(ctx);
+ alcCloseDevice(device);
+}
diff --git a/examples/alhelpers.h b/examples/alhelpers.h
new file mode 100644
index 00000000..5559ea6f
--- /dev/null
+++ b/examples/alhelpers.h
@@ -0,0 +1,95 @@
+#ifndef ALHELPERS_H
+#define ALHELPERS_H
+
+#ifndef _WIN32
+#include <unistd.h>
+#define Sleep(x) usleep((x)*1000)
+#else
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifndef AL_SOFT_buffer_samples
+#define AL_SOFT_buffer_samples 1
+/* Sample types */
+#define AL_BYTE 0x1400
+#define AL_UNSIGNED_BYTE 0x1401
+#define AL_SHORT 0x1402
+#define AL_UNSIGNED_SHORT 0x1403
+#define AL_INT 0x1404
+#define AL_UNSIGNED_INT 0x1405
+#define AL_FLOAT 0x1406
+#define AL_DOUBLE 0x1407
+#define AL_BYTE3 0x1408
+#define AL_UNSIGNED_BYTE3 0x1409
+
+/* Channel configurations */
+#define AL_MONO 0x1500
+#define AL_STEREO 0x1501
+#define AL_REAR 0x1502
+#define AL_QUAD 0x1503
+#define AL_5POINT1 0x1504
+#define AL_6POINT1 0x1505
+#define AL_7POINT1 0x1506
+
+/* Storage formats */
+#define AL_MONO8 0x1100
+#define AL_MONO16 0x1101
+#define AL_MONO32F 0x10010
+#define AL_STEREO8 0x1102
+#define AL_STEREO16 0x1103
+#define AL_STEREO32F 0x10011
+#define AL_QUAD8 0x1204
+#define AL_QUAD16 0x1205
+#define AL_QUAD32F 0x1206
+#define AL_REAR8 0x1207
+#define AL_REAR16 0x1208
+#define AL_REAR32F 0x1209
+#define AL_5POINT1_8 0x120A
+#define AL_5POINT1_16 0x120B
+#define AL_5POINT1_32F 0x120C
+#define AL_6POINT1_8 0x120D
+#define AL_6POINT1_16 0x120E
+#define AL_6POINT1_32F 0x120F
+#define AL_7POINT1_8 0x1210
+#define AL_7POINT1_16 0x1211
+#define AL_7POINT1_32F 0x1212
+
+/* Buffer attributes */
+#define AL_INTERNAL_FORMAT 0x2008
+#define AL_BYTE_LENGTH 0x2009
+#define AL_SAMPLE_LENGTH 0x200A
+#define AL_SEC_LENGTH 0x200B
+
+typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*);
+typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*);
+typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*);
+typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum);
+#endif
+
+
+/* Some helper functions to get the name from the channel and type enums. */
+const char *ChannelsName(ALenum chans);
+const char *TypeName(ALenum type);
+
+/* Helpers to convert frame counts and byte lengths. */
+ALsizei FramesToBytes(ALsizei size, ALenum channels, ALenum type);
+ALsizei BytesToFrames(ALsizei size, ALenum channels, ALenum type);
+
+/* Retrieves a compatible buffer format given the channel configuration and
+ * sample type. Returns 0 if no supported format can be found. */
+ALenum GetFormat(ALenum channels, ALenum type);
+
+/* Easy device init/deinit functions. InitAL returns 0 on success. */
+int InitAL(void);
+void CloseAL(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ALHELPERS_H */
diff --git a/examples/alstream.c b/examples/alstream.c
new file mode 100644
index 00000000..f25ca0be
--- /dev/null
+++ b/examples/alstream.c
@@ -0,0 +1,331 @@
+/*
+ * OpenAL Audio Stream Example
+ *
+ * Copyright (c) 2011 by Chris Robinson <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* This file contains a relatively simple streaming audio player. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <assert.h>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "alhelpers.h"
+#include "alffmpeg.h"
+
+
+/* Define the number of buffers and buffer size (in samples) to use. 4 buffers
+ * with 8192 samples each gives a nice per-chunk size, and lets the queue last
+ * for almost 3/4ths of a second for a 44.1khz stream. */
+#define NUM_BUFFERS 4
+#define BUFFER_SIZE 8192
+
+typedef struct StreamPlayer {
+ /* These are the buffers and source to play out through OpenAL with */
+ ALuint buffers[NUM_BUFFERS];
+ ALuint source;
+
+ /* Handles for the audio stream */
+ FilePtr file;
+ StreamPtr stream;
+
+ /* A temporary data buffer for readAVAudioData to write to and pass to
+ * OpenAL with */
+ ALbyte *data;
+ ALsizei datasize;
+
+ /* The format of the output stream */
+ ALenum format;
+ ALenum channels;
+ ALenum type;
+ ALuint rate;
+} StreamPlayer;
+
+static StreamPlayer *NewPlayer(void);
+static void DeletePlayer(StreamPlayer *player);
+static int OpenPlayerFile(StreamPlayer *player, const char *filename);
+static void ClosePlayerFile(StreamPlayer *player);
+static int StartPlayer(StreamPlayer *player);
+static int UpdatePlayer(StreamPlayer *player);
+
+/* Creates a new player object, and allocates the needed OpenAL source and
+ * buffer objects. Error checking is simplified for the purposes of this
+ * example, and will cause an abort if needed. */
+static StreamPlayer *NewPlayer(void)
+{
+ StreamPlayer *player;
+
+ player = malloc(sizeof(*player));
+ assert(player != NULL);
+
+ memset(player, 0, sizeof(*player));
+
+ /* Generate the buffers and source */
+ alGenBuffers(NUM_BUFFERS, player->buffers);
+ assert(alGetError() == AL_NO_ERROR && "Could not create buffers");
+
+ alGenSources(1, &player->source);
+ assert(alGetError() == AL_NO_ERROR && "Could not create source");
+
+ /* Set parameters so mono sources play out the front-center speaker and
+ * won't distance attenuate. */
+ alSource3i(player->source, AL_POSITION, 0, 0, -1);
+ alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE);
+ alSourcei(player->source, AL_ROLLOFF_FACTOR, 0);
+ assert(alGetError() == AL_NO_ERROR && "Could not set source parameters");
+
+ return player;
+}
+
+/* Destroys a player object, deleting the source and buffers. No error handling
+ * since these calls shouldn't fail with a properly-made player object. */
+static void DeletePlayer(StreamPlayer *player)
+{
+ ClosePlayerFile(player);
+
+ alDeleteSources(1, &player->source);
+ alDeleteBuffers(NUM_BUFFERS, player->buffers);
+ if(alGetError() != AL_NO_ERROR)
+ fprintf(stderr, "Failed to delete object IDs\n");
+
+ memset(player, 0, sizeof(*player));
+ free(player);
+}
+
+
+/* Opens the first audio stream of the named file. If a file is already open,
+ * it will be closed first. */
+static int OpenPlayerFile(StreamPlayer *player, const char *filename)
+{
+ ClosePlayerFile(player);
+
+ /* Open the file and get the first stream from it */
+ player->file = openAVFile(filename);
+ player->stream = getAVAudioStream(player->file, 0);
+ if(!player->stream)
+ {
+ fprintf(stderr, "Could not open audio in %s\n", filename);
+ goto error;
+ }
+
+ /* Get the stream format, and figure out the OpenAL format */
+ if(getAVAudioInfo(player->stream, &player->rate, &player->channels,
+ &player->type) != 0)
+ {
+ fprintf(stderr, "Error getting audio info for %s\n", filename);
+ goto error;
+ }
+
+ player->format = GetFormat(player->channels, player->type);
+ if(player->format == 0)
+ {
+ fprintf(stderr, "Unsupported format (%s, %s) for %s\n",
+ ChannelsName(player->channels), TypeName(player->type),
+ filename);
+ goto error;
+ }
+
+ /* Allocate enough space for the temp buffer, given the format */
+ player->datasize = FramesToBytes(BUFFER_SIZE, player->channels,
+ player->type);
+ player->data = malloc(player->datasize);
+ if(player->data == NULL)
+ {
+ fprintf(stderr, "Error allocating %d bytes\n", player->datasize);
+ goto error;
+ }
+
+ return 1;
+
+error:
+ closeAVFile(player->file);
+ player->file = NULL;
+ player->stream = NULL;
+ player->datasize = 0;
+
+ return 0;
+}
+
+/* Closes the audio file stream */
+static void ClosePlayerFile(StreamPlayer *player)
+{
+ closeAVFile(player->file);
+ player->file = NULL;
+ player->stream = NULL;
+
+ free(player->data);
+ player->data = NULL;
+ player->datasize = 0;
+}
+
+
+/* Prebuffers some audio from the file, and starts playing the source */
+static int StartPlayer(StreamPlayer *player)
+{
+ size_t i, got;
+
+ /* Rewind the source position and clear the buffer queue */
+ alSourceRewind(player->source);
+ alSourcei(player->source, AL_BUFFER, 0);
+
+ /* Fill the buffer queue */
+ for(i = 0;i < NUM_BUFFERS;i++)
+ {
+ /* Get some data to give it to the buffer */
+ got = readAVAudioData(player->stream, player->data, player->datasize);
+ if(got == 0) break;
+
+ alBufferData(player->buffers[i], player->format, player->data, got,
+ player->rate);
+ }
+ if(alGetError() != AL_NO_ERROR)
+ {
+ fprintf(stderr, "Error buffering for playback\n");
+ return 0;
+ }
+
+ /* Now queue and start playback! */
+ alSourceQueueBuffers(player->source, i, player->buffers);
+ alSourcePlay(player->source);
+ if(alGetError() != AL_NO_ERROR)
+ {
+ fprintf(stderr, "Error starting playback\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int UpdatePlayer(StreamPlayer *player)
+{
+ ALint processed, state;
+
+ /* Get relevant source info */
+ alGetSourcei(player->source, AL_SOURCE_STATE, &state);
+ alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed);
+ if(alGetError() != AL_NO_ERROR)
+ {
+ fprintf(stderr, "Error checking source state\n");
+ return 0;
+ }
+
+ /* Unqueue and handle each processed buffer */
+ while(processed > 0)
+ {
+ ALuint bufid;
+ size_t got;
+
+ alSourceUnqueueBuffers(player->source, 1, &bufid);
+ processed--;
+
+ /* Read the next chunk of data, refill the buffer, and queue it
+ * back on the source */
+ got = readAVAudioData(player->stream, player->data, player->datasize);
+ if(got > 0)
+ {
+ alBufferData(bufid, player->format, player->data, got,
+ player->rate);
+ alSourceQueueBuffers(player->source, 1, &bufid);
+ }
+ if(alGetError() != AL_NO_ERROR)
+ {
+ fprintf(stderr, "Error buffering data\n");
+ return 0;
+ }
+ }
+
+ /* Make sure the source hasn't underrun */
+ if(state != AL_PLAYING && state != AL_PAUSED)
+ {
+ ALint queued;
+
+ /* If no buffers are queued, playback is finished */
+ alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued);
+ if(queued == 0)
+ return 0;
+
+ alSourcePlay(player->source);
+ if(alGetError() != AL_NO_ERROR)
+ {
+ fprintf(stderr, "Error restarting playback\n");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+int main(int argc, char **argv)
+{
+ StreamPlayer *player;
+ int i;
+
+ /* Print out usage if no file was specified */
+ if(argc < 2)
+ {
+ fprintf(stderr, "Usage: %s <filenames...>\n", argv[0]);
+ return 1;
+ }
+
+ if(InitAL() != 0)
+ return 1;
+
+ player = NewPlayer();
+
+ /* Play each file listed on the command line */
+ for(i = 1;i < argc;i++)
+ {
+ if(!OpenPlayerFile(player, argv[i]))
+ continue;
+
+ fprintf(stderr, "Playing %s (%s, %s, %dhz)\n", argv[i],
+ TypeName(player->type), ChannelsName(player->channels),
+ player->rate);
+
+ if(!StartPlayer(player))
+ {
+ ClosePlayerFile(player);
+ continue;
+ }
+
+ while(UpdatePlayer(player))
+ Sleep(10);
+
+ /* All done with this file. Close it and go to the next */
+ ClosePlayerFile(player);
+ }
+ fprintf(stderr, "Done.\n");
+
+ /* All files done. Delete the player, and close OpenAL */
+ DeletePlayer(player);
+ player = NULL;
+
+ CloseAL();
+
+ return 0;
+}