From 8e679f2da722852274c8093f711a451021b5c362 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Sat, 14 Apr 2012 10:15:51 -0300 Subject: [PATCH] get stereo audo working on linux --- src/framework/CMakeLists.txt | 1 + src/framework/sound/combinedsoundsource.cpp | 101 ++++++++++++++++++++ src/framework/sound/combinedsoundsource.h | 55 +++++++++++ src/framework/sound/declarations.h | 2 + src/framework/sound/soundbuffer.cpp | 14 +-- src/framework/sound/soundbuffer.h | 7 +- src/framework/sound/soundmanager.cpp | 46 ++++++--- src/framework/sound/soundsource.cpp | 18 +++- src/framework/sound/soundsource.h | 26 ++--- src/framework/sound/streamsoundsource.cpp | 80 +++++++++++++--- src/framework/sound/streamsoundsource.h | 8 ++ 11 files changed, 311 insertions(+), 47 deletions(-) create mode 100644 src/framework/sound/combinedsoundsource.cpp create mode 100644 src/framework/sound/combinedsoundsource.h diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index 71a78e9e..738d2fd5 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -203,6 +203,7 @@ SET(framework_SOURCES ${framework_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/sound/soundmanager.cpp ${CMAKE_CURRENT_LIST_DIR}/sound/oggsoundfile.cpp ${CMAKE_CURRENT_LIST_DIR}/sound/streamsoundsource.cpp + ${CMAKE_CURRENT_LIST_DIR}/sound/combinedsoundsource.cpp # framework otml ${CMAKE_CURRENT_LIST_DIR}/otml/otmldocument.cpp diff --git a/src/framework/sound/combinedsoundsource.cpp b/src/framework/sound/combinedsoundsource.cpp new file mode 100644 index 00000000..eacc86d3 --- /dev/null +++ b/src/framework/sound/combinedsoundsource.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010-2012 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(); +} diff --git a/src/framework/sound/combinedsoundsource.h b/src/framework/sound/combinedsoundsource.h new file mode 100644 index 00000000..3b7ab9cf --- /dev/null +++ b/src/framework/sound/combinedsoundsource.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2012 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 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 m_sources; +}; + +#endif diff --git a/src/framework/sound/declarations.h b/src/framework/sound/declarations.h index c2bd0040..aa094a8e 100644 --- a/src/framework/sound/declarations.h +++ b/src/framework/sound/declarations.h @@ -35,12 +35,14 @@ class SoundSource; class SoundBuffer; class SoundFile; class StreamSoundSource; +class CombinedSoundSource; class OggSoundFile; typedef std::shared_ptr SoundSourcePtr; typedef std::shared_ptr SoundFilePtr; typedef std::shared_ptr SoundBufferPtr; typedef std::shared_ptr StreamSoundSourcePtr; +typedef std::shared_ptr CombinedSoundSourcePtr; typedef std::shared_ptr OggSoundFilePtr; #endif diff --git a/src/framework/sound/soundbuffer.cpp b/src/framework/sound/soundbuffer.cpp index 2f8e0b54..f72c1f1b 100644 --- a/src/framework/sound/soundbuffer.cpp +++ b/src/framework/sound/soundbuffer.cpp @@ -23,8 +23,6 @@ #include "soundbuffer.h" #include "soundfile.h" -#include - SoundBuffer::SoundBuffer() { m_bufferId = 0; @@ -38,7 +36,7 @@ SoundBuffer::~SoundBuffer() assert(alGetError() == AL_NO_ERROR); } -bool SoundBuffer::loadSoundFile(const SoundFilePtr& soundFile) +bool SoundBuffer::fillBuffer(const SoundFilePtr& soundFile) { ALenum format = soundFile->getSampleFormat(); if(format == AL_UNDETERMINED) { @@ -53,12 +51,16 @@ bool SoundBuffer::loadSoundFile(const SoundFilePtr& soundFile) 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& data, int size, int rate) +{ + alBufferData(m_bufferId, sampleFormat, &data[0], size, rate); ALenum err = alGetError(); 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 true; } diff --git a/src/framework/sound/soundbuffer.h b/src/framework/sound/soundbuffer.h index 294a7459..90b79e49 100644 --- a/src/framework/sound/soundbuffer.h +++ b/src/framework/sound/soundbuffer.h @@ -25,15 +25,18 @@ #include "declarations.h" +#include + class SoundBuffer { public: SoundBuffer(); ~SoundBuffer(); - bool loadSoundFile(const SoundFilePtr& soundFile); + bool fillBuffer(const SoundFilePtr& soundFile); + bool fillBuffer(ALenum sampleFormat, const DataBuffer& data, int size, int rate); - int getBufferId() { return m_bufferId; } + ALuint getBufferId() { return m_bufferId; } private: ALuint m_bufferId; diff --git a/src/framework/sound/soundmanager.cpp b/src/framework/sound/soundmanager.cpp index bdeb8fbe..8b64e21a 100644 --- a/src/framework/sound/soundmanager.cpp +++ b/src/framework/sound/soundmanager.cpp @@ -25,6 +25,7 @@ #include "soundbuffer.h" #include "soundfile.h" #include "streamsoundsource.h" +#include "combinedsoundsource.h" #include #include @@ -124,7 +125,7 @@ void SoundManager::preload(const std::string& filename) return; SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer); - if(buffer->loadSoundFile(soundFile)) + if(buffer->fillBuffer(soundFile)) m_buffers[filename] = buffer; } @@ -186,30 +187,53 @@ void SoundManager::stopMusic(float fadetime) SoundSourcePtr SoundManager::createSoundSource(const std::string& filename) { - SoundSourcePtr soundSource; + SoundSourcePtr source; auto it = m_buffers.find(filename); if(it != m_buffers.end()) { - soundSource = SoundSourcePtr(new SoundSource); - soundSource->setBuffer(it->second); + source = SoundSourcePtr(new SoundSource); + source->setBuffer(it->second); } else { SoundFilePtr soundFile = SoundFile::loadSoundFile(filename); if(!soundFile) return nullptr; if(soundFile->getSize() <= MAX_CACHE_SIZE) { - soundSource = SoundSourcePtr(new SoundSource); + source = SoundSourcePtr(new SoundSource); SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer); - buffer->loadSoundFile(soundFile); - soundSource->setBuffer(buffer); + buffer->fillBuffer(soundFile); + source->setBuffer(buffer); m_buffers[filename] = buffer; logWarning("uncached sound '", filename, "' requested to be played"); } else { - StreamSoundSourcePtr streamSoundSource(new StreamSoundSource); - streamSoundSource->setSoundFile(soundFile); - soundSource = streamSoundSource; + StreamSoundSourcePtr streamSource(new StreamSoundSource); + streamSource->setSoundFile(soundFile); + 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; } diff --git a/src/framework/sound/soundsource.cpp b/src/framework/sound/soundsource.cpp index 6833fdac..bc73865b 100644 --- a/src/framework/sound/soundsource.cpp +++ b/src/framework/sound/soundsource.cpp @@ -33,9 +33,11 @@ SoundSource::SoundSource() SoundSource::~SoundSource() { - stop(); - alDeleteSources(1, &m_sourceId); - assert(alGetError() == AL_NO_ERROR); + if(m_sourceId != 0) { + stop(); + alDeleteSources(1, &m_sourceId); + assert(alGetError() == AL_NO_ERROR); + } } void SoundSource::play() @@ -93,3 +95,13 @@ void SoundSource::setPitch(float 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); +} diff --git a/src/framework/sound/soundsource.h b/src/framework/sound/soundsource.h index f00c9407..ef00f8c3 100644 --- a/src/framework/sound/soundsource.h +++ b/src/framework/sound/soundsource.h @@ -27,27 +27,31 @@ class SoundSource { +protected: + SoundSource(ALuint sourceId) : m_sourceId(sourceId) { } + public: SoundSource(); virtual ~SoundSource(); - void play(); - void stop(); + virtual void play(); + virtual void stop(); + virtual bool isPlaying(); - bool isPlaying(); - - void setBuffer(const SoundBufferPtr& buffer); virtual void setLooping(bool looping); - void setRelative(bool relative); - void setReferenceDistance(float distance); - void setGain(float gain); - void setPitch(float pitch); - - // TODO: velocity, position + virtual void setRelative(bool relative); + virtual void setReferenceDistance(float distance); + virtual void setGain(float gain); + virtual void setPitch(float pitch); + virtual void setPosition(const Point& pos); + virtual void setVelocity(const Point& velocity); protected: + void setBuffer(const SoundBufferPtr& buffer); + virtual void update() { } friend class SoundManager; + friend class CombinedSoundSource; ALuint m_sourceId; SoundBufferPtr m_buffer; diff --git a/src/framework/sound/streamsoundsource.cpp b/src/framework/sound/streamsoundsource.cpp index 5a07f794..87e407a3 100644 --- a/src/framework/sound/streamsoundsource.cpp +++ b/src/framework/sound/streamsoundsource.cpp @@ -32,25 +32,39 @@ StreamSoundSource::StreamSoundSource() for(auto& buffer : m_buffers) buffer = SoundBufferPtr(new SoundBuffer); m_fadeState = NoFading; + m_downMix = NoDownMix; } StreamSoundSource::~StreamSoundSource() { 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) { 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; alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued); 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() { ALint processed = 0; @@ -76,9 +100,8 @@ void StreamSoundSource::update() if(processed == 0 || !m_looping) return; - // we might have to restart the source if we had a buffer underrun - //log_info << "Restarting audio source because of buffer underrun" << std::endl; - //play(); + logTraceError("restarting audio source because of buffer underrun"); + play(); } float realTime = g_clock.asyncTime(); @@ -104,7 +127,8 @@ void StreamSoundSource::update() bool StreamSoundSource::fillBufferAndQueue(ALuint buffer) { // fill buffer - DataBuffer bufferData(STREAM_FRAGMENT_SIZE); + static DataBuffer bufferData(STREAM_FRAGMENT_SIZE); + ALenum format = m_soundFile->getSampleFormat(); int bytesRead = 0; do { @@ -119,8 +143,21 @@ bool StreamSoundSource::fillBufferAndQueue(ALuint buffer) } } 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 0) { - ALenum format = m_soundFile->getSampleFormat(); alBufferData(buffer, format, &bufferData[0], bytesRead, m_soundFile->getRate()); ALenum err = alGetError(); 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 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) diff --git a/src/framework/sound/streamsoundsource.h b/src/framework/sound/streamsoundsource.h index 8511830a..56646bd7 100644 --- a/src/framework/sound/streamsoundsource.h +++ b/src/framework/sound/streamsoundsource.h @@ -34,23 +34,31 @@ class StreamSoundSource : public SoundSource }; public: + enum DownMix { NoDownMix, DownMixLeft, DownMixRight }; enum FadeState { NoFading, FadingOn, FadingOff }; StreamSoundSource(); virtual ~StreamSoundSource(); + void play(); + void stop(); + void setSoundFile(const SoundFilePtr& soundFile); + void downMix(DownMix downMix); void setFading(FadeState state, float fadetime); FadeState getFadeState() { return m_fadeState; } void update(); private: + void queueBuffers(); + void unqueueBuffers(); bool fillBufferAndQueue(ALuint buffer); SoundFilePtr m_soundFile; std::array m_buffers; + DownMix m_downMix; FadeState m_fadeState; float m_fadeStartTime; float m_fadeTime;