get stereo audo working on linux

This commit is contained in:
Eduardo Bart 2012-04-14 10:15:51 -03:00
parent ae67c6adbc
commit 8e679f2da7
11 changed files with 311 additions and 47 deletions

View File

@ -203,6 +203,7 @@ SET(framework_SOURCES ${framework_SOURCES}
${CMAKE_CURRENT_LIST_DIR}/sound/soundmanager.cpp ${CMAKE_CURRENT_LIST_DIR}/sound/soundmanager.cpp
${CMAKE_CURRENT_LIST_DIR}/sound/oggsoundfile.cpp ${CMAKE_CURRENT_LIST_DIR}/sound/oggsoundfile.cpp
${CMAKE_CURRENT_LIST_DIR}/sound/streamsoundsource.cpp ${CMAKE_CURRENT_LIST_DIR}/sound/streamsoundsource.cpp
${CMAKE_CURRENT_LIST_DIR}/sound/combinedsoundsource.cpp
# framework otml # framework otml
${CMAKE_CURRENT_LIST_DIR}/otml/otmldocument.cpp ${CMAKE_CURRENT_LIST_DIR}/otml/otmldocument.cpp

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2010-2012 OTClient <https://github.com/edubart/otclient>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "combinedsoundsource.h"
CombinedSoundSource::CombinedSoundSource() : SoundSource(0)
{
}
void CombinedSoundSource::addSource(const SoundSourcePtr& source)
{
m_sources.push_back(source);
}
void CombinedSoundSource::play()
{
for(const SoundSourcePtr& source : m_sources)
source->play();
}
void CombinedSoundSource::stop()
{
for(const SoundSourcePtr& source : m_sources)
source->stop();
}
bool CombinedSoundSource::isPlaying()
{
for(const SoundSourcePtr& source : m_sources) {
if(source->isPlaying())
return true;
}
return false;
}
void CombinedSoundSource::setLooping(bool looping)
{
for(const SoundSourcePtr& source : m_sources)
source->setLooping(looping);
}
void CombinedSoundSource::setRelative(bool relative)
{
for(const SoundSourcePtr& source : m_sources)
source->setRelative(relative);
}
void CombinedSoundSource::setReferenceDistance(float distance)
{
for(const SoundSourcePtr& source : m_sources)
source->setReferenceDistance(distance);
}
void CombinedSoundSource::setGain(float gain)
{
for(const SoundSourcePtr& source : m_sources)
source->setGain(gain);
}
void CombinedSoundSource::setPitch(float pitch)
{
for(const SoundSourcePtr& source : m_sources)
source->setPitch(pitch);
}
void CombinedSoundSource::setPosition(const Point& pos)
{
for(const SoundSourcePtr& source : m_sources)
source->setPosition(pos);
}
void CombinedSoundSource::setVelocity(const Point& velocity)
{
for(const SoundSourcePtr& source : m_sources)
source->setVelocity(velocity);
}
void CombinedSoundSource::update()
{
for(const SoundSourcePtr& source : m_sources)
source->update();
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2010-2012 OTClient <https://github.com/edubart/otclient>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef COMBINEDSOUNDSOURCE_H
#define COMBINEDSOUNDSOURCE_H
#include "soundsource.h"
class CombinedSoundSource : public SoundSource
{
public:
CombinedSoundSource();
void addSource(const SoundSourcePtr& source);
std::vector<SoundSourcePtr> getSources() { return m_sources; }
void play();
void stop();
bool isPlaying();
void setLooping(bool looping);
void setRelative(bool relative);
void setReferenceDistance(float distance);
void setGain(float gain);
void setPitch(float pitch);
void setPosition(const Point& pos);
void setVelocity(const Point& velocity);
protected:
virtual void update();
private:
std::vector<SoundSourcePtr> m_sources;
};
#endif

View File

@ -35,12 +35,14 @@ class SoundSource;
class SoundBuffer; class SoundBuffer;
class SoundFile; class SoundFile;
class StreamSoundSource; class StreamSoundSource;
class CombinedSoundSource;
class OggSoundFile; class OggSoundFile;
typedef std::shared_ptr<SoundSource> SoundSourcePtr; typedef std::shared_ptr<SoundSource> SoundSourcePtr;
typedef std::shared_ptr<SoundFile> SoundFilePtr; typedef std::shared_ptr<SoundFile> SoundFilePtr;
typedef std::shared_ptr<SoundBuffer> SoundBufferPtr; typedef std::shared_ptr<SoundBuffer> SoundBufferPtr;
typedef std::shared_ptr<StreamSoundSource> StreamSoundSourcePtr; typedef std::shared_ptr<StreamSoundSource> StreamSoundSourcePtr;
typedef std::shared_ptr<CombinedSoundSource> CombinedSoundSourcePtr;
typedef std::shared_ptr<OggSoundFile> OggSoundFilePtr; typedef std::shared_ptr<OggSoundFile> OggSoundFilePtr;
#endif #endif

View File

@ -23,8 +23,6 @@
#include "soundbuffer.h" #include "soundbuffer.h"
#include "soundfile.h" #include "soundfile.h"
#include <framework/util/databuffer.h>
SoundBuffer::SoundBuffer() SoundBuffer::SoundBuffer()
{ {
m_bufferId = 0; m_bufferId = 0;
@ -38,7 +36,7 @@ SoundBuffer::~SoundBuffer()
assert(alGetError() == AL_NO_ERROR); assert(alGetError() == AL_NO_ERROR);
} }
bool SoundBuffer::loadSoundFile(const SoundFilePtr& soundFile) bool SoundBuffer::fillBuffer(const SoundFilePtr& soundFile)
{ {
ALenum format = soundFile->getSampleFormat(); ALenum format = soundFile->getSampleFormat();
if(format == AL_UNDETERMINED) { if(format == AL_UNDETERMINED) {
@ -53,12 +51,16 @@ bool SoundBuffer::loadSoundFile(const SoundFilePtr& soundFile)
return false; return false;
} }
alBufferData(m_bufferId, format, &samples[0], soundFile->getSize(), soundFile->getRate()); return fillBuffer(format, samples, samples.size(), soundFile->getRate());
}
bool SoundBuffer::fillBuffer(ALenum sampleFormat, const DataBuffer<char>& data, int size, int rate)
{
alBufferData(m_bufferId, sampleFormat, &data[0], size, rate);
ALenum err = alGetError(); ALenum err = alGetError();
if(err != AL_NO_ERROR) { if(err != AL_NO_ERROR) {
logError("unable to fill audio buffer data for '", soundFile->getName(), "': ", alGetString(err)); logError("unable to fill audio buffer data: ", alGetString(err));
return false; return false;
} }
return true; return true;
} }

View File

@ -25,15 +25,18 @@
#include "declarations.h" #include "declarations.h"
#include <framework/util/databuffer.h>
class SoundBuffer class SoundBuffer
{ {
public: public:
SoundBuffer(); SoundBuffer();
~SoundBuffer(); ~SoundBuffer();
bool loadSoundFile(const SoundFilePtr& soundFile); bool fillBuffer(const SoundFilePtr& soundFile);
bool fillBuffer(ALenum sampleFormat, const DataBuffer<char>& data, int size, int rate);
int getBufferId() { return m_bufferId; } ALuint getBufferId() { return m_bufferId; }
private: private:
ALuint m_bufferId; ALuint m_bufferId;

View File

@ -25,6 +25,7 @@
#include "soundbuffer.h" #include "soundbuffer.h"
#include "soundfile.h" #include "soundfile.h"
#include "streamsoundsource.h" #include "streamsoundsource.h"
#include "combinedsoundsource.h"
#include <framework/core/clock.h> #include <framework/core/clock.h>
#include <framework/core/eventdispatcher.h> #include <framework/core/eventdispatcher.h>
@ -124,7 +125,7 @@ void SoundManager::preload(const std::string& filename)
return; return;
SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer); SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer);
if(buffer->loadSoundFile(soundFile)) if(buffer->fillBuffer(soundFile))
m_buffers[filename] = buffer; m_buffers[filename] = buffer;
} }
@ -186,30 +187,53 @@ void SoundManager::stopMusic(float fadetime)
SoundSourcePtr SoundManager::createSoundSource(const std::string& filename) SoundSourcePtr SoundManager::createSoundSource(const std::string& filename)
{ {
SoundSourcePtr soundSource; SoundSourcePtr source;
auto it = m_buffers.find(filename); auto it = m_buffers.find(filename);
if(it != m_buffers.end()) { if(it != m_buffers.end()) {
soundSource = SoundSourcePtr(new SoundSource); source = SoundSourcePtr(new SoundSource);
soundSource->setBuffer(it->second); source->setBuffer(it->second);
} else { } else {
SoundFilePtr soundFile = SoundFile::loadSoundFile(filename); SoundFilePtr soundFile = SoundFile::loadSoundFile(filename);
if(!soundFile) if(!soundFile)
return nullptr; return nullptr;
if(soundFile->getSize() <= MAX_CACHE_SIZE) { if(soundFile->getSize() <= MAX_CACHE_SIZE) {
soundSource = SoundSourcePtr(new SoundSource); source = SoundSourcePtr(new SoundSource);
SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer); SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer);
buffer->loadSoundFile(soundFile); buffer->fillBuffer(soundFile);
soundSource->setBuffer(buffer); source->setBuffer(buffer);
m_buffers[filename] = buffer; m_buffers[filename] = buffer;
logWarning("uncached sound '", filename, "' requested to be played"); logWarning("uncached sound '", filename, "' requested to be played");
} else { } else {
StreamSoundSourcePtr streamSoundSource(new StreamSoundSource); StreamSoundSourcePtr streamSource(new StreamSoundSource);
streamSoundSource->setSoundFile(soundFile); streamSource->setSoundFile(soundFile);
soundSource = streamSoundSource; source = streamSource;
#ifdef __linux
// due to OpenAL implementation bug stereo buffers are always downmixed to mono on linux systems
// this is hack to work around the issue
// solution taken from http://opensource.creative.com/pipermail/openal/2007-April/010355.html
if(soundFile->getSampleFormat() == AL_FORMAT_STEREO16) {
CombinedSoundSourcePtr combinedSource(new CombinedSoundSource);
streamSource->downMix(StreamSoundSource::DownMixLeft);
streamSource->setRelative(true);
streamSource->setPosition(Point(-128, 0));
combinedSource->addSource(streamSource);
streamSource = StreamSoundSourcePtr(new StreamSoundSource);
streamSource->setSoundFile(SoundFile::loadSoundFile(filename));
streamSource->downMix(StreamSoundSource::DownMixRight);
streamSource->setRelative(true);
streamSource->setPosition(Point(128,0));
combinedSource->addSource(streamSource);
source = combinedSource;
}
#endif
} }
} }
return soundSource; return source;
} }

View File

@ -33,9 +33,11 @@ SoundSource::SoundSource()
SoundSource::~SoundSource() SoundSource::~SoundSource()
{ {
stop(); if(m_sourceId != 0) {
alDeleteSources(1, &m_sourceId); stop();
assert(alGetError() == AL_NO_ERROR); alDeleteSources(1, &m_sourceId);
assert(alGetError() == AL_NO_ERROR);
}
} }
void SoundSource::play() void SoundSource::play()
@ -93,3 +95,13 @@ void SoundSource::setPitch(float pitch)
{ {
alSourcef(m_sourceId, AL_PITCH, pitch); alSourcef(m_sourceId, AL_PITCH, pitch);
} }
void SoundSource::setPosition(const Point& pos)
{
alSource3f(m_sourceId, AL_POSITION, pos.x, pos.y, 0);
}
void SoundSource::setVelocity(const Point& velocity)
{
alSource3f(m_sourceId, AL_VELOCITY, velocity.x, velocity.y, 0);
}

View File

@ -27,27 +27,31 @@
class SoundSource class SoundSource
{ {
protected:
SoundSource(ALuint sourceId) : m_sourceId(sourceId) { }
public: public:
SoundSource(); SoundSource();
virtual ~SoundSource(); virtual ~SoundSource();
void play(); virtual void play();
void stop(); virtual void stop();
virtual bool isPlaying();
bool isPlaying();
void setBuffer(const SoundBufferPtr& buffer);
virtual void setLooping(bool looping); virtual void setLooping(bool looping);
void setRelative(bool relative); virtual void setRelative(bool relative);
void setReferenceDistance(float distance); virtual void setReferenceDistance(float distance);
void setGain(float gain); virtual void setGain(float gain);
void setPitch(float pitch); virtual void setPitch(float pitch);
virtual void setPosition(const Point& pos);
// TODO: velocity, position virtual void setVelocity(const Point& velocity);
protected: protected:
void setBuffer(const SoundBufferPtr& buffer);
virtual void update() { } virtual void update() { }
friend class SoundManager; friend class SoundManager;
friend class CombinedSoundSource;
ALuint m_sourceId; ALuint m_sourceId;
SoundBufferPtr m_buffer; SoundBufferPtr m_buffer;

View File

@ -32,25 +32,39 @@ StreamSoundSource::StreamSoundSource()
for(auto& buffer : m_buffers) for(auto& buffer : m_buffers)
buffer = SoundBufferPtr(new SoundBuffer); buffer = SoundBufferPtr(new SoundBuffer);
m_fadeState = NoFading; m_fadeState = NoFading;
m_downMix = NoDownMix;
} }
StreamSoundSource::~StreamSoundSource() StreamSoundSource::~StreamSoundSource()
{ {
stop(); stop();
ALint queued;
alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued);
for(int i = 0; i < queued; ++i) {
ALuint buffer;
alSourceUnqueueBuffers(m_sourceId, 1, &buffer);
}
m_buffers.fill(nullptr);
} }
void StreamSoundSource::setSoundFile(const SoundFilePtr& soundFile) void StreamSoundSource::setSoundFile(const SoundFilePtr& soundFile)
{ {
m_soundFile = soundFile; m_soundFile = soundFile;
}
void StreamSoundSource::play()
{
if(!m_soundFile) {
logError("there is not sound file to play the stream");
return;
}
queueBuffers();
SoundSource::play();
}
void StreamSoundSource::stop()
{
SoundSource::stop();
unqueueBuffers();
}
void StreamSoundSource::queueBuffers()
{
ALint queued; ALint queued;
alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued); alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued);
for(int i = 0; i < STREAM_FRAGMENTS - queued; ++i) { for(int i = 0; i < STREAM_FRAGMENTS - queued; ++i) {
@ -59,6 +73,16 @@ void StreamSoundSource::setSoundFile(const SoundFilePtr& soundFile)
} }
} }
void StreamSoundSource::unqueueBuffers()
{
ALint queued;
alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued);
for(int i = 0; i < queued; ++i) {
ALuint buffer;
alSourceUnqueueBuffers(m_sourceId, 1, &buffer);
}
}
void StreamSoundSource::update() void StreamSoundSource::update()
{ {
ALint processed = 0; ALint processed = 0;
@ -76,9 +100,8 @@ void StreamSoundSource::update()
if(processed == 0 || !m_looping) if(processed == 0 || !m_looping)
return; return;
// we might have to restart the source if we had a buffer underrun logTraceError("restarting audio source because of buffer underrun");
//log_info << "Restarting audio source because of buffer underrun" << std::endl; play();
//play();
} }
float realTime = g_clock.asyncTime(); float realTime = g_clock.asyncTime();
@ -104,7 +127,8 @@ void StreamSoundSource::update()
bool StreamSoundSource::fillBufferAndQueue(ALuint buffer) bool StreamSoundSource::fillBufferAndQueue(ALuint buffer)
{ {
// fill buffer // fill buffer
DataBuffer<char> bufferData(STREAM_FRAGMENT_SIZE); static DataBuffer<char> bufferData(STREAM_FRAGMENT_SIZE);
ALenum format = m_soundFile->getSampleFormat();
int bytesRead = 0; int bytesRead = 0;
do { do {
@ -119,8 +143,21 @@ bool StreamSoundSource::fillBufferAndQueue(ALuint buffer)
} }
} while(bytesRead < STREAM_FRAGMENT_SIZE); } while(bytesRead < STREAM_FRAGMENT_SIZE);
bool done = bytesRead >= STREAM_FRAGMENT_SIZE;
if(m_downMix != NoDownMix) {
if(format == AL_FORMAT_STEREO16) {
if(bytesRead > 0) {
uint16_t *data = (uint16_t*)bufferData.data();
bytesRead /= 2;
for(int i=0;i<bytesRead;i++)
data[i] = data[2*i + (m_downMix == DownMixLeft ? 0 : 1)];
}
format = AL_FORMAT_MONO16;
}
}
if(bytesRead > 0) { if(bytesRead > 0) {
ALenum format = m_soundFile->getSampleFormat();
alBufferData(buffer, format, &bufferData[0], bytesRead, m_soundFile->getRate()); alBufferData(buffer, format, &bufferData[0], bytesRead, m_soundFile->getRate());
ALenum err = alGetError(); ALenum err = alGetError();
if(err != AL_NO_ERROR) if(err != AL_NO_ERROR)
@ -133,7 +170,22 @@ bool StreamSoundSource::fillBufferAndQueue(ALuint buffer)
} }
// return false if there aren't more buffers to fill // return false if there aren't more buffers to fill
return bytesRead >= STREAM_FRAGMENT_SIZE; return done;
}
void StreamSoundSource::downMix(StreamSoundSource::DownMix downMix)
{
if(!m_soundFile) {
logError("down mix must be set after setting a sound file");
return;
}
if(m_soundFile->getSampleFormat() != AL_FORMAT_STEREO16) {
logError("can only downmix 16 bit stereo audio files");
return;
}
m_downMix = downMix;
} }
void StreamSoundSource::setFading(FadeState state, float fadeTime) void StreamSoundSource::setFading(FadeState state, float fadeTime)

View File

@ -34,23 +34,31 @@ class StreamSoundSource : public SoundSource
}; };
public: public:
enum DownMix { NoDownMix, DownMixLeft, DownMixRight };
enum FadeState { NoFading, FadingOn, FadingOff }; enum FadeState { NoFading, FadingOn, FadingOff };
StreamSoundSource(); StreamSoundSource();
virtual ~StreamSoundSource(); virtual ~StreamSoundSource();
void play();
void stop();
void setSoundFile(const SoundFilePtr& soundFile); void setSoundFile(const SoundFilePtr& soundFile);
void downMix(DownMix downMix);
void setFading(FadeState state, float fadetime); void setFading(FadeState state, float fadetime);
FadeState getFadeState() { return m_fadeState; } FadeState getFadeState() { return m_fadeState; }
void update(); void update();
private: private:
void queueBuffers();
void unqueueBuffers();
bool fillBufferAndQueue(ALuint buffer); bool fillBufferAndQueue(ALuint buffer);
SoundFilePtr m_soundFile; SoundFilePtr m_soundFile;
std::array<SoundBufferPtr,STREAM_FRAGMENTS> m_buffers; std::array<SoundBufferPtr,STREAM_FRAGMENTS> m_buffers;
DownMix m_downMix;
FadeState m_fadeState; FadeState m_fadeState;
float m_fadeStartTime; float m_fadeStartTime;
float m_fadeTime; float m_fadeTime;