mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 18:35:20 +00:00
Merged multiple WIPs into one big mess
Merge branch 'master' into stream_buffer
This commit is contained in:
commit
c7701ace82
57
sound/buffer.h
Normal file
57
sound/buffer.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#ifndef MANGLE_SOUND_BUFFER_H
|
||||||
|
#define MANGLE_SOUND_BUFFER_H
|
||||||
|
|
||||||
|
#include "source.h"
|
||||||
|
#include "sources/memsource.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace Mangle {
|
||||||
|
namespace Sound {
|
||||||
|
|
||||||
|
/** A sample buffer is a factory that creates SampleSources from one
|
||||||
|
single sound source. It is helpful when you have many instances of
|
||||||
|
one sound and want to use one shared memory buffer.
|
||||||
|
|
||||||
|
This is just a helper class - you don't have to include it in your
|
||||||
|
program if you don't need it.
|
||||||
|
*/
|
||||||
|
class SampleBuffer
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Reads the source into a memory buffer. Not heavily optimized.
|
||||||
|
SampleBuffer(SampleSource *source)
|
||||||
|
{
|
||||||
|
size_t final = 0;
|
||||||
|
|
||||||
|
while(!source->eof())
|
||||||
|
{
|
||||||
|
const int add = 16*1024;
|
||||||
|
|
||||||
|
// Allocate more memory
|
||||||
|
size_t newSize = final + add;
|
||||||
|
buffer.resize(newSize);
|
||||||
|
|
||||||
|
// Fill in data
|
||||||
|
size_t read = source->read(&buffer[final], add);
|
||||||
|
|
||||||
|
// If we couldn't read enough data, we should be at the end
|
||||||
|
// of the stream
|
||||||
|
assert(read == add || source->eof());
|
||||||
|
|
||||||
|
final += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Downsize the buffer to the actual length
|
||||||
|
buffer.resize(final);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a new source
|
||||||
|
SampleSource *get()
|
||||||
|
{ return new MemorySource(&buffer[0], buffer.size()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
}}
|
||||||
|
#endif
|
@ -1,83 +0,0 @@
|
|||||||
#ifndef MANGLE_SOUND_INPUT_H
|
|
||||||
#define MANGLE_SOUND_INPUT_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "../stream/stream.h"
|
|
||||||
|
|
||||||
namespace Mangle {
|
|
||||||
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:
|
|
||||||
/// If true, the stream version of load() works
|
|
||||||
bool canLoadStream;
|
|
||||||
|
|
||||||
/// Load a sound input source from file
|
|
||||||
virtual InputSource *load(const std::string &file) = 0;
|
|
||||||
|
|
||||||
/// Load a sound input source from stream (if canLoadStream is true)
|
|
||||||
virtual InputSource *load(Stream::Stream *input) = 0;
|
|
||||||
|
|
||||||
/// Virtual destructor
|
|
||||||
virtual ~InputManager() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
}} // namespaces
|
|
||||||
#endif
|
|
@ -1,21 +1,24 @@
|
|||||||
#ifndef MANGLE_SOUND_SOUND_H
|
#ifndef MANGLE_SOUND_OUTPUT_H
|
||||||
#define MANGLE_SOUND_SOUND_H
|
#define MANGLE_SOUND_OUTPUT_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "input.h"
|
#include "source.h"
|
||||||
|
|
||||||
#include "../stream/stream.h"
|
#include "../stream/stream.h"
|
||||||
|
|
||||||
namespace Mangle {
|
namespace Mangle {
|
||||||
namespace Sound {
|
namespace Sound {
|
||||||
|
|
||||||
/// Abstract interface for sound instances
|
/// Abstract interface for a single playable sound
|
||||||
/** This class represents one sound instance, which may be played,
|
/** This class represents one sound outlet, which may be played,
|
||||||
stopped, paused and so on. Instances are created from the Sound
|
stopped, paused and so on.
|
||||||
class. All instances must be terminated manually using the drop()
|
|
||||||
function when they are no longer in use.
|
Sound instances are created from the SoundFactory class. Sounds
|
||||||
|
may be connected to a SampleSource or read directly from a file,
|
||||||
|
and they may support 3d sounds, looping and other features
|
||||||
|
depending on the capabilities of the backend system.
|
||||||
*/
|
*/
|
||||||
class Instance
|
class Sound
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Play or resume the sound
|
/// Play or resume the sound
|
||||||
@ -36,63 +39,26 @@ class Instance
|
|||||||
/// Set the position. May not have any effect on 2D sounds.
|
/// Set the position. May not have any effect on 2D sounds.
|
||||||
virtual void setPos(float x, float y, float z) = 0;
|
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 destructor
|
||||||
virtual ~Sound() {}
|
virtual ~Sound() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Abstract interface for the main sound manager class
|
/// Factory interface for creating Sound objects
|
||||||
/** The sound manager is used to load sound files and is a factory for
|
/** The SoundFactory is the main entry point to a given sound output
|
||||||
Sound objects. It is the main entry point to a given sound system
|
system. It is used to create Sound objects, which may be connected
|
||||||
implementation.
|
to a sound file or stream, and which may be individually played,
|
||||||
|
paused, and so on.
|
||||||
|
|
||||||
The class also contains a set of public bools which describe the
|
The class also contains a set of public bools which describe the
|
||||||
capabilities the particular system. These should be set by
|
capabilities the particular system. These should be set by
|
||||||
implementations (base classes) in their respective constructors.
|
implementations (base classes) in their respective constructors.
|
||||||
*/
|
*/
|
||||||
class Manager
|
class SoundFactory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// Virtual destructor
|
||||||
|
virtual ~SoundFactory() {}
|
||||||
|
|
||||||
/** @brief If set to true, you should call update() regularly (every frame
|
/** @brief If set to true, you should call update() regularly (every frame
|
||||||
or so) on this sound manager. If false, update() should not be
|
or so) on this sound manager. If false, update() should not be
|
||||||
called.
|
called.
|
||||||
@ -111,23 +77,21 @@ class Manager
|
|||||||
*/
|
*/
|
||||||
bool canRepeatStream;
|
bool canRepeatStream;
|
||||||
|
|
||||||
/// true if we can load sounds directly from file
|
/// true if we can load sounds directly from file (containing encoded data)
|
||||||
bool canLoadFile;
|
bool canLoadFile;
|
||||||
|
|
||||||
/// true if we can load sounds from an InputSource
|
/// If true, we can lound sound files from a Stream (containing encoded data)
|
||||||
bool canLoadSource;
|
|
||||||
|
|
||||||
/// If true, we can lound sound files from a Stream
|
|
||||||
bool canLoadStream;
|
bool canLoadStream;
|
||||||
|
|
||||||
|
/// true if we can load sounds from a SampleSource (containing raw data)
|
||||||
|
bool canLoadSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Load a sound from an input source. Only valid if
|
@brief Load a sound from a sample source. Only valid if
|
||||||
canLoadSource is true.
|
canLoadSource is true.
|
||||||
|
|
||||||
This function loads a sound from a given stream as defined by
|
This function loads a sound from a given stream as defined by
|
||||||
InputSource and InputStream. The InputSource and all streams
|
SampleSource.
|
||||||
created from it will be dropped when drop() is called on the
|
|
||||||
owning sound / instance.
|
|
||||||
|
|
||||||
@param input the input source
|
@param input the input source
|
||||||
@param stream true if the file should be streamed.
|
@param stream true if the file should be streamed.
|
||||||
@ -135,10 +99,10 @@ class Manager
|
|||||||
large files, but they are not required to.
|
large files, but they are not required to.
|
||||||
@return a new Sound object
|
@return a new Sound object
|
||||||
*/
|
*/
|
||||||
virtual Sound *load(InputSource *input, bool stream=false) = 0;
|
virtual Sound *load(SampleSource *input, bool stream=false) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Load a sound directly from file. Only valid if canLoadStream
|
@brief Load a sound file from stream. Only valid if canLoadStream
|
||||||
is true.
|
is true.
|
||||||
|
|
||||||
@param input audio file stream
|
@param input audio file stream
|
||||||
@ -159,10 +123,10 @@ class Manager
|
|||||||
|
|
||||||
/// Call this every frame if needsUpdate is true
|
/// Call this every frame if needsUpdate is true
|
||||||
/**
|
/**
|
||||||
Update function that should be called regularly (about every
|
This should be called regularly (about every frame in a normal
|
||||||
frame in a normal game setting.) Implementions may use this to
|
game setting.) Implementions may use this for filling streaming
|
||||||
fill streaming buffers and similar. Implementations that do not
|
buffers and similar tasks. Implementations that do not need this
|
||||||
need this should set needsUpdate to false.
|
should set needsUpdate to false.
|
||||||
*/
|
*/
|
||||||
virtual void update() = 0;
|
virtual void update() = 0;
|
||||||
|
|
||||||
@ -177,9 +141,6 @@ class Manager
|
|||||||
virtual void setListenerPos(float x, float y, float z,
|
virtual void setListenerPos(float x, float y, float z,
|
||||||
float fx, float fy, float fz,
|
float fx, float fy, float fz,
|
||||||
float ux, float uy, float uz) = 0;
|
float ux, float uy, float uz) = 0;
|
||||||
|
|
||||||
/// Virtual destructor
|
|
||||||
virtual ~Manager() {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // Namespaces
|
}} // Namespaces
|
@ -1,11 +1,10 @@
|
|||||||
#include "output_openal.h"
|
#include "openal_out.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <vector>
|
// ALuint bufferID;
|
||||||
|
|
||||||
using namespace Mangle::Sound;
|
using namespace Mangle::Sound;
|
||||||
|
|
||||||
|
|
||||||
// ---- Helper functions and classes ----
|
// ---- Helper functions and classes ----
|
||||||
|
|
||||||
class OpenAL_Exception : public std::exception
|
class OpenAL_Exception : public std::exception
|
||||||
@ -64,97 +63,71 @@ static void getALFormat(InputStream *inp, int &fmt, int &rate)
|
|||||||
fail("Unsupported input format");
|
fail("Unsupported input format");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- OpenAL_Sound ----
|
||||||
|
|
||||||
// ---- Manager ----
|
OpenAL_Sound::OpenAL_Sound(SampleSource *input)
|
||||||
|
|
||||||
OpenAL_Manager::OpenAL_Manager()
|
|
||||||
: Context(NULL), Device(NULL)
|
|
||||||
{
|
{
|
||||||
needsUpdate = true;
|
|
||||||
has3D = true;
|
|
||||||
canRepeatStream = false;
|
|
||||||
canLoadFile = false;
|
|
||||||
canLoadSource = true;
|
|
||||||
canLoadStream = false;
|
|
||||||
|
|
||||||
// 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()
|
void OpenAL_Sound::play()
|
||||||
{
|
{
|
||||||
// Deinitialize sound system
|
alSourcePlay(inst);
|
||||||
alcMakeContextCurrent(NULL);
|
checkALError("starting playback");
|
||||||
if(Context) alcDestroyContext(Context);
|
|
||||||
if(Device) alcCloseDevice(Device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Sound *OpenAL_Manager::load(const std::string &file, bool stream)
|
void OpenAL_Sound::stop()
|
||||||
{ assert(0 && "OpenAL cannot decode files"); }
|
|
||||||
|
|
||||||
Sound *OpenAL_Manager::load(Stream::Stream*,bool)
|
|
||||||
{ assert(0 && "OpenAL cannot decode streams"); }
|
|
||||||
|
|
||||||
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
|
alSourceStop(inst);
|
||||||
LST::iterator it, next;
|
checkALError("stopping");
|
||||||
for(it = streaming.begin();
|
|
||||||
it != streaming.end();
|
|
||||||
it++)
|
|
||||||
{
|
|
||||||
(*it)->update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenAL_Manager::setListenerPos(float x, float y, float z,
|
void OpenAL_Sound::pause()
|
||||||
float fx, float fy, float fz,
|
|
||||||
float ux, float uy, float uz)
|
|
||||||
{
|
{
|
||||||
ALfloat orient[6];
|
alSourcePause(inst);
|
||||||
orient[0] = fx;
|
checkALError("pausing");
|
||||||
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)
|
bool OpenAL_Sound::isPlaying()
|
||||||
{
|
{
|
||||||
streaming.push_front(inst);
|
ALint state;
|
||||||
return streaming.begin();
|
alGetSourcei(inst, AL_SOURCE_STATE, &state);
|
||||||
|
|
||||||
|
return state == AL_PLAYING;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenAL_Manager::remove_stream(LST::iterator it)
|
void OpenAL_Sound::setVolume(float volume)
|
||||||
{
|
{
|
||||||
streaming.erase(it);
|
if(volume > 1.0) volume = 1.0;
|
||||||
|
if(volume < 0.0) volume = 0.0;
|
||||||
|
alSourcef(inst, AL_GAIN, volume);
|
||||||
|
checkALError("setting volume");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL_Sound::setPos(float x, float y, float z)
|
||||||
|
{
|
||||||
|
alSource3f(inst, AL_POSITION, x, y, z);
|
||||||
|
checkALError("setting position");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---- 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)
|
Instance *OpenAL_Sound::getInstance(bool is3d, bool repeat)
|
||||||
{
|
{
|
||||||
@ -217,49 +190,6 @@ Instance *OpenAL_Sound::getInstance(bool is3d, bool repeat)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---- 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::OpenAL_Simple_Instance(ALuint buf)
|
OpenAL_Simple_Instance::OpenAL_Simple_Instance(ALuint buf)
|
109
sound/outputs/openal_out.h
Normal file
109
sound/outputs/openal_out.h
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#ifndef MANGLE_SOUND_OPENAL_OUT_H
|
||||||
|
#define MANGLE_SOUND_OPENAL_OUT_H
|
||||||
|
|
||||||
|
#include "../output.h"
|
||||||
|
|
||||||
|
#include <AL/al.h>
|
||||||
|
#include <AL/alc.h>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
namespace Mangle {
|
||||||
|
namespace Sound {
|
||||||
|
|
||||||
|
/// OpenAL sound output
|
||||||
|
class OpenAL_Sound : public Sound
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
ALuint inst;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpenAL_Sound(SampleSource *input);
|
||||||
|
~OpenAL_Sound();
|
||||||
|
|
||||||
|
/// Play or resume the sound
|
||||||
|
void play();
|
||||||
|
|
||||||
|
/// Stop the sound
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/// Pause the sound, may be resumed later
|
||||||
|
void pause();
|
||||||
|
|
||||||
|
/// Check if the sound is still playing
|
||||||
|
bool isPlaying();
|
||||||
|
|
||||||
|
/// Set the volume. The parameter must be between 0.0 and 1.0.
|
||||||
|
void setVolume(float);
|
||||||
|
|
||||||
|
/// Set the 3D position.
|
||||||
|
void setPos(float x, float y, float z);
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpenALFactory : public SoundFactory
|
||||||
|
{
|
||||||
|
ALCdevice *Device;
|
||||||
|
ALCcontext *Context;
|
||||||
|
bool didSetup;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Initialize object. Pass true (default) if you want the
|
||||||
|
/// constructor to set up the current ALCdevice and ALCcontext for
|
||||||
|
/// you.
|
||||||
|
OpenALFactory(bool doSetup = true)
|
||||||
|
: didSetup(doSetup)
|
||||||
|
{
|
||||||
|
needsUpdate = false;
|
||||||
|
has3D = true;
|
||||||
|
canRepeatStream = false;
|
||||||
|
canLoadFile = false;
|
||||||
|
canLoadStream = false;
|
||||||
|
canLoadSource = true;
|
||||||
|
|
||||||
|
if(doSetup)
|
||||||
|
{
|
||||||
|
// Set up sound system
|
||||||
|
Device = alcOpenDevice(NULL);
|
||||||
|
Context = alcCreateContext(Device, NULL);
|
||||||
|
|
||||||
|
if(!Device || !Context)
|
||||||
|
fail("Failed to initialize context or device");
|
||||||
|
|
||||||
|
alcMakeContextCurrent(Context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~OpenALFactory()
|
||||||
|
{
|
||||||
|
// Deinitialize sound system
|
||||||
|
if(didSetup)
|
||||||
|
{
|
||||||
|
alcMakeContextCurrent(NULL);
|
||||||
|
if(Context) alcDestroyContext(Context);
|
||||||
|
if(Device) alcCloseDevice(Device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sound *load(const std::string &file, bool stream=false) { assert(0); }
|
||||||
|
Sound *load(Stream::Stream *input, bool stream=false) { assert(0); }
|
||||||
|
Sound *load(SampleSource* input, bool stream=false)
|
||||||
|
{ return new OpenAL_Sound(input); }
|
||||||
|
|
||||||
|
void update() {}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespaces
|
||||||
|
#endif
|
1
sound/servers/.gitignore
vendored
1
sound/servers/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
|
|
@ -1,95 +0,0 @@
|
|||||||
#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 Mangle::Sound;
|
|
||||||
|
|
||||||
AudiereManager::AudiereManager()
|
|
||||||
{
|
|
||||||
needsUpdate = false;
|
|
||||||
has3D = false;
|
|
||||||
canRepeatStream = true;
|
|
||||||
canLoadFile = true;
|
|
||||||
canLoadSource = false;
|
|
||||||
canLoadStream = 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); }
|
|
@ -1,77 +0,0 @@
|
|||||||
#ifndef MANGLE_SOUND_AUDIERE_H
|
|
||||||
#define MANGLE_SOUND_AUDIERE_H
|
|
||||||
|
|
||||||
#include "../sound.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <audiere.h>
|
|
||||||
|
|
||||||
namespace Mangle {
|
|
||||||
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);
|
|
||||||
|
|
||||||
/// not implemented yet
|
|
||||||
virtual Sound *load(Stream::Stream *input, bool stream=false)
|
|
||||||
{ assert(0); }
|
|
||||||
|
|
||||||
/// 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
|
|
@ -1,59 +0,0 @@
|
|||||||
#ifndef MANGLE_SOUND_AUDIERE_INPUT_H
|
|
||||||
#define MANGLE_SOUND_AUDIERE_INPUT_H
|
|
||||||
|
|
||||||
#include "../input.h"
|
|
||||||
|
|
||||||
#include <audiere.h>
|
|
||||||
|
|
||||||
namespace Mangle {
|
|
||||||
namespace Sound {
|
|
||||||
|
|
||||||
/// Implementation of Sound::InputManager for Audiere
|
|
||||||
class AudiereInput : public InputManager
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AudiereInput();
|
|
||||||
|
|
||||||
/// Load a source from a file
|
|
||||||
InputSource *load(const std::string &file);
|
|
||||||
|
|
||||||
/// Load a source from a stream
|
|
||||||
virtual InputSource *load(Stream::Stream *input);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Audiere InputSource implementation
|
|
||||||
class AudiereSource : public InputSource
|
|
||||||
{
|
|
||||||
audiere::SampleBufferPtr buf;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AudiereSource(const std::string &file);
|
|
||||||
AudiereSource(Stream::Stream *input);
|
|
||||||
InputStream *getStream();
|
|
||||||
void drop() { delete this; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Audiere InputStream implementation
|
|
||||||
class AudiereStream : public InputStream
|
|
||||||
{
|
|
||||||
audiere::SampleSourcePtr sample;
|
|
||||||
int frameSize; // Size of one frame, in bytes
|
|
||||||
|
|
||||||
static const int PSIZE = 10;
|
|
||||||
|
|
||||||
// Temporary storage for unevenly read samples. See the comment for
|
|
||||||
// getData() in the .cpp file.
|
|
||||||
char pullOver[PSIZE];
|
|
||||||
// How much of the above buffer is in use
|
|
||||||
int pullSize;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AudiereStream(audiere::SampleSourcePtr _sample);
|
|
||||||
|
|
||||||
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
|
|
||||||
uint32_t getData(void *data, uint32_t length);
|
|
||||||
void drop() { delete this; }
|
|
||||||
};
|
|
||||||
|
|
||||||
}} // Namespace
|
|
||||||
#endif
|
|
@ -1,75 +0,0 @@
|
|||||||
#ifndef MANGLE_SOUND_FFMPEG_H
|
|
||||||
#define MANGLE_SOUND_FFMPEG_H
|
|
||||||
|
|
||||||
#include "../input.h"
|
|
||||||
#include <exception>
|
|
||||||
#include <vector>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Mangle {
|
|
||||||
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);
|
|
||||||
|
|
||||||
/// not supported
|
|
||||||
virtual InputSource *load(Stream::Stream *input) { assert(0); }
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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
|
|
@ -1,28 +0,0 @@
|
|||||||
#ifndef MANGLE_FFMPEG_OPENAL_H
|
|
||||||
#define MANGLE_FFMPEG_OPENAL_H
|
|
||||||
|
|
||||||
#include "input_filter.h"
|
|
||||||
#include "input_ffmpeg.h"
|
|
||||||
#include "output_openal.h"
|
|
||||||
|
|
||||||
namespace Mangle {
|
|
||||||
namespace Sound {
|
|
||||||
|
|
||||||
/// An InputFilter that adds FFmpeg decoding to OpenAL
|
|
||||||
class OpenAL_FFM_Manager : public InputFilter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OpenAL_FFM_Manager()
|
|
||||||
{
|
|
||||||
set(new OpenAL_Manager,
|
|
||||||
new FFM_InputManager);
|
|
||||||
}
|
|
||||||
~OpenAL_FFM_Manager()
|
|
||||||
{
|
|
||||||
delete snd;
|
|
||||||
delete inp;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}}
|
|
||||||
#endif
|
|
@ -1,125 +0,0 @@
|
|||||||
#ifndef MANGLE_SOUND_OPENAL_H
|
|
||||||
#define MANGLE_SOUND_OPENAL_H
|
|
||||||
|
|
||||||
#include "../sound.h"
|
|
||||||
|
|
||||||
#include <AL/al.h>
|
|
||||||
#include <AL/alc.h>
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
namespace Mangle {
|
|
||||||
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(Stream::Stream *input, 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
|
|
63
sound/source.h
Normal file
63
sound/source.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#ifndef MANGLE_SOUND_SOURCE_H
|
||||||
|
#define MANGLE_SOUND_SOURCE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "../stream/stream.h"
|
||||||
|
|
||||||
|
namespace Mangle {
|
||||||
|
namespace Sound {
|
||||||
|
|
||||||
|
/// A stream containing raw sound data and information about the format
|
||||||
|
class SampleSource : public Stream::Stream
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
bool isEof;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SampleSource()
|
||||||
|
{
|
||||||
|
// These are usually not needed for sound data
|
||||||
|
isSeekable = false;
|
||||||
|
hasPosition = false;
|
||||||
|
hasSize = false;
|
||||||
|
|
||||||
|
isEof = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) const = 0;
|
||||||
|
|
||||||
|
bool eof() const { return isEof; }
|
||||||
|
|
||||||
|
// Disabled functions
|
||||||
|
void seek(size_t pos) const { assert(0); }
|
||||||
|
size_t tell() const { assert(0); }
|
||||||
|
size_t size() const { assert(0); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A factory interface for loading SampleSources from file or stream
|
||||||
|
class SampleSourceLoader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// If true, the stream version of load() works
|
||||||
|
bool canLoadStream;
|
||||||
|
|
||||||
|
/// If true, the file version of load() works
|
||||||
|
bool canLoadFile;
|
||||||
|
|
||||||
|
/// Load a sound input source from file (if canLoadFile is true)
|
||||||
|
virtual SampleSource *load(const std::string &file) = 0;
|
||||||
|
|
||||||
|
/// Load a sound input source from stream (if canLoadStream is true)
|
||||||
|
virtual SampleSource *load(Stream::Stream *input) = 0;
|
||||||
|
|
||||||
|
/// Virtual destructor
|
||||||
|
virtual ~SampleSourceLoader() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespaces
|
||||||
|
#endif
|
@ -1,5 +1,4 @@
|
|||||||
#include "input_audiere.h"
|
#include "audiere_source.h"
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "../../stream/clients/audiere_file.h"
|
#include "../../stream/clients/audiere_file.h"
|
||||||
|
|
||||||
@ -23,65 +22,9 @@ static void fail(const std::string &msg)
|
|||||||
using namespace audiere;
|
using namespace audiere;
|
||||||
using namespace Mangle::Sound;
|
using namespace Mangle::Sound;
|
||||||
|
|
||||||
// --- InputManager ---
|
// --- SampleSource ---
|
||||||
|
|
||||||
AudiereInput::AudiereInput()
|
void AudiereSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
|
||||||
{
|
|
||||||
canLoadStream = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputSource *AudiereInput::load(const std::string &file)
|
|
||||||
{ return new AudiereSource(file); }
|
|
||||||
|
|
||||||
InputSource *AudiereInput::load(Stream::Stream *input)
|
|
||||||
{ return new AudiereSource(input); }
|
|
||||||
|
|
||||||
// --- InputSource ---
|
|
||||||
|
|
||||||
AudiereSource::AudiereSource(const std::string &file)
|
|
||||||
{
|
|
||||||
SampleSourcePtr sample = OpenSampleSource(file.c_str());
|
|
||||||
if(!sample)
|
|
||||||
fail("Couldn't load file " + file);
|
|
||||||
|
|
||||||
buf = CreateSampleBuffer(sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudiereSource::AudiereSource(Stream::Stream *input)
|
|
||||||
{
|
|
||||||
SampleSourcePtr sample = OpenSampleSource
|
|
||||||
(new Stream::AudiereFile(input));
|
|
||||||
if(!sample)
|
|
||||||
fail("Couldn't load stream");
|
|
||||||
|
|
||||||
buf = CreateSampleBuffer(sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream *AudiereSource::getStream()
|
|
||||||
{
|
|
||||||
return new AudiereStream(buf->openStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- InputStream ---
|
|
||||||
|
|
||||||
AudiereStream::AudiereStream(SampleSourcePtr _sample)
|
|
||||||
: sample(_sample), pullSize(0)
|
|
||||||
{
|
|
||||||
assert(sample);
|
|
||||||
|
|
||||||
SampleFormat fmt;
|
|
||||||
int channels, rate;
|
|
||||||
sample->getFormat(channels, rate, fmt);
|
|
||||||
|
|
||||||
// Calculate the size of one frame
|
|
||||||
frameSize = GetSampleSize(fmt) * channels;
|
|
||||||
|
|
||||||
// Make sure that our pullover hack will work. Increase this size if
|
|
||||||
// this doesn't work in all cases.
|
|
||||||
assert(frameSize <= PSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudiereStream::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
|
|
||||||
{
|
{
|
||||||
SampleFormat fmt;
|
SampleFormat fmt;
|
||||||
sample->getFormat(*channels, *rate, fmt);
|
sample->getFormat(*channels, *rate, fmt);
|
||||||
@ -98,10 +41,11 @@ void AudiereStream::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
|
|||||||
reading a whole number of frames - if not, we need to store the
|
reading a whole number of frames - if not, we need to store the
|
||||||
remainding part of the last frame and remember it for the next read
|
remainding part of the last frame and remember it for the next read
|
||||||
operation.
|
operation.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
uint32_t AudiereStream::getData(void *_data, uint32_t length)
|
size_t AudiereSource::read(void *_data, size_t length)
|
||||||
{
|
{
|
||||||
|
if(isEof) return 0;
|
||||||
|
|
||||||
char *data = (char*)_data;
|
char *data = (char*)_data;
|
||||||
|
|
||||||
// Move the remains from the last operation first
|
// Move the remains from the last operation first
|
||||||
@ -122,18 +66,74 @@ uint32_t AudiereStream::getData(void *_data, uint32_t length)
|
|||||||
// Read the data
|
// Read the data
|
||||||
int res = sample->read(frames, data);
|
int res = sample->read(frames, data);
|
||||||
|
|
||||||
// Are we missing data? If res<length and we're at the end of the
|
if(res < frames)
|
||||||
// stream, then this doesn't apply.
|
isEof = true;
|
||||||
if(res == frames && pullSize &&
|
|
||||||
// Read one more sample
|
// Are we missing data? If we're at the end of the stream, then this
|
||||||
(sample->read(1, pullOver) != 0))
|
// doesn't apply.
|
||||||
|
if(!isEof && pullSize)
|
||||||
{
|
{
|
||||||
// Now, move as much of it as we can fit into the output
|
// Read one more sample
|
||||||
// data
|
if(sample->read(1, pullOver) != 0)
|
||||||
memcpy(data+length-pullSize, pullOver, pullSize);
|
{
|
||||||
|
// Then, move as much of it as we can fit into the output
|
||||||
|
// data
|
||||||
|
memcpy(data+length-pullSize, pullOver, pullSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Failed reading, we're out of data
|
||||||
|
isEof = true;
|
||||||
}
|
}
|
||||||
else pullSize = 0;
|
|
||||||
|
// If we're at the end of the stream, then no data remains to be
|
||||||
|
// pulled over
|
||||||
|
if(isEof)
|
||||||
|
pullSize = 0;
|
||||||
|
|
||||||
// Return the total number of bytes stored
|
// Return the total number of bytes stored
|
||||||
return frameSize*res + pullSize;
|
return frameSize*res + pullSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Constructors ---
|
||||||
|
|
||||||
|
AudiereSource::AudiereSource(const std::string &file)
|
||||||
|
{
|
||||||
|
sample = OpenSampleSource(file.c_str());
|
||||||
|
|
||||||
|
if(!sample)
|
||||||
|
fail("Couldn't load file " + file);
|
||||||
|
|
||||||
|
getFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudiereSource::AudiereSource(Stream::Stream *input)
|
||||||
|
{
|
||||||
|
// Use our Stream::AudiereFile implementation to convert a Mangle
|
||||||
|
// 'Stream' to an Audiere 'File'
|
||||||
|
sample = OpenSampleSource(new Stream::AudiereFile(input));
|
||||||
|
if(!sample)
|
||||||
|
fail("Couldn't load stream");
|
||||||
|
|
||||||
|
getFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudiereSource::AudiereSource(audiere::SampleSourcePtr src)
|
||||||
|
: sample(src)
|
||||||
|
{ assert(sample); getFormat(); }
|
||||||
|
|
||||||
|
// Common function called from all constructors
|
||||||
|
AudiereSource::getFormat()
|
||||||
|
{
|
||||||
|
assert(sample);
|
||||||
|
|
||||||
|
SampleFormat fmt;
|
||||||
|
int channels, rate;
|
||||||
|
sample->getFormat(channels, rate, fmt);
|
||||||
|
|
||||||
|
// Calculate the size of one frame
|
||||||
|
frameSize = GetSampleSize(fmt) * channels;
|
||||||
|
|
||||||
|
// Make sure that our pullover hack will work. Increase this size if
|
||||||
|
// this doesn't work in all cases.
|
||||||
|
assert(frameSize <= PSIZE);
|
||||||
|
}
|
51
sound/sources/audiere_source.h
Normal file
51
sound/sources/audiere_source.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H
|
||||||
|
#define MANGLE_SOUND_AUDIERE_SOURCE_H
|
||||||
|
|
||||||
|
#include "../source.h"
|
||||||
|
|
||||||
|
#include <audiere.h>
|
||||||
|
|
||||||
|
namespace Mangle {
|
||||||
|
namespace Sound {
|
||||||
|
|
||||||
|
/// A sample source that decodes files using Audiere
|
||||||
|
class AudiereSource : public SampleSource
|
||||||
|
{
|
||||||
|
audiere::SampleSourcePtr sample;
|
||||||
|
|
||||||
|
// Number of bytes we cache between reads. This should correspond to
|
||||||
|
// the maximum possible value of frameSize.
|
||||||
|
static const int PSIZE = 10;
|
||||||
|
|
||||||
|
// Size of one frame, in bytes
|
||||||
|
int frameSize;
|
||||||
|
|
||||||
|
// Temporary storage for unevenly read samples. See the comment for
|
||||||
|
// read() in the .cpp file.
|
||||||
|
char pullOver[PSIZE];
|
||||||
|
// How much of the above buffer is in use
|
||||||
|
int pullSize;
|
||||||
|
|
||||||
|
void getFormat();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Decode the given sound file
|
||||||
|
AudiereSource(const std::string &file);
|
||||||
|
|
||||||
|
/// Decode the given sound stream
|
||||||
|
AudiereSource(Stream::Stream *src);
|
||||||
|
|
||||||
|
/// Read directly from an existing audiere::SampleSource
|
||||||
|
AudiereSource(audiere::SampleSourcePtr src);
|
||||||
|
|
||||||
|
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
|
||||||
|
size_t read(void *data, size_t length);
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "loadertemplate.h"
|
||||||
|
|
||||||
|
/// A factory that loads AudiereSources from file and stream
|
||||||
|
typedef SSL_Template<AudiereSource,true,true> AudiereLoader;
|
||||||
|
|
||||||
|
}} // Namespace
|
||||||
|
#endif
|
@ -1,12 +1,22 @@
|
|||||||
#include "input_ffmpeg.h"
|
#include "input_ffmpeg.h"
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
using namespace Mangle::Sound;
|
using namespace Mangle::Sound;
|
||||||
|
|
||||||
// Static output buffer. Not thread safe, but supports multiple
|
// Static output buffer. Not thread safe, but supports multiple
|
||||||
// streams operated from the same thread.
|
// streams operated from the same thread.
|
||||||
static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
||||||
bool FFM_InputManager::init = false;
|
|
||||||
|
/// 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();
|
||||||
|
};
|
||||||
|
|
||||||
FFM_Exception::FFM_Exception(const std::string &m)
|
FFM_Exception::FFM_Exception(const std::string &m)
|
||||||
: msg(m) {}
|
: msg(m) {}
|
||||||
@ -21,44 +31,23 @@ static void fail(const std::string &msg)
|
|||||||
throw FFM_Exception("FFMpeg exception: " + msg);
|
throw FFM_Exception("FFMpeg exception: " + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Loader ---
|
||||||
|
|
||||||
// --- Manager ---
|
static bool init = false;
|
||||||
|
|
||||||
FFM_InputManager::FFM_InputManager()
|
FFMpegLoader::FFMpegLoader(bool setup)
|
||||||
{
|
{
|
||||||
if(!init)
|
if(setup && !init)
|
||||||
{
|
{
|
||||||
av_register_all();
|
av_register_all();
|
||||||
av_log_set_level(AV_LOG_ERROR);
|
av_log_set_level(AV_LOG_ERROR);
|
||||||
init = true;
|
init = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
canLoadStream = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InputSource *FFM_InputManager::load(const std::string &file)
|
|
||||||
{ return new FFM_InputSource(file); }
|
|
||||||
|
|
||||||
|
|
||||||
// --- Source ---
|
// --- Source ---
|
||||||
|
|
||||||
FFM_InputSource::FFM_InputSource(const std::string &file)
|
FFMpegSource::FFMpegSource(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;
|
std::string msg;
|
||||||
AVCodec *codec;
|
AVCodec *codec;
|
||||||
@ -83,7 +72,7 @@ FFM_InputStream::FFM_InputStream(const std::string &file)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(StreamNum == FmtCtx->nb_streams)
|
if(StreamNum == FmtCtx->nb_streams)
|
||||||
fail("File " + file + " didn't contain any audio streams");
|
fail("File '" + file + "' didn't contain any audio streams");
|
||||||
|
|
||||||
// Open the decoder
|
// Open the decoder
|
||||||
CodecCtx = FmtCtx->streams[StreamNum]->codec;
|
CodecCtx = FmtCtx->streams[StreamNum]->codec;
|
||||||
@ -91,7 +80,7 @@ FFM_InputStream::FFM_InputStream(const std::string &file)
|
|||||||
|
|
||||||
if(!codec || avcodec_open(CodecCtx, codec) < 0)
|
if(!codec || avcodec_open(CodecCtx, codec) < 0)
|
||||||
{
|
{
|
||||||
msg = "Error loading " + file + ": ";
|
msg = "Error loading '" + file + "': ";
|
||||||
if(codec)
|
if(codec)
|
||||||
msg += "coded error";
|
msg += "coded error";
|
||||||
else
|
else
|
||||||
@ -108,24 +97,24 @@ FFM_InputStream::FFM_InputStream(const std::string &file)
|
|||||||
fail(msg);
|
fail(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
FFM_InputStream::~FFM_InputStream()
|
FFMpegSource::~FFMpegSource()
|
||||||
{
|
{
|
||||||
avcodec_close(CodecCtx);
|
avcodec_close(CodecCtx);
|
||||||
av_close_input_file(FmtCtx);
|
av_close_input_file(FmtCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFM_InputStream::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
|
void FFMpegSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
|
||||||
{
|
{
|
||||||
if(rate) *rate = CodecCtx->sample_rate;
|
if(rate) *rate = CodecCtx->sample_rate;
|
||||||
if(channels) *channels = CodecCtx->channels;
|
if(channels) *channels = CodecCtx->channels;
|
||||||
if(bits) *bits = 16;
|
if(bits) *bits = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t FFM_InputStream::getData(void *data, uint32_t length)
|
size_t FFMpegSource::read(void *data, size_t length)
|
||||||
{
|
{
|
||||||
if(empty) return 0;
|
if(isEof) return 0;
|
||||||
|
|
||||||
uint32_t left = length;
|
size_t left = length;
|
||||||
uint8_t *outPtr = (uint8_t*)data;
|
uint8_t *outPtr = (uint8_t*)data;
|
||||||
|
|
||||||
// First, copy over any stored data we might be sitting on
|
// First, copy over any stored data we might be sitting on
|
||||||
@ -195,7 +184,7 @@ uint32_t FFM_InputStream::getData(void *data, uint32_t length)
|
|||||||
memcpy(outPtr, outBuf, copy);
|
memcpy(outPtr, outBuf, copy);
|
||||||
|
|
||||||
// left = how much space is left in the caller output
|
// left = how much space is left in the caller output
|
||||||
// buffer
|
// buffer. This loop repeats as long left is > 0
|
||||||
left -= copy;
|
left -= copy;
|
||||||
outPtr += copy;
|
outPtr += copy;
|
||||||
assert(left >= 0);
|
assert(left >= 0);
|
||||||
@ -215,10 +204,7 @@ uint32_t FFM_InputStream::getData(void *data, uint32_t length)
|
|||||||
|
|
||||||
// If we're returning less than asked for, then we're done
|
// If we're returning less than asked for, then we're done
|
||||||
if(left > 0)
|
if(left > 0)
|
||||||
empty = true;
|
isEof = true;
|
||||||
|
|
||||||
return length - left;
|
return length - left;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFM_InputStream::drop()
|
|
||||||
{ delete this; }
|
|
53
sound/sources/ffmpeg_source.h
Normal file
53
sound/sources/ffmpeg_source.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#ifndef MANGLE_SOUND_FFMPEG_H
|
||||||
|
#define MANGLE_SOUND_FFMPEG_H
|
||||||
|
|
||||||
|
#include "../input.h"
|
||||||
|
#include <exception>
|
||||||
|
#include <vector>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Mangle {
|
||||||
|
namespace Sound {
|
||||||
|
|
||||||
|
class FFMpegSource : public SampleSource
|
||||||
|
{
|
||||||
|
AVFormatContext *FmtCtx;
|
||||||
|
AVCodecContext *CodecCtx;
|
||||||
|
int StreamNum;
|
||||||
|
|
||||||
|
std::vector<uint8_t> storage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Decode the given sound file
|
||||||
|
FFMpegSource(const std::string &file);
|
||||||
|
|
||||||
|
/// Decode the given sound stream (not supported by FFmpeg)
|
||||||
|
FFMpegSource(Stream::Stream *src) { assert(0); }
|
||||||
|
|
||||||
|
~FFMpegSource();
|
||||||
|
|
||||||
|
// Overrides
|
||||||
|
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
|
||||||
|
size_t read(void *data, size_t length);
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "loadertemplate.h"
|
||||||
|
|
||||||
|
/// A factory that loads FFMpegSources from file
|
||||||
|
class FFMpegLoader : public SSL_Template<AudiereSource,false,true>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Sets up the libavcodec library. If you want to do your own
|
||||||
|
/// setup, send a setup=false parameter.
|
||||||
|
FFMpegLoader(bool setup=true);
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespaces
|
||||||
|
#endif
|
26
sound/sources/loadertemplate.h
Normal file
26
sound/sources/loadertemplate.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef SSL_TEMPL_H
|
||||||
|
#define SSL_TEMPL_H
|
||||||
|
|
||||||
|
template <class SourceT, bool stream, bool file>
|
||||||
|
class SSL_Template : public SampleSourceLoader
|
||||||
|
{
|
||||||
|
SSL_Template()
|
||||||
|
{
|
||||||
|
canLoadStream = stream;
|
||||||
|
canLoadFile = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleSource *load(const std::string &file)
|
||||||
|
{
|
||||||
|
assert(canLoadFile);
|
||||||
|
return new SourceT(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleSource *load(Stream::Stream *input)
|
||||||
|
{
|
||||||
|
assert(canLoadStream);
|
||||||
|
return new SourceT(input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
49
sound/sources/memsource.h
Normal file
49
sound/sources/memsource.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef MANGLE_SOUND_MEMSOURCE_H
|
||||||
|
#define MANGLE_SOUND_MEMSOURCE_H
|
||||||
|
|
||||||
|
#include "../source.h"
|
||||||
|
|
||||||
|
namespace Mangle {
|
||||||
|
namespace Sound {
|
||||||
|
|
||||||
|
/// A sample source reading directly from a memory buffer
|
||||||
|
class MemorySource : public SampleSource
|
||||||
|
{
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
size_t pos;
|
||||||
|
|
||||||
|
int32_t rate, channels, bits;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MemorySource(void *_buf, size_t _len, int32_t _rate, int32_t _channels, int32_t _bits)
|
||||||
|
: len(_len), pos(0), rate(_rate), channels(_channels), bits(_bits)
|
||||||
|
{ buf = (char*)_buf; }
|
||||||
|
|
||||||
|
/// Get the sample rate, number of channels, and bits per
|
||||||
|
/// sample. NULL parameters are ignored.
|
||||||
|
void getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) const
|
||||||
|
{
|
||||||
|
if(_rate) *_rate = rate;
|
||||||
|
if(_channels) *_channels = channels;
|
||||||
|
if(_bits) *_bits = bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eof() const { return pos == len; }
|
||||||
|
|
||||||
|
size_t read(void *out, size_t count)
|
||||||
|
{
|
||||||
|
assert(len >= pos);
|
||||||
|
|
||||||
|
if(count > (len-pos))
|
||||||
|
count = len-pos;
|
||||||
|
|
||||||
|
if(count) memcpy(out, buf+pos, count);
|
||||||
|
pos += count;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespaces
|
||||||
|
#endif
|
Loading…
x
Reference in New Issue
Block a user