Add an example program that streams audio using ffmpeg

This commit is contained in:
Chris Robinson 2012-01-01 12:36:41 -08:00
parent 06caac2619
commit 466cac328f
6 changed files with 1348 additions and 0 deletions

View File

@ -17,6 +17,7 @@ INCLUDE(CheckSymbolExists)
INCLUDE(CheckCCompilerFlag) INCLUDE(CheckCCompilerFlag)
INCLUDE(CheckCSourceCompiles) INCLUDE(CheckCSourceCompiles)
INCLUDE(CheckTypeSize) INCLUDE(CheckTypeSize)
INCLUDE(FindPkgConfig)
PROJECT(OpenAL C) PROJECT(OpenAL C)
@ -56,6 +57,8 @@ OPTION(WERROR "Treat compile warnings as errors" OFF)
OPTION(UTILS "Build and install utility programs" ON) 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) OPTION(ALSOFT_CONFIG "Install alsoft.conf configuration file" OFF)
@ -730,3 +733,20 @@ IF(UTILS)
MESSAGE(STATUS "Building utility programs") MESSAGE(STATUS "Building utility programs")
MESSAGE(STATUS "") MESSAGE(STATUS "")
ENDIF() 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()

610
examples/alffmpeg.c Normal file
View File

@ -0,0 +1,610 @@
/*
* FFmpeg Decoder Helpers
*
* Copyright (c) 2011 by Chris Robinson <chris.kcat@gmail.com>
*
* 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;
}

66
examples/alffmpeg.h Normal file
View File

@ -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 */

226
examples/alhelpers.c Normal file
View File

@ -0,0 +1,226 @@
/*
* OpenAL Helpers
*
* Copyright (c) 2011 by Chris Robinson <chris.kcat@gmail.com>
*
* 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);
}

95
examples/alhelpers.h Normal file
View File

@ -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 */

331
examples/alstream.c Normal file
View File

@ -0,0 +1,331 @@
/*
* OpenAL Audio Stream Example
*
* Copyright (c) 2011 by Chris Robinson <chris.kcat@gmail.com>
*
* 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;
}