mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-24 18:39:59 +00:00
Initial public commit
This commit is contained in:
commit
9712fa03b7
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
upload_docs.sh
|
||||||
|
docs
|
||||||
|
*~
|
26
LICENSE.txt
Normal file
26
LICENSE.txt
Normal file
@ -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.
|
||||||
|
|
118
README.txt
Normal file
118
README.txt
Normal file
@ -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
Normal file
1
sound/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
1
sound/imp/.gitignore
vendored
Normal file
1
sound/imp/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
94
sound/imp/audiere_imp.cpp
Normal file
94
sound/imp/audiere_imp.cpp
Normal file
@ -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); }
|
73
sound/imp/audiere_imp.h
Normal file
73
sound/imp/audiere_imp.h
Normal file
@ -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
|
222
sound/imp/input_ffmpeg.cpp
Normal file
222
sound/imp/input_ffmpeg.cpp
Normal file
@ -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; }
|
71
sound/imp/input_ffmpeg.h
Normal file
71
sound/imp/input_ffmpeg.h
Normal file
@ -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
|
28
sound/imp/openal_ffmpeg.h
Normal file
28
sound/imp/openal_ffmpeg.h
Normal file
@ -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
|
349
sound/imp/output_openal.cpp
Normal file
349
sound/imp/output_openal.cpp
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
124
sound/imp/output_openal.h
Normal file
124
sound/imp/output_openal.h
Normal file
@ -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
|
75
sound/imp/sound_pair.h
Normal file
75
sound/imp/sound_pair.h
Normal file
@ -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
|
75
sound/input.h
Normal file
75
sound/input.h
Normal file
@ -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
|
172
sound/sound.h
Normal file
172
sound/sound.h
Normal file
@ -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
|
1
sound/tests/.gitignore
vendored
Normal file
1
sound/tests/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*_test
|
17
sound/tests/Makefile
Normal file
17
sound/tests/Makefile
Normal file
@ -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
|
7
sound/tests/audiere_test.cpp
Normal file
7
sound/tests/audiere_test.cpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "audiere_imp.h"
|
||||||
|
|
||||||
|
using namespace GOOI::Sound;
|
||||||
|
|
||||||
|
AudiereManager mg;
|
||||||
|
|
||||||
|
#include "common.cpp"
|
41
sound/tests/common.cpp
Normal file
41
sound/tests/common.cpp
Normal file
@ -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;
|
||||||
|
}
|
BIN
sound/tests/cow.wav
Normal file
BIN
sound/tests/cow.wav
Normal file
Binary file not shown.
7
sound/tests/ffmpeg_openal_test.cpp
Normal file
7
sound/tests/ffmpeg_openal_test.cpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "openal_ffmpeg.h"
|
||||||
|
|
||||||
|
using namespace GOOI::Sound;
|
||||||
|
|
||||||
|
OpenAL_FFM_Manager mg;
|
||||||
|
|
||||||
|
#include "common.cpp"
|
BIN
sound/tests/owl.ogg
Normal file
BIN
sound/tests/owl.ogg
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user