forked from mirror/openmw-tes3mp
Implemented OpenAL streaming. Fixed bugs in SampleReader and elsewhere.
This commit is contained in:
parent
932465442b
commit
cd4ed4e6bf
6 changed files with 217 additions and 46 deletions
|
@ -11,6 +11,20 @@ using namespace Mangle::Sound;
|
||||||
|
|
||||||
// ---- Helper functions and classes ----
|
// ---- Helper functions and classes ----
|
||||||
|
|
||||||
|
// Static buffer used to shuffle sound data from the input into
|
||||||
|
// OpenAL. The data is only stored temporarily and then immediately
|
||||||
|
// shuffled off to the library. This is not thread safe, but it works
|
||||||
|
// fine with multiple sounds in one thread. It could be made thread
|
||||||
|
// safe simply by using thread local storage.
|
||||||
|
const size_t BSIZE = 32*1024;
|
||||||
|
static char tmp_buffer[BSIZE];
|
||||||
|
|
||||||
|
// Number of buffers used (per sound) for streaming sounds. Each
|
||||||
|
// buffer is of size BSIZE. Increasing this will make streaming sounds
|
||||||
|
// more fault tolerant against temporary lapses in call to update(),
|
||||||
|
// but will also increase memory usage. 4 should be ok.
|
||||||
|
const int STREAM_BUF_NUM = 4;
|
||||||
|
|
||||||
static void fail(const std::string &msg)
|
static void fail(const std::string &msg)
|
||||||
{ throw str_exception("OpenAL exception: " + msg); }
|
{ throw str_exception("OpenAL exception: " + msg); }
|
||||||
|
|
||||||
|
@ -73,10 +87,18 @@ static void getALFormat(SampleSourcePtr inp, int &fmt, int &rate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OpenAL sound output
|
/// OpenAL sound output
|
||||||
class OpenAL_Sound : public Sound
|
class Mangle::Sound::OpenAL_Sound : public Sound
|
||||||
{
|
{
|
||||||
ALuint inst;
|
ALuint inst;
|
||||||
ALuint bufferID;
|
|
||||||
|
// Buffers. Only the first is used for non-streaming sounds.
|
||||||
|
ALuint bufferID[4];
|
||||||
|
|
||||||
|
// Number of buffers used
|
||||||
|
int bufNum;
|
||||||
|
|
||||||
|
// Parameters used for filling buffers
|
||||||
|
int fmt, rate;
|
||||||
|
|
||||||
// Poor mans reference counting. Might improve this later. When
|
// Poor mans reference counting. Might improve this later. When
|
||||||
// NULL, the buffer has not been set up yet.
|
// NULL, the buffer has not been set up yet.
|
||||||
|
@ -87,18 +109,74 @@ class OpenAL_Sound : public Sound
|
||||||
// Input stream
|
// Input stream
|
||||||
SampleSourcePtr input;
|
SampleSourcePtr input;
|
||||||
|
|
||||||
|
OpenAL_Factory *owner;
|
||||||
|
bool ownerAlive;
|
||||||
|
|
||||||
|
// Used for streamed sound list
|
||||||
|
OpenAL_Sound *next, *prev;
|
||||||
|
|
||||||
void setupBuffer();
|
void setupBuffer();
|
||||||
|
|
||||||
|
// Fill data into the given buffer and queue it, if there is any
|
||||||
|
// data left to queue. Assumes the buffer is already unqueued, if
|
||||||
|
// necessary.
|
||||||
|
void queueBuffer(ALuint buf)
|
||||||
|
{
|
||||||
|
// If there is no more data, do nothing
|
||||||
|
if(!input) return;
|
||||||
|
if(input->eof())
|
||||||
|
{
|
||||||
|
input.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get some new data
|
||||||
|
size_t bytes = input->read(tmp_buffer, BSIZE);
|
||||||
|
if(bytes == 0)
|
||||||
|
{
|
||||||
|
input.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move data into the OpenAL buffer
|
||||||
|
alBufferData(buf, fmt, tmp_buffer, bytes, rate);
|
||||||
|
// Queue it
|
||||||
|
alSourceQueueBuffers(inst, 1, &buf);
|
||||||
|
checkALError("Queueing buffer data");
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Read samples from the given input buffer
|
/// Read samples from the given input buffer
|
||||||
OpenAL_Sound(SampleSourcePtr input);
|
OpenAL_Sound(SampleSourcePtr input, OpenAL_Factory *fact);
|
||||||
|
|
||||||
/// Play an existing buffer, with a given ref counter. Used
|
/// Play an existing buffer, with a given ref counter. Used
|
||||||
/// internally for cloning.
|
/// internally for cloning.
|
||||||
OpenAL_Sound(ALuint buf, int *ref);
|
OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact);
|
||||||
|
|
||||||
~OpenAL_Sound();
|
~OpenAL_Sound();
|
||||||
|
|
||||||
|
// Must be called regularly on streamed sounds
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
if(!streaming) return;
|
||||||
|
if(!input) return;
|
||||||
|
|
||||||
|
// Get the number of processed buffers
|
||||||
|
ALint count;
|
||||||
|
alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count);
|
||||||
|
checkALError("getting number of unprocessed buffers");
|
||||||
|
|
||||||
|
for(int i=0; i<count; i++)
|
||||||
|
{
|
||||||
|
ALuint buf;
|
||||||
|
// Unqueue one of the processed buffer
|
||||||
|
alSourceUnqueueBuffers(inst, 1, &buf);
|
||||||
|
|
||||||
|
// Then reload it with data (if any) and queue it up
|
||||||
|
queueBuffer(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void play();
|
void play();
|
||||||
void stop();
|
void stop();
|
||||||
void pause();
|
void pause();
|
||||||
|
@ -107,7 +185,14 @@ class OpenAL_Sound : public Sound
|
||||||
void setPos(float x, float y, float z);
|
void setPos(float x, float y, float z);
|
||||||
void setPitch(float);
|
void setPitch(float);
|
||||||
void setRepeat(bool);
|
void setRepeat(bool);
|
||||||
void setStreaming(bool s) { streaming = s; }
|
|
||||||
|
void notifyOwnerDeath()
|
||||||
|
{ ownerAlive = false; }
|
||||||
|
|
||||||
|
// We can enable streaming, but never disable it.
|
||||||
|
void setStreaming(bool s)
|
||||||
|
{ if(s) streaming = true; }
|
||||||
|
|
||||||
SoundPtr clone();
|
SoundPtr clone();
|
||||||
|
|
||||||
// a = AL_REFERENCE_DISTANCE
|
// a = AL_REFERENCE_DISTANCE
|
||||||
|
@ -123,11 +208,7 @@ class OpenAL_Sound : public Sound
|
||||||
|
|
||||||
SoundPtr OpenAL_Factory::loadRaw(SampleSourcePtr input)
|
SoundPtr OpenAL_Factory::loadRaw(SampleSourcePtr input)
|
||||||
{
|
{
|
||||||
return SoundPtr(new OpenAL_Sound(input));
|
return SoundPtr(new OpenAL_Sound(input, this));
|
||||||
}
|
|
||||||
|
|
||||||
void OpenAL_Factory::update()
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenAL_Factory::setListenerPos(float x, float y, float z,
|
void OpenAL_Factory::setListenerPos(float x, float y, float z,
|
||||||
|
@ -173,8 +254,33 @@ OpenAL_Factory::OpenAL_Factory(bool doSetup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenAL_Factory::update()
|
||||||
|
{
|
||||||
|
// Loop through all streaming sounds and update them
|
||||||
|
StreamList::iterator it = streaming.begin();
|
||||||
|
for(;it != streaming.end(); it++)
|
||||||
|
(*it)->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL_Factory::notifyStreaming(OpenAL_Sound *snd)
|
||||||
|
{
|
||||||
|
// Add the sound to the streaming list
|
||||||
|
streaming.push_back(snd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenAL_Factory::notifyDelete(OpenAL_Sound *snd)
|
||||||
|
{
|
||||||
|
// Remove the sound from the stream list
|
||||||
|
streaming.remove(snd);
|
||||||
|
}
|
||||||
|
|
||||||
OpenAL_Factory::~OpenAL_Factory()
|
OpenAL_Factory::~OpenAL_Factory()
|
||||||
{
|
{
|
||||||
|
// Notify remaining streamed sounds that we're dying
|
||||||
|
StreamList::iterator it = streaming.begin();
|
||||||
|
for(;it != streaming.end(); it++)
|
||||||
|
(*it)->notifyOwnerDeath();
|
||||||
|
|
||||||
// Deinitialize sound system
|
// Deinitialize sound system
|
||||||
if(didSetup)
|
if(didSetup)
|
||||||
{
|
{
|
||||||
|
@ -249,27 +355,31 @@ SoundPtr OpenAL_Sound::clone()
|
||||||
{
|
{
|
||||||
setupBuffer();
|
setupBuffer();
|
||||||
assert(!streaming && "cloning streamed sounds not supported");
|
assert(!streaming && "cloning streamed sounds not supported");
|
||||||
return SoundPtr(new OpenAL_Sound(bufferID, refCnt));
|
return SoundPtr(new OpenAL_Sound(bufferID[0], refCnt, owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor used for cloned sounds
|
// Constructor used for cloned sounds
|
||||||
OpenAL_Sound::OpenAL_Sound(ALuint buf, int *ref)
|
OpenAL_Sound::OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact)
|
||||||
: bufferID(buf), refCnt(ref), streaming(false)
|
: refCnt(ref), streaming(false), owner(fact), ownerAlive(false)
|
||||||
{
|
{
|
||||||
// Increase the reference count
|
// Increase the reference count
|
||||||
assert(ref != NULL);
|
assert(ref != NULL);
|
||||||
*refCnt++;
|
*refCnt++;
|
||||||
|
|
||||||
|
// Set up buffer
|
||||||
|
bufferID[0] = buf;
|
||||||
|
bufNum = 1;
|
||||||
|
|
||||||
// Create a source
|
// Create a source
|
||||||
alGenSources(1, &inst);
|
alGenSources(1, &inst);
|
||||||
checkALError("creating instance (clone)");
|
checkALError("creating instance (clone)");
|
||||||
alSourcei(inst, AL_BUFFER, bufferID);
|
alSourcei(inst, AL_BUFFER, bufferID[0]);
|
||||||
checkALError("assigning buffer (clone)");
|
checkALError("assigning buffer (clone)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor used for original (non-cloned) sounds
|
// Constructor used for original (non-cloned) sounds
|
||||||
OpenAL_Sound::OpenAL_Sound(SampleSourcePtr _input)
|
OpenAL_Sound::OpenAL_Sound(SampleSourcePtr _input, OpenAL_Factory *fact)
|
||||||
: bufferID(0), refCnt(NULL), streaming(false), input(_input)
|
: refCnt(NULL), streaming(false), input(_input), owner(fact), ownerAlive(false)
|
||||||
{
|
{
|
||||||
// Create a source
|
// Create a source
|
||||||
alGenSources(1, &inst);
|
alGenSources(1, &inst);
|
||||||
|
@ -287,46 +397,60 @@ void OpenAL_Sound::setupBuffer()
|
||||||
assert(input);
|
assert(input);
|
||||||
|
|
||||||
// Get the format
|
// Get the format
|
||||||
int fmt, rate;
|
|
||||||
getALFormat(input, fmt, rate);
|
getALFormat(input, fmt, rate);
|
||||||
|
|
||||||
|
// Create a cheap reference counter for the buffer
|
||||||
|
refCnt = new int;
|
||||||
|
*refCnt = 1;
|
||||||
|
|
||||||
|
if(streaming) bufNum = STREAM_BUF_NUM;
|
||||||
|
else bufNum = 1;
|
||||||
|
|
||||||
|
// Set up the OpenAL buffer(s)
|
||||||
|
alGenBuffers(bufNum, bufferID);
|
||||||
|
checkALError("generating buffer(s)");
|
||||||
|
assert(bufferID[0] != 0);
|
||||||
|
|
||||||
|
// STREAMING.
|
||||||
if(streaming)
|
if(streaming)
|
||||||
{
|
{
|
||||||
// To be done
|
// Just queue all the buffers with data and exit. queueBuffer()
|
||||||
|
// will work correctly also in the case where there is not
|
||||||
|
// enough data to fill all the buffers.
|
||||||
|
for(int i=0; i<bufNum; i++)
|
||||||
|
queueBuffer(bufferID[i]);
|
||||||
|
|
||||||
|
// Notify the manager what we're doing
|
||||||
|
owner->notifyStreaming(this);
|
||||||
|
ownerAlive = true;
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NON-STREAMING
|
// NON-STREAMING. We have to load all the data and shove it into the
|
||||||
|
// buffer.
|
||||||
// Set up the OpenAL buffer
|
|
||||||
alGenBuffers(1, &bufferID);
|
|
||||||
checkALError("generating buffer");
|
|
||||||
assert(bufferID != 0);
|
|
||||||
|
|
||||||
// Does the stream support pointer operations?
|
// Does the stream support pointer operations?
|
||||||
if(input->hasPtr)
|
if(input->hasPtr)
|
||||||
{
|
{
|
||||||
// If so, we can read the data directly from the stream
|
// If so, we can read the data directly from the stream
|
||||||
alBufferData(bufferID, fmt, input->getPtr(), input->size(), rate);
|
alBufferData(bufferID[0], fmt, input->getPtr(), input->size(), rate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Read the entire stream into a temporary buffer first
|
// Read the entire stream into a temporary buffer first
|
||||||
Mangle::Stream::BufferStream buf(input, streaming?1024*1024:32*1024);
|
Mangle::Stream::BufferStream buf(input, 128*1024);
|
||||||
|
|
||||||
// Then copy that into OpenAL
|
// Then copy that into OpenAL
|
||||||
alBufferData(bufferID, fmt, buf.getPtr(), buf.size(), rate);
|
alBufferData(bufferID[0], fmt, buf.getPtr(), buf.size(), rate);
|
||||||
}
|
}
|
||||||
checkALError("loading sound buffer");
|
checkALError("loading sound data");
|
||||||
|
|
||||||
// We're done with the input stream, release the pointer
|
// We're done with the input stream, release the pointer
|
||||||
input.reset();
|
input.reset();
|
||||||
|
|
||||||
alSourcei(inst, AL_BUFFER, bufferID);
|
alSourcei(inst, AL_BUFFER, bufferID[0]);
|
||||||
checkALError("assigning buffer");
|
checkALError("assigning buffer");
|
||||||
|
|
||||||
// Create a cheap reference counter for the buffer
|
|
||||||
refCnt = new int;
|
|
||||||
*refCnt = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenAL_Sound::~OpenAL_Sound()
|
OpenAL_Sound::~OpenAL_Sound()
|
||||||
|
@ -337,12 +461,19 @@ OpenAL_Sound::~OpenAL_Sound()
|
||||||
// Return sound
|
// Return sound
|
||||||
alDeleteSources(1, &inst);
|
alDeleteSources(1, &inst);
|
||||||
|
|
||||||
|
// Notify the factory that we quit. You will hear from our union
|
||||||
|
// rep. The bool check is to handle cases where the manager goes out
|
||||||
|
// of scope before the sounds do. In that case, don't try to contact
|
||||||
|
// the factory.
|
||||||
|
if(ownerAlive)
|
||||||
|
owner->notifyDelete(this);
|
||||||
|
|
||||||
// Decrease the reference counter
|
// Decrease the reference counter
|
||||||
if((-- *refCnt) == 0)
|
if((-- *refCnt) == 0)
|
||||||
{
|
{
|
||||||
// We're the last owner. Delete the buffer and the counter
|
// We're the last owner. Delete the buffer(s) and the counter
|
||||||
// itself.
|
// itself.
|
||||||
alDeleteBuffers(1, &bufferID);
|
alDeleteBuffers(bufNum, bufferID);
|
||||||
checkALError("deleting buffer");
|
checkALError("deleting buffer");
|
||||||
delete refCnt;
|
delete refCnt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,27 @@
|
||||||
#define MANGLE_SOUND_OPENAL_OUT_H
|
#define MANGLE_SOUND_OPENAL_OUT_H
|
||||||
|
|
||||||
#include "../output.hpp"
|
#include "../output.hpp"
|
||||||
|
#include <list>
|
||||||
|
|
||||||
namespace Mangle {
|
namespace Mangle {
|
||||||
namespace Sound {
|
namespace Sound {
|
||||||
|
|
||||||
|
class OpenAL_Sound;
|
||||||
|
|
||||||
class OpenAL_Factory : public SoundFactory
|
class OpenAL_Factory : public SoundFactory
|
||||||
{
|
{
|
||||||
void *device;
|
void *device;
|
||||||
void *context;
|
void *context;
|
||||||
bool didSetup;
|
bool didSetup;
|
||||||
|
|
||||||
|
// List of streaming sounds that need to be updated every frame.
|
||||||
|
typedef std::list<OpenAL_Sound*> StreamList;
|
||||||
|
StreamList streaming;
|
||||||
|
|
||||||
|
friend class OpenAL_Sound;
|
||||||
|
void notifyStreaming(OpenAL_Sound*);
|
||||||
|
void notifyDelete(OpenAL_Sound*);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Initialize object. Pass true (default) if you want the
|
/// Initialize object. Pass true (default) if you want the
|
||||||
/// constructor to set up the current ALCdevice and ALCcontext for
|
/// constructor to set up the current ALCdevice and ALCcontext for
|
||||||
|
|
|
@ -19,9 +19,9 @@ void SndFileSource::getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits)
|
||||||
|
|
||||||
size_t SndFileSource::readSamples(void *data, size_t length)
|
size_t SndFileSource::readSamples(void *data, size_t length)
|
||||||
{
|
{
|
||||||
// Read frames. We count channels as part of the frame. Even though
|
// Read frames. We count channels as part of the frame, even though
|
||||||
// libsndfile does not, since it still requires the number of frames
|
// libsndfile does not. This is because the library still requires
|
||||||
// read to be a multiple of channels.
|
// the number of frames read to be a multiple of channels.
|
||||||
return channels*sf_read_short((SNDFILE*)handle, (short*)data, length*channels);
|
return channels*sf_read_short((SNDFILE*)handle, (short*)data, length*channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,15 +22,36 @@ size_t SampleReader::read(void *_data, size_t length)
|
||||||
if(isEof) return 0;
|
if(isEof) return 0;
|
||||||
char *data = (char*)_data;
|
char *data = (char*)_data;
|
||||||
|
|
||||||
// Move the remains from the last operation first
|
// Pullsize holds the number of bytes that were copied "extra" at
|
||||||
|
// the end of LAST round. If non-zero, it also means there is data
|
||||||
|
// left in the pullOver buffer.
|
||||||
if(pullSize)
|
if(pullSize)
|
||||||
{
|
{
|
||||||
// pullSize is how much was stored the last time. The data is
|
// Amount of data left
|
||||||
// stored at the end of the buffer.
|
size_t doRead = frameSize - pullSize;
|
||||||
memcpy(data, pullOver+(frameSize-pullSize), pullSize);
|
assert(doRead > 0);
|
||||||
length -= pullSize;
|
|
||||||
data += pullSize;
|
// Make sure we don't read more than we're supposed to
|
||||||
pullSize = 0;
|
if(doRead > length) doRead = length;
|
||||||
|
|
||||||
|
memcpy(data, pullOver+pullSize, doRead);
|
||||||
|
|
||||||
|
// Update the number of bytes now copied
|
||||||
|
pullSize += doRead;
|
||||||
|
assert(pullSize <= frameSize);
|
||||||
|
|
||||||
|
if(pullSize < frameSize)
|
||||||
|
{
|
||||||
|
// There is STILL data left in the pull buffer, and we've
|
||||||
|
// done everything we were supposed to. Leave it and return.
|
||||||
|
assert(doRead == length);
|
||||||
|
return doRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up variables for further reading below. No need to update
|
||||||
|
// pullSize, it is overwritten anyway.
|
||||||
|
length -= doRead;
|
||||||
|
data += doRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of whole frames
|
// Number of whole frames
|
||||||
|
@ -38,6 +59,7 @@ size_t SampleReader::read(void *_data, size_t length)
|
||||||
|
|
||||||
// Read the data
|
// Read the data
|
||||||
size_t res = readSamples(data, frames);
|
size_t res = readSamples(data, frames);
|
||||||
|
assert(res <= frames);
|
||||||
|
|
||||||
// Total bytes read
|
// Total bytes read
|
||||||
size_t num = res*frameSize;
|
size_t num = res*frameSize;
|
||||||
|
@ -53,12 +75,14 @@ size_t SampleReader::read(void *_data, size_t length)
|
||||||
|
|
||||||
// Determine the overshoot
|
// Determine the overshoot
|
||||||
pullSize = length - num;
|
pullSize = length - num;
|
||||||
|
assert(pullSize < frameSize && pullSize >= 0);
|
||||||
|
|
||||||
// Are we missing data?
|
// Are we missing data?
|
||||||
if(pullSize)
|
if(pullSize)
|
||||||
{
|
{
|
||||||
// Fill in one sample
|
// Fill in one sample
|
||||||
res = readSamples(pullOver,1);
|
res = readSamples(pullOver,1);
|
||||||
|
assert(res == 1 || res == 0);
|
||||||
if(res)
|
if(res)
|
||||||
{
|
{
|
||||||
// Move as much as we can into the output buffer
|
// Move as much as we can into the output buffer
|
||||||
|
|
|
@ -39,11 +39,16 @@ int main()
|
||||||
snd->setVolume(0.8);
|
snd->setVolume(0.8);
|
||||||
snd->setPitch(0.9);
|
snd->setPitch(0.9);
|
||||||
|
|
||||||
|
// Also test streaming, since all the other examples test
|
||||||
|
// non-streaming sounds.
|
||||||
|
snd->setStreaming(true);
|
||||||
|
|
||||||
snd->play();
|
snd->play();
|
||||||
|
|
||||||
while(snd->isPlaying())
|
while(snd->isPlaying())
|
||||||
{
|
{
|
||||||
usleep(10000);
|
usleep(10000);
|
||||||
|
mg.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(exception &e)
|
catch(exception &e)
|
||||||
|
|
|
@ -29,7 +29,7 @@ class StdStream : public Stream
|
||||||
size_t read(void* buf, size_t len)
|
size_t read(void* buf, size_t len)
|
||||||
{
|
{
|
||||||
inf->read((char*)buf, len);
|
inf->read((char*)buf, len);
|
||||||
if(inf->fail())
|
if(inf->bad())
|
||||||
fail("error reading from stream");
|
fail("error reading from stream");
|
||||||
return inf->gcount();
|
return inf->gcount();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue