From 466cac328f15f3538c895208bfe7ea14f3923ffc Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 1 Jan 2012 12:36:41 -0800 Subject: [PATCH] Add an example program that streams audio using ffmpeg --- CMakeLists.txt | 20 ++ examples/alffmpeg.c | 610 +++++++++++++++++++++++++++++++++++++++++++++++++++ examples/alffmpeg.h | 66 ++++++ examples/alhelpers.c | 226 +++++++++++++++++++ examples/alhelpers.h | 95 ++++++++ examples/alstream.c | 331 ++++++++++++++++++++++++++++ 6 files changed, 1348 insertions(+) create mode 100644 examples/alffmpeg.c create mode 100644 examples/alffmpeg.h create mode 100644 examples/alhelpers.c create mode 100644 examples/alhelpers.h create mode 100644 examples/alstream.c 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 + * + * 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 +#include +#include +#include +#include + +#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 +#include + +/* 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 + * + * 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 + +#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 +#define Sleep(x) usleep((x)*1000) +#else +#define WIN32_LEAN_AND_MEAN +#include +#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 + * + * 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 +#include +#include +#include +#include + +#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 \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; +} -- 2.11.4.GIT