Initial public commit

actorid
Nicolay Korslund 15 years ago
commit 9712fa03b7

3
.gitignore vendored

@ -0,0 +1,3 @@
upload_docs.sh
docs
*~

1510
Doxyfile

File diff suppressed because it is too large Load Diff

@ -0,0 +1,26 @@
Game-oriented object interfaces (GOOI) is licensed under the
'zlib/libpng' license:
----
Copyright (c) 2009 Nicolay Korslund
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

@ -0,0 +1,118 @@
Welcome to GOOI v0.1
--------------------
Written by: Nicolay Korslund (korslund@gmail.com)
License: zlib/png (see LICENSE.txt)
WWW: http://asm-soft.com/gooi/
Documentation: http://asm-soft.com/gooi/docs
GOOI stands for Game-Oriented Object Interfaces. It is meant to become
a small set of generic interfaces for various game middleware
libraries, such as sound, input, graphics, and so on. It consists of
several independent modules, one for each of these areas. These may be
used together to build an entire game engine, or they can be used
individually as separate libraries.
However, GOOI does NOT actually implement a game engine, or any new
fundamental functionality. More on that below.
Currently there is only the Sound module, but more will come in the
future (including input, 2D/3D graphics, GUI, physics, file
system/archive access, and more.)
Main idea
---------
The idea behind to provide a uniform, consistent interface to other
game libraries. The library does not provide ANY functionality on its
own. Instead it connects to a backend implementation of your choice.
The Sound module, for example, currently has backends for OpenAL
(output only), FFmpeg (input only) and for Audiere. Hopefully we'll
soon add IrrKlang, FMod, DirectSound and Miles to that. It can combine
libraries to get more complete functionality (like using OpenAL for
output and FFmpeg to decode sound files), and it's also easy to write
your own backend if you're using a different (or home-brewed) sound
system.
Regardless of what backend you use, the front-end interface (found in
sound/sound.h) is identical, and as a library user you shouldn't
notice much difference at all if you swap one backend for another at a
later point.
The goal in the long run is to support a wide variety of game-related
libraries, and as many backend libraries (free and commercial) as
possible, so that you the user will have to write as little code as
possible.
What is it good for
-------------------
The main point of GOOI, as we said above, is that it connects to any
library of your choice "behind the scenes" but provides the same,
super-simple interface front-end for all of them. There can benefit
you in many ways:
- If you want to use a new library that GOOI support. You don't
have to scour the net for tutorials and usage examples, since much
of the common usage code is already included in the implementation
classes.
- If you don't want to pollute your code with library-specific code.
The GOOI interfaces can help you keep your code clean, and its user
interface is often simpler than the exteral library one.
- If you are creating a library that depends on a specific feature
(such as sound), but you don't want to lock your users into any
specific sound library. GOOI works as an abstraction that lets your
users select their own implementation. My own Monster scripting
language ( http://monsterscript.net ) will use this tactic, to
provide native-but-generic sound, input and GUI support, among other
features.
- If you want to support multiple backends, or make it possible to
easily switch backends later. You can select backends at compile
time or even at runtime. Maybe you decide to switch to to a
commercial library at a late stage in development, or you discover
that your favorite backend doesn't work on all the platforms you
want to reach.
The GOOI implementations are extremely light-weight - often just one
or two cpp/h pairs. You plug them directly into your program, there's
no separate build step required.
Since the library aims to be very modularly put together, you can
also, in many cases, just copy-and-paste the parts you need and ignore
the rest. Or modify stuff without fearing that the whole 'system' will
come crashing down, because there is no big 'system' to speak of.
Past and future
---------------
GOOI started out as a spin-off from OpenMW, another project of mine
( http://openmw.sourceforge.net ). OpenMW is an attempt to recreate
the engine behind the commercial game Morrowind, using only open
source software.
The projects are still tightly interlinked, and the will continue to
be until OpenMW is finished. That means that all near-future work on
GOOI for my part will be more or less guided by what OpenMW needs. But
I'll gladly accept external contributions that are not OpenMW-related.
Conclusion
----------
As you might have guessed, GOOI is more a concept in development than
a finished library right now.
All feedback, ideas, concepts, questions and code are very
welcome. Send them to: korslund@gmail.com
I will put up a forum later as well if there's enough interest.

1
sound/.gitignore vendored

@ -0,0 +1 @@

@ -0,0 +1,94 @@
#include "audiere_imp.h"
// Exception handling
class Audiere_Exception : public std::exception
{
std::string msg;
public:
Audiere_Exception(const std::string &m) : msg(m) {}
~Audiere_Exception() throw() {}
virtual const char* what() const throw() { return msg.c_str(); }
};
static void fail(const std::string &msg)
{
throw Audiere_Exception("Audiere exception: " + msg);
}
using namespace audiere;
using namespace GOOI::Sound;
AudiereManager::AudiereManager()
{
needsUpdate = false;
has3D = false;
canRepeatStream = true;
canLoadFile = true;
canLoadSource = false;
device = OpenDevice("");
if(device == NULL)
fail("Failed to open device");
}
// --- Manager ---
Sound *AudiereManager::load(const std::string &file, bool stream)
{ return new AudiereSound(file, device, stream); }
// --- Sound ---
AudiereSound::AudiereSound(const std::string &file,
AudioDevicePtr _device,
bool _stream)
: device(_device), stream(_stream)
{
sample = OpenSampleSource(file.c_str());
if(!sample)
fail("Couldn't load file " + file);
buf = CreateSampleBuffer(sample);
}
Instance *AudiereSound::getInstance(bool is3d, bool repeat)
{
// Ignore is3d. Audiere doesn't implement 3d sound. We could make a
// hack software 3D implementation later, but it's not that
// important.
SampleSourcePtr sample = buf->openStream();
if(!sample)
fail("Failed to open sample stream");
OutputStreamPtr sound = OpenSound(device, sample, stream);
if(repeat)
sound->setRepeat(true);
return new AudiereInstance(sound);
}
// --- Instance ---
AudiereInstance::AudiereInstance(OutputStreamPtr _sound)
: sound(_sound) {}
void AudiereInstance::play()
{ sound->play(); }
void AudiereInstance::stop()
{ sound->stop(); }
void AudiereInstance::pause()
{ stop(); }
bool AudiereInstance::isPlaying()
{ return sound->isPlaying(); }
void AudiereInstance::setVolume(float vol)
{ sound->setVolume(vol); }

@ -0,0 +1,73 @@
#ifndef GOOI_SOUND_AUDIERE_H
#define GOOI_SOUND_AUDIERE_H
#include "../sound.h"
#include <assert.h>
#include <audiere.h>
namespace GOOI {
namespace Sound {
/// Implementation of Sound::Manager for Audiere
class AudiereManager : public Manager
{
audiere::AudioDevicePtr device;
public:
AudiereManager();
virtual Sound *load(const std::string &file, bool stream=false);
/// disabled
virtual Sound *load(InputSource *input, bool stream=false)
{ assert(0); }
/// disabled
virtual void update() { assert(0); }
/// disabled
virtual void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz)
{ assert(0); };
};
/// Audiere Sound implementation
class AudiereSound : public Sound
{
audiere::AudioDevicePtr device;
audiere::SampleSourcePtr sample;
audiere::SampleBufferPtr buf;
bool stream;
public:
virtual Instance *getInstance(bool is3d, bool repeat);
virtual void drop()
{ delete this; }
AudiereSound(const std::string &file, audiere::AudioDevicePtr device,
bool stream);
};
/// Audiere Instance implementation
class AudiereInstance : public Instance
{
audiere::OutputStreamPtr sound;
public:
virtual void play();
virtual void stop();
virtual void pause();
virtual bool isPlaying();
virtual void setVolume(float);
/// disabled
virtual void setPos(float x, float y, float z)
{ assert(0); }
virtual void drop()
{ delete this; }
AudiereInstance(audiere::OutputStreamPtr);
};
}} // Namespace
#endif

@ -0,0 +1,222 @@
#include "input_ffmpeg.h"
#include <assert.h>
using namespace GOOI::Sound;
// Static output buffer. Not thread safe, but supports multiple
// streams operated from the same thread.
static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE];
bool FFM_InputManager::init = false;
FFM_Exception::FFM_Exception(const std::string &m)
: msg(m) {}
const char* FFM_Exception::what() const throw()
{ return msg.c_str(); }
FFM_Exception::~FFM_Exception() throw() {}
static void fail(const std::string &msg)
{
throw FFM_Exception("FFMpeg exception: " + msg);
}
// --- Manager ---
FFM_InputManager::FFM_InputManager()
{
if(!init)
{
av_register_all();
av_log_set_level(AV_LOG_ERROR);
init = true;
}
}
InputSource *FFM_InputManager::load(const std::string &file)
{ return new FFM_InputSource(file); }
// --- Source ---
FFM_InputSource::FFM_InputSource(const std::string &file)
{
// FFmpeg doesn't handle several instances from one source. So we
// just store the filename.
name = file;
}
InputStream *FFM_InputSource::getStream()
{ return new FFM_InputStream(name); }
void FFM_InputSource::drop()
{ delete this; }
// --- Stream ---
FFM_InputStream::FFM_InputStream(const std::string &file)
{
std::string msg;
AVCodec *codec;
empty = false;
if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0)
fail("Error loading audio file " + file);
if(av_find_stream_info(FmtCtx) < 0)
{
msg = "Error in file stream " + file;
goto err;
}
// Pick the first audio stream, if any
for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++)
{
// Pick the first audio stream
if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO)
break;
}
if(StreamNum == FmtCtx->nb_streams)
fail("File " + file + " didn't contain any audio streams");
// Open the decoder
CodecCtx = FmtCtx->streams[StreamNum]->codec;
codec = avcodec_find_decoder(CodecCtx->codec_id);
if(!codec || avcodec_open(CodecCtx, codec) < 0)
{
msg = "Error loading " + file + ": ";
if(codec)
msg += "coded error";
else
msg += "no codec found";
goto err;
}
// No errors, we're done
return;
// Handle errors
err:
av_close_input_file(FmtCtx);
fail(msg);
}
FFM_InputStream::~FFM_InputStream()
{
avcodec_close(CodecCtx);
av_close_input_file(FmtCtx);
}
void FFM_InputStream::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
{
if(rate) *rate = CodecCtx->sample_rate;
if(channels) *channels = CodecCtx->channels;
if(bits) *bits = 16;
}
uint32_t FFM_InputStream::getData(void *data, uint32_t length)
{
if(empty) return 0;
uint32_t left = length;
uint8_t *outPtr = (uint8_t*)data;
// First, copy over any stored data we might be sitting on
{
int s = storage.size();
int copy = s;
if(s)
{
// Make sure there's room
if(copy > left)
copy = left;
// Copy
memcpy(outPtr, &storage[0], copy);
outPtr += copy;
left -= copy;
// Is there anything left in the storage?
s -= copy;
if(s)
{
assert(left == 0);
// Move it to the start and resize
memmove(&storage[0], &storage[copy], s);
storage.resize(s);
}
}
}
// Next, get more input data from stream, and decode it
while(left)
{
AVPacket packet;
// Get the next packet, if any
if(av_read_frame(FmtCtx, &packet) < 0)
break;
// We only allow one stream per file at the moment
assert(StreamNum == packet.stream_index);
// Decode the packet
int len = AVCODEC_MAX_AUDIO_FRAME_SIZE;
int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf,
&len, packet.data, packet.size);
assert(tmp < 0 || tmp == packet.size);
// We don't need the input packet any longer
av_free_packet(&packet);
if(tmp < 0)
fail("Error decoding audio stream");
// Copy whatever data we got, and advance the pointer
if(len > 0)
{
// copy = how many bytes do we copy now
int copy = len;
if(copy > left)
copy = left;
// len = how many bytes are left uncopied
len -= copy;
// copy data
memcpy(outPtr, outBuf, copy);
// left = how much space is left in the caller output
// buffer
left -= copy;
outPtr += copy;
assert(left >= 0);
if(len > 0)
{
// There were uncopied bytes. Store them for later.
assert(left == 0);
storage.resize(len);
memcpy(&storage[0], outBuf, len);
}
}
}
// End of loop. Return the number of bytes copied.
assert(left <= length);
// If we're returning less than asked for, then we're done
if(left > 0)
empty = true;
return length - left;
}
void FFM_InputStream::drop()
{ delete this; }

@ -0,0 +1,71 @@
#ifndef GOOI_SOUND_FFMPEG_H
#define GOOI_SOUND_FFMPEG_H
#include "../input.h"
#include <exception>
#include <vector>
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
namespace GOOI {
namespace Sound {
/// FFmpeg exception
class FFM_Exception : public std::exception
{
std::string msg;
public:
FFM_Exception(const std::string &m);
~FFM_Exception() throw();
virtual const char* what() const throw();
};
/// FFMpeg implementation of InputManager
class FFM_InputManager : public InputManager
{
static bool init;
public:
FFM_InputManager();
virtual InputSource *load(const std::string &file);
};
/// FFMpeg implementation of InputSource
class FFM_InputSource : public InputSource
{
std::string name;
public:
FFM_InputSource(const std::string &file);
virtual InputStream *getStream();
virtual void drop();
};
/// FFMpeg implementation of InputStream
class FFM_InputStream : public InputStream
{
AVFormatContext *FmtCtx;
AVCodecContext *CodecCtx;
int StreamNum;
bool empty;
std::vector<uint8_t> storage;
public:
FFM_InputStream(const std::string &file);
~FFM_InputStream();
virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
virtual uint32_t getData(void *data, uint32_t length);
virtual void drop();
};
}} // namespaces
#endif

@ -0,0 +1,28 @@
#ifndef GOOI_FFMPEG_OPENAL_H
#define GOOI_FFMPEG_OPENAL_H
#include "sound_pair.h"
#include "input_ffmpeg.h"
#include "output_openal.h"
namespace GOOI {
namespace Sound {
/// A PairManager filter that adds FFmpeg decoding to OpenAL
class OpenAL_FFM_Manager : public PairManager
{
public:
OpenAL_FFM_Manager()
{
set(new FFM_InputManager,
new OpenAL_Manager);
}
~OpenAL_FFM_Manager()
{
delete snd;
delete inp;
}
};
}}
#endif

@ -0,0 +1,349 @@
#include "output_openal.h"
#include <assert.h>
#include <vector>
using namespace GOOI::Sound;
// ---- Helper functions and classes ----
class OpenAL_Exception : public std::exception
{
std::string msg;
public:
OpenAL_Exception(const std::string &m) : msg(m) {}
~OpenAL_Exception() throw() {}
virtual const char* what() const throw() { return msg.c_str(); }
};
static void fail(const std::string &msg)
{
throw OpenAL_Exception("OpenAL exception: " + msg);
}
static void checkALError(const std::string &msg)
{
ALenum err = alGetError();
if(err != AL_NO_ERROR)
fail("\"" + std::string(alGetString(err)) + "\" while " + msg);
}
static void getALFormat(InputStream *inp, int &fmt, int &rate)
{
int ch, bits;
inp->getInfo(&rate, &ch, &bits);
fmt = 0;
if(bits == 8)
{
if(ch == 1) fmt = AL_FORMAT_MONO8;
if(ch == 2) fmt = AL_FORMAT_STEREO8;
if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8");
if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN8");
}
}
if(bits == 16)
{
if(ch == 1) fmt = AL_FORMAT_MONO16;
if(ch == 2) fmt = AL_FORMAT_STEREO16;
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16");
if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16");
if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN16");
}
}
if(fmt == 0)
fail("Unsupported input format");
}
// ---- Manager ----
OpenAL_Manager::OpenAL_Manager()
: Context(NULL), Device(NULL)
{
needsUpdate = true;
has3D = true;
canRepeatStream = false;
canLoadFile = false;
canLoadSource = true;
// Set up sound system
Device = alcOpenDevice(NULL);
Context = alcCreateContext(Device, NULL);
if(!Device || !Context)
fail("Failed to initialize context or device");
alcMakeContextCurrent(Context);
}
OpenAL_Manager::~OpenAL_Manager()
{
// Deinitialize sound system
alcMakeContextCurrent(NULL);
if(Context) alcDestroyContext(Context);
if(Device) alcCloseDevice(Device);
}
Sound *OpenAL_Manager::load(const std::string &file, bool stream)
{ assert(0 && "OpenAL cannot decode files"); }
Sound *OpenAL_Manager::load(InputSource *source, bool stream)
{ return new OpenAL_Sound(source, this, stream); }
void OpenAL_Manager::update()
{
// Loop through all the streaming sounds and update them
LST::iterator it, next;
for(it = streaming.begin();
it != streaming.end();
it++)
{
(*it)->update();
}
}
void OpenAL_Manager::setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz)
{
ALfloat orient[6];
orient[0] = fx;
orient[1] = fy;
orient[2] = fz;
orient[3] = ux;
orient[4] = uy;
orient[5] = uz;
alListener3f(AL_POSITION, x, y, z);
alListenerfv(AL_ORIENTATION, orient);
checkALError("setting listener position");
}
OpenAL_Manager::LST::iterator OpenAL_Manager::add_stream(OpenAL_Stream_Instance* inst)
{
streaming.push_front(inst);
return streaming.begin();
}
void OpenAL_Manager::remove_stream(LST::iterator it)
{
streaming.erase(it);
}
// ---- Sound ----
OpenAL_Sound::~OpenAL_Sound()
{
// Kill the input source
if(source) source->drop();
// And any allocated buffers
if(bufferID)
alDeleteBuffers(1, &bufferID);
}
Instance *OpenAL_Sound::getInstance(bool is3d, bool repeat)
{
assert((!repeat || !stream) && "OpenAL implementation does not support looping streams");
if(stream)
return new OpenAL_Stream_Instance(source->getStream(), owner);
// Load the buffer if it hasn't been done already
if(bufferID == 0)
{
// Get an input stream and load the file from it
InputStream *inp = source->getStream();
std::vector<unsigned char> buffer;
// Add 32 kb at each increment
const int ADD = 32*1024;
// Fill the buffer. We increase the buffer until it's large
// enough to fit all the data.
while(true)
{
// Increase the buffer
int oldlen = buffer.size();
buffer.resize(oldlen+ADD);
// Read the data
size_t len = inp->getData(&buffer[oldlen], ADD);
// If we read less than requested, we're done.
if(len < ADD)
{
// Downsize the buffer to the right size
buffer.resize(oldlen+len);
break;
}
}
// Get the format
int fmt, rate;
getALFormat(inp, fmt, rate);
// We don't need the file anymore
inp->drop();
source->drop();
source = NULL;
// Move the data into OpenAL
alGenBuffers(1, &bufferID);
alBufferData(bufferID, fmt, &buffer[0], buffer.size(), rate);
checkALError("loading sound buffer");
} // End of buffer loading
// At this point, the file data has been loaded into the buffer
// in 'bufferID', and we should be ready to go.
assert(bufferID != 0);
return new OpenAL_Simple_Instance(bufferID);
}
// ---- OpenAL_Instance_Base ----
void OpenAL_Instance_Base::play()
{
alSourcePlay(inst);
checkALError("starting playback");
}
void OpenAL_Instance_Base::stop()
{
alSourceStop(inst);
checkALError("stopping");
}
void OpenAL_Instance_Base::pause()
{
alSourcePause(inst);
checkALError("pausing");
}
bool OpenAL_Instance_Base::isPlaying()
{
ALint state;
alGetSourcei(inst, AL_SOURCE_STATE, &state);
return state == AL_PLAYING;
}
void OpenAL_Instance_Base::setVolume(float volume)
{
if(volume > 1.0) volume = 1.0;
if(volume < 0.0) volume = 0.0;
alSourcef(inst, AL_GAIN, volume);
checkALError("setting volume");
}
void OpenAL_Instance_Base::setPos(float x, float y, float z)
{
alSource3f(inst, AL_POSITION, x, y, z);
checkALError("setting position");
}
// ---- OpenAL_Simple_Instance ----
OpenAL_Simple_Instance::OpenAL_Simple_Instance(ALuint buf)
{
// Create instance and associate buffer
alGenSources(1, &inst);
alSourcei(inst, AL_BUFFER, buf);
}
OpenAL_Simple_Instance::~OpenAL_Simple_Instance()
{
// Stop
alSourceStop(inst);
// Return sound
alDeleteSources(1, &inst);
}
// ---- OpenAL_Stream_Instance ----
OpenAL_Stream_Instance::OpenAL_Stream_Instance(InputStream *_stream,
OpenAL_Manager *_owner)
: stream(_stream), owner(_owner)
{
// Deduce the file format from the stream info
getALFormat(stream, fmt, rate);
// Create the buffers and the sound instance
alGenBuffers(BUFS, bufs);
alGenSources(1, &inst);
checkALError("initializing");
// Fill the buffers and que them
for(int i=0; i<BUFS; i++)
queueBuffer(bufs[i]);
checkALError("buffering initial data");
// Add ourselves to the stream list
lit = owner->add_stream(this);
}
void OpenAL_Stream_Instance::queueBuffer(ALuint bId)
{
char buf[SIZE];
// Get the data
int len = stream->getData(buf, SIZE);
if(len == 0)
return;
// .. and stash it
alBufferData(bId, fmt, buf, len, rate);
alSourceQueueBuffers(inst, 1, &bId);
}
OpenAL_Stream_Instance::~OpenAL_Stream_Instance()
{
// Remove ourselves from streaming list
owner->remove_stream(lit);
// Stop
alSourceStop(inst);
// Kill the input stream
stream->drop();
// Return sound
alDeleteSources(1, &inst);
// Delete buffers
alDeleteBuffers(BUFS, bufs);
}
void OpenAL_Stream_Instance::update()
{
ALint count;
alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count);
for(int i = 0;i < count;i++)
{
// Unque a finished buffer
ALuint bId;
alSourceUnqueueBuffers(inst, 1, &bId);
// Queue a new buffer
queueBuffer(bId);
}
}

@ -0,0 +1,124 @@
#ifndef GOOI_SOUND_OPENAL_H
#define GOOI_SOUND_OPENAL_H
#include "../sound.h"
#include <AL/al.h>
#include <AL/alc.h>
#include <list>
namespace GOOI {
namespace Sound {
class OpenAL_Stream_Instance;
/// OpenAL implementation of Manager
class OpenAL_Manager : public Manager
{
public:
// List of all streaming sounds - these need to be updated regularly
typedef std::list<OpenAL_Stream_Instance*> LST;
OpenAL_Manager();
virtual ~OpenAL_Manager();
LST::iterator add_stream(OpenAL_Stream_Instance*);
void remove_stream(LST::iterator);
virtual Sound *load(const std::string &file, bool stream=false);
virtual Sound *load(InputSource* input, bool stream=false);
virtual void update();
virtual void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz);
private:
ALCdevice *Device;
ALCcontext *Context;
LST streaming;
};
/// OpenAL implementation of Sound
class OpenAL_Sound : public Sound
{
InputSource *source;
OpenAL_Manager *owner;
bool stream;
// Used for non-streaming files, contains the entire sound buffer if
// non-zero
ALuint bufferID;
public:
OpenAL_Sound(InputSource *src, OpenAL_Manager *own, bool str)
: source(src), owner(own), stream(str), bufferID(0) {}
~OpenAL_Sound();
virtual Instance *getInstance(bool is3d, bool repeat);
void drop() { delete this; }
};
/// Shared parent class that holds an OpenAL sound instance. Just used
/// for shared functionality, has no setup or cleanup code.
class OpenAL_Instance_Base : public Instance
{
protected:
ALuint inst;
public:
void drop() { delete this; }
virtual void play();
virtual void stop();
virtual void pause();
virtual bool isPlaying();
virtual void setVolume(float);
virtual void setPos(float x, float y, float z);
};
/// Non-streaming OpenAL-implementation of Instance. Uses a shared
/// sound buffer in OpenAL_Sound.
class OpenAL_Simple_Instance : public OpenAL_Instance_Base
{
public:
OpenAL_Simple_Instance(ALuint buf);
~OpenAL_Simple_Instance();
};
/// Streaming OpenAL-implementation of Instance.
class OpenAL_Stream_Instance : public OpenAL_Instance_Base
{
// Since OpenAL streams have to be updated manually each frame, we
// need to have a sufficiently large buffer so that we don't run out
// of data in the mean time. Each instance will take around 512 Kb
// of memory, independent of how large the file is.
static const int BUFS = 4;
static const int SIZE = 128*1024;
// Buffers
ALuint bufs[BUFS];
// Sound format settings
int rate, fmt;
// Source of data
InputStream *stream;
OpenAL_Manager *owner;
// List iterator, used for removing ourselves from the streaming
// list when we're deleted.
OpenAL_Manager::LST::iterator lit;
// Load and queue a new buffer
void queueBuffer(ALuint buffer);
public:
OpenAL_Stream_Instance(InputStream*, OpenAL_Manager*);
~OpenAL_Stream_Instance();
void update();
};
}} // namespaces
#endif

@ -0,0 +1,75 @@
#ifndef GOOI_SOUND_PAIR_H
#define GOOI_SOUND_PAIR_H
#include "sound.h"
#include <assert.h>
namespace GOOI {
namespace Sound {
/**
@brief This filter class adds file loading capabilities to a
Sound::Manager class, by associating an InputManager with it.
The class takes an existing Manager able to load streams, and
associates an InputManager with it. The combined class is able to
load files directly.
Example:
\code
// Combine FFmpeg input and OpenAL output. OpenAL cannot decode
// sound files on its own.
SoundPairManager mg(new FFM_InputManager, new OpenAL_Manager);
// We can now load filenames directly.
mg.load("file1.mp3");
\endcode
*/
class PairManager : public Manager
{
protected:
Manager *snd;
InputManager *inp;
public:
/// Empty constructor
PairManager() {}
/// Assign an input manager and a sound manager to this object
PairManager(InputManager *_inp, Manager *_snd)
{ set(_inp, _snd); }
/// Assign an input manager and a sound manager to this object
void set(InputManager *_inp, Manager *_snd)
{
inp = _inp;
snd = _snd;
needsUpdate = snd->needsUpdate;
has3D = snd->has3D;
canRepeatStream = snd->canRepeatStream;
// Both these should be true, or the use of this class is pretty
// pointless
canLoadSource = snd->canLoadSource;
canLoadFile = canLoadSource;
assert(canLoadSource && canLoadFile);
}
virtual Sound *load(const std::string &file, bool stream=false)
{ return load(inp->load(file), stream); }
virtual Sound *load(InputSource *input, bool stream=false)
{ return snd->load(input, stream); }
virtual void update() { snd->update(); }
virtual void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz)
{ snd->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); }
};
}}
#endif

@ -0,0 +1,75 @@
#ifndef GOOI_SOUND_INPUT_H
#define GOOI_SOUND_INPUT_H
#include <string>
#include <stdint.h>
namespace GOOI {
namespace Sound {
/// An abstract interface for a read-once stream of audio data.
/** All instances of this is created through InputSource. Objects
should be manually deleted through a call to drop() when they are
no longer needed.
*/
class InputStream
{
public:
/// Get the sample rate, number of channels, and bits per
/// sample. NULL parameters are ignored.
virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits) = 0;
/// Get decoded sound data from the stream.
/** Stores 'length' bytes (or less) in the buffer pointed to by
'output'. Returns the number of bytes written. The function will
only return less than 'length' at the end of the stream. When
the stream is empty, all subsequent calls will return zero.
@param output where to store data
@param length number of bytes to get
@return number of bytes actually written
*/
virtual uint32_t getData(void *output, uint32_t length) = 0;
/// Kill this object
virtual void drop() = 0;
/// Virtual destructor
virtual ~InputStream() {}
};
/// Abstract interface representing one sound source.
/** A sound source may represent one sound file or buffer, and is a
factory for producing InputStream objects from that
sound. Instances of this class are created by an InputManager. All
instances should be deleted through drop() when they are no longer
needed.
*/
class InputSource
{
public:
/// Create a stream from this sound
virtual InputStream *getStream() = 0;
/// Kill this object
virtual void drop() = 0;
/// Virtual destructor
virtual ~InputSource() {}
};
/// Main interface to a sound decoder backend.
/** An input manager is a factory of InputSource objects.
*/
class InputManager
{
public:
/// Load a sound input source from file
virtual InputSource *load(const std::string &file) = 0;
/// Virtual destructor
virtual ~InputManager() {}
};
}} // namespaces
#endif

@ -0,0 +1,172 @@
#ifndef GOOI_SOUND_SOUND_H
#define GOOI_SOUND_SOUND_H
#include <string>
#include "input.h"
namespace GOOI {
namespace Sound {
/// Abstract interface for sound instances
/** This class represents one sound instance, which may be played,
stopped, paused and so on. Instances are created from the Sound
class. All instances must be terminated manually using the drop()
function when they are no longer in use.
*/
class Instance
{
public:
/// Play or resume the sound
virtual void play() = 0;
/// Stop the sound
virtual void stop() = 0;
/// Pause the sound, may be resumed later
virtual void pause() = 0;
/// Check if the sound is still playing
virtual bool isPlaying() = 0;
/// Set the volume. The parameter must be between 0.0 and 1.0.
virtual void setVolume(float) = 0;
/// Set the position. May not have any effect on 2D sounds.
virtual void setPos(float x, float y, float z) = 0;
/// Kill the current object
virtual void drop() = 0;
/// Virtual destructor
virtual ~Instance() {}
};
/// Abstract interface for sound files or sources
/** This class acts as a factory for sound Instance objects.
Implementations may choose to store shared sound buffers or other
optimizations in subclasses of Sound. Objects of this class are
created through the Manager class. All objects of this class
should be terminated manually using the drop() function when they
are no longer in use.
*/
class Sound
{
public:
/**
@brief Create an instance of this sound
See also the capability flags in the Manager class.
@param is3d true if this the sound is to be 3d enabled
@param repeat true if the sound should loop
@return new Instance object
*/
virtual Instance *getInstance(bool is3d, bool repeat) = 0;
// Some prefab functions
/// Shortcut for creating 3D instances
Instance *get3D(bool loop=false)
{ return getInstance(true, loop); }
/// Shortcut for creating 2D instances
Instance *get2D(bool loop=false)
{ return getInstance(false, loop); }
/// Kill the current object
virtual void drop() = 0;
/// Virtual destructor
virtual ~Sound() {}
};
/// Abstract interface for the main sound manager class
/** The sound manager is used to load sound files and is a factory for
Sound objects. It is the main entry point to a given sound system
implementation.
The class also contains a set of public bools which describe the
capabilities the particular system. These should be set by
implementations (base classes) in their respective constructors.
*/
class Manager
{
public:
/** @brief If set to true, you should call update() regularly (every frame
or so) on this sound manager. If false, update() should not be
called.
*/
bool needsUpdate;
/** @brief true if 3D functions are available. If false, all use of
3D sounds and calls to setPos / setListenerPos will result in
undefined behavior.
*/
bool has3D;
/** @brief true if 'repeat' and 'stream' can be used simultaneously.
If false, repeating a streamed sound will give undefined
behavior.
*/
bool canRepeatStream;
/// true if we can load sounds directly from file
bool canLoadFile;
/// true if we can load sounds from an InputSource
bool canLoadSource;
/**
@brief Load a sound from an input source. Only valid if
canLoadSource is true.
This function loads a sound from a given stream as defined by
InputSource and InputStream. The InputSource and all streams
created from it will be dropped when drop() is called on the
owning sound / instance.
@param input the input source
@param stream true if the file should be streamed.
Implementations may use this for optimizing playback of
large files, but they are not required to.
@return a new Sound object
*/
virtual Sound *load(InputSource *input, bool stream=false) = 0;
/**
@brief Load a sound directly from file. Only valid if canLoadFile
is true.
@param file filename
@param stream true if the file should be streamed
@see load(InputSource*,bool)
*/
virtual Sound *load(const std::string &file, bool stream=false) = 0;
/// Call this every frame if needsUpdate is true
/**
Update function that should be called regularly (about every
frame in a normal game setting.) Implementions may use this to
fill streaming buffers and similar. Implementations that do not
need this should set needsUpdate to false.
*/
virtual void update() = 0;
/// Set listener position (coordinates, front and up vectors)
/**
Only valid if has3D is true.
@param x,y,z listener position
@param fx,fy,fz listener's looking direction
@param ux,uy,uz listener's up direction
*/
virtual void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz) = 0;
/// Virtual destructor
virtual ~Manager() {}
};
}} // Namespaces
#endif

@ -0,0 +1 @@
*_test

@ -0,0 +1,17 @@
GCC=g++ -I../ -I../imp/
all: audiere_test ffmpeg_openal_test
L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat)
L_OPENAL=$(shell pkg-config --libs openal)
L_AUDIERE=-laudiere
ffmpeg_openal_test: ffmpeg_openal_test.cpp ../imp/input_ffmpeg.cpp ../imp/output_openal.cpp
$(GCC) $^ -o $@ $(L_FFMPEG) $(L_OPENAL)
audiere_test: audiere_test.cpp ../imp/audiere_imp.cpp
$(GCC) $^ -o $@ $(L_AUDIERE)
clean:
rm *_test

@ -0,0 +1,7 @@
#include "audiere_imp.h"
using namespace GOOI::Sound;
AudiereManager mg;
#include "common.cpp"

@ -0,0 +1,41 @@
// This file is included directly into the test programs
#include <iostream>
#include <exception>
using namespace std;
void play(const char* name, bool music=false)
{
cout << "Playing " << name << "\n";
Sound *snd = NULL;
Instance *s = NULL;
try
{
snd = mg.load(name, music);
s = snd->getInstance(false, false);
s->play();
while(s->isPlaying())
{
usleep(10000);
if(mg.needsUpdate) mg.update();
}
}
catch(exception &e)
{
cout << " ERROR: " << e.what() << "\n";
}
if(s) s->drop();
if(snd) snd->drop();
}
int main()
{
play("cow.wav");
play("owl.ogg", true);
return 0;
}

Binary file not shown.

@ -0,0 +1,7 @@
#include "openal_ffmpeg.h"
using namespace GOOI::Sound;
OpenAL_FFM_Manager mg;
#include "common.cpp"

Binary file not shown.
Loading…
Cancel
Save