diff --git a/rwengine/CMakeLists.txt b/rwengine/CMakeLists.txt index cddfe5f9..4b638495 100644 --- a/rwengine/CMakeLists.txt +++ b/rwengine/CMakeLists.txt @@ -20,6 +20,8 @@ set(RWENGINE_SOURCES src/audio/Sound.hpp src/audio/SoundBuffer.cpp src/audio/SoundBuffer.hpp + src/audio/SoundBufferStreamed.cpp + src/audio/SoundBufferStreamed.hpp src/audio/SoundManager.cpp src/audio/SoundManager.hpp src/audio/SoundSource.cpp diff --git a/rwengine/src/audio/Sound.cpp b/rwengine/src/audio/Sound.cpp index df934cd7..f6266f38 100644 --- a/rwengine/src/audio/Sound.cpp +++ b/rwengine/src/audio/Sound.cpp @@ -2,6 +2,12 @@ #include "audio/SoundBuffer.hpp" +Sound::~Sound() { + if (buffer) { + stop(); + } +} + bool Sound::isPlaying() const { return buffer->isPlaying(); } diff --git a/rwengine/src/audio/Sound.hpp b/rwengine/src/audio/Sound.hpp index fbb29122..ec1c5ba1 100644 --- a/rwengine/src/audio/Sound.hpp +++ b/rwengine/src/audio/Sound.hpp @@ -19,7 +19,7 @@ struct Sound { std::unique_ptr buffer; Sound() = default; - ~Sound() = default; + ~Sound(); bool isPlaying() const; diff --git a/rwengine/src/audio/SoundBuffer.cpp b/rwengine/src/audio/SoundBuffer.cpp index 9d367eb8..a6df940d 100644 --- a/rwengine/src/audio/SoundBuffer.cpp +++ b/rwengine/src/audio/SoundBuffer.cpp @@ -2,8 +2,13 @@ #include -#include "audio/alCheck.hpp" #include "audio/SoundSource.hpp" +#include "audio/alCheck.hpp" + +namespace { +constexpr int kNrBuffersStreaming = 4; +constexpr int kSizeOfChunk = 4; +} // namespace SoundBuffer::SoundBuffer() { alCheck(alGenSources(1, &source)); @@ -29,7 +34,6 @@ bool SoundBuffer::bufferData(SoundSource& soundSource) { static_cast(soundSource.data.size() * sizeof(int16_t)), soundSource.sampleRate)); alCheck(alSourcei(source, AL_BUFFER, buffer)); - return true; } @@ -53,12 +57,18 @@ bool SoundBuffer::isStopped() const { void SoundBuffer::play() { alCheck(alSourcePlay(source)); + + running = true; } void SoundBuffer::pause() { alCheck(alSourcePause(source)); + + running = false; } void SoundBuffer::stop() { alCheck(alSourceStop(source)); + + running = false; } void SoundBuffer::setPosition(const glm::vec3& position) { diff --git a/rwengine/src/audio/SoundBuffer.hpp b/rwengine/src/audio/SoundBuffer.hpp index 2a5f6949..ce7d4bb6 100644 --- a/rwengine/src/audio/SoundBuffer.hpp +++ b/rwengine/src/audio/SoundBuffer.hpp @@ -4,20 +4,22 @@ #include #include +#include + class SoundSource; /// OpenAL tool for playing /// sound instance. struct SoundBuffer { SoundBuffer(); - ~SoundBuffer(); - bool bufferData(SoundSource& soundSource); + virtual ~SoundBuffer(); + virtual bool bufferData(SoundSource& soundSource); bool isPlaying() const; bool isPaused() const; bool isStopped() const; - void play(); + virtual void play(); void pause(); void stop(); @@ -28,6 +30,8 @@ struct SoundBuffer { void setMaxDistance(float maxDist); ALuint source; + bool running = false; +private: ALuint buffer; }; diff --git a/rwengine/src/audio/SoundBufferStreamed.cpp b/rwengine/src/audio/SoundBufferStreamed.cpp new file mode 100644 index 00000000..3c15aa05 --- /dev/null +++ b/rwengine/src/audio/SoundBufferStreamed.cpp @@ -0,0 +1,108 @@ +#include "audio/SoundBufferStreamed.hpp" + +#include + +#include "audio/SoundSource.hpp" +#include "audio/alCheck.hpp" + +SoundBufferStreamed::SoundBufferStreamed() { + alCheck(alGenSources(1, &source)); + + alCheck(alGenBuffers(kNrBuffersStreaming, buffers.data())); + + alCheck(alSourcef(source, AL_PITCH, 1)); + alCheck(alSourcef(source, AL_GAIN, 1)); + alCheck(alSource3f(source, AL_POSITION, 0, 0, 0)); + alCheck(alSource3f(source, AL_VELOCITY, 0, 0, 0)); + alCheck(alSourcei(source, AL_LOOPING, AL_FALSE)); +} + +SoundBufferStreamed::~SoundBufferStreamed() { + alCheck(alDeleteBuffers(kNrBuffersStreaming, buffers.data())); +} + +bool SoundBufferStreamed::bufferData(SoundSource &soundSource) { + /* Rewind the source position and clear the buffer queue */ + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + + std::lock_guard lock(soundSource.mutex); + /* Fill the buffer queue */ + for (auto i = 0u; i < buffers.size() && + streamedData * kSizeOfChunk < soundSource.data.size(); + i++) { + auto sizeOfNextChunk = std::min(static_cast(kSizeOfChunk), + soundSource.data.size()); + + alCheck(alBufferData( + buffers[i], + soundSource.channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, + &soundSource.data[streamedData * kSizeOfChunk], + static_cast(sizeOfNextChunk * sizeof(int16_t)), + soundSource.sampleRate)); + streamedData++; + } + + alSourceQueueBuffers(source, kNrBuffersStreaming, buffers.data()); + + this->soundSource = &soundSource; + + return true; +} + +void SoundBufferStreamed::play() { + alSourcePlay(source); + + running = true; + + loadingThread = std::async(std::launch::async, + &SoundBufferStreamed::updateBuffers, this); +} + +void SoundBufferStreamed::updateBuffers() { + while (running) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ALint processed, state; + + /* Get relevant source info */ + alGetSourcei(source, AL_SOURCE_STATE, &state); + alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed); + + std::lock_guard lock(soundSource->mutex); + + /* Unqueue and handle each processed buffer */ + while (processed > 0 && + streamedData * kSizeOfChunk < soundSource->data.size()) { + ALuint bufid{}; + auto sizeOfNextChunk = + std::min(static_cast(kSizeOfChunk), + soundSource->data.size() - + static_cast(kSizeOfChunk) * streamedData); + + alSourceUnqueueBuffers(source, 1, &bufid); + processed--; + + if (sizeOfNextChunk > 0) { + alBufferData(bufid, + soundSource->channels == 1 ? AL_FORMAT_MONO16 + : AL_FORMAT_STEREO16, + &soundSource->data[streamedData * kSizeOfChunk], + sizeOfNextChunk * sizeof(int16_t), + soundSource->sampleRate); + streamedData++; + alSourceQueueBuffers(source, 1, &bufid); + } + } + + /* Make sure the source hasn't underrun */ + if (state != AL_PLAYING && state != AL_PAUSED) { + ALint queued; + + /* If no buffers are queued, playback is finished */ + alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); + if (queued == 0) return; + + alSourcePlay(source); + } + } +} diff --git a/rwengine/src/audio/SoundBufferStreamed.hpp b/rwengine/src/audio/SoundBufferStreamed.hpp new file mode 100644 index 00000000..ffb40695 --- /dev/null +++ b/rwengine/src/audio/SoundBufferStreamed.hpp @@ -0,0 +1,27 @@ +#ifndef _RWENGINE_SOUND_BUFFER_STREAMED_HPP_ +#define _RWENGINE_SOUND_BUFFER_STREAMED_HPP_ + +#include "audio/SoundBuffer.hpp" + +#include +#include + +struct SoundBufferStreamed : public SoundBuffer { + static constexpr unsigned int kNrBuffersStreaming = 4; + static constexpr unsigned int kSizeOfChunk = 4096; + + SoundBufferStreamed(); + ~SoundBufferStreamed() override; + bool bufferData(SoundSource& soundSource) final; + + virtual void play() final; + +private: + SoundSource* soundSource = nullptr; + void updateBuffers(); + unsigned int streamedData = 0; + std::array buffers; + std::future loadingThread; +}; + +#endif diff --git a/rwengine/src/audio/SoundManager.cpp b/rwengine/src/audio/SoundManager.cpp index 2267e1bd..7a072419 100644 --- a/rwengine/src/audio/SoundManager.cpp +++ b/rwengine/src/audio/SoundManager.cpp @@ -7,10 +7,11 @@ extern "C" { #include } -#include "audio/alCheck.hpp" #include "audio/Sound.hpp" #include "audio/SoundBuffer.hpp" +#include "audio/SoundBufferStreamed.hpp" #include "audio/SoundSource.hpp" +#include "audio/alCheck.hpp" #include "engine/GameData.hpp" #include "engine/GameWorld.hpp" #include "render/ViewCamera.hpp" @@ -98,19 +99,19 @@ void SoundManager::deinitializeOpenAL() { // De-initialize OpenAL if (alContext) { - alcMakeContextCurrent(nullptr); - alcDestroyContext(alContext); - } - alContext = nullptr; + alcMakeContextCurrent(nullptr); + alcDestroyContext(alContext); + } + alContext = nullptr; - if (alDevice) { - alcCloseDevice(alDevice); - } - alDevice = nullptr; + if (alDevice) { + alcCloseDevice(alDevice); + } + alDevice = nullptr; } bool SoundManager::loadSound(const std::string& name, - const std::string& fileName) { + const std::string& fileName, bool streamed) { Sound* sound = nullptr; auto sound_iter = sounds.find(name); @@ -118,14 +119,14 @@ bool SoundManager::loadSound(const std::string& name, sound = &sound_iter->second; } else { auto [it, emplaced] = sounds.emplace(std::piecewise_construct, - std::forward_as_tuple(name), - std::forward_as_tuple()); + std::forward_as_tuple(name), + std::forward_as_tuple()); sound = &it->second; sound->source = std::make_shared(); - sound->buffer = std::make_unique(); + sound->buffer = streamed ? std::make_unique() : std::make_unique(); - sound->source->loadFromFile(fileName); + sound->source->loadFromFile(fileName, streamed); sound->isLoaded = sound->buffer->bufferData(*sound->source); } @@ -148,7 +149,7 @@ size_t SoundManager::createSfxInstance(size_t index) { Sound* sound = nullptr; auto soundRef = sfx.find(index); - if(soundRef == sfx.end()) { + if (soundRef == sfx.end()) { // Sound source is not loaded yet loadSound(index); soundRef = sfx.find(index); @@ -161,16 +162,15 @@ size_t SoundManager::createSfxInstance(size_t index) { // Let's use this buffer sound.buffer = std::make_unique(); sound.source = soundRef->second.source; - sound.isLoaded = - sound.buffer->bufferData(*sound.source); + sound.isLoaded = sound.buffer->bufferData(*sound.source); return id; } } // There's no available free buffer, so // we should create a new one. auto [it, emplaced] = buffers.emplace(std::piecewise_construct, - std::forward_as_tuple(bufferNr), - std::forward_as_tuple()); + std::forward_as_tuple(bufferNr), + std::forward_as_tuple()); sound = &it->second; sound->id = bufferNr; @@ -341,7 +341,6 @@ void SoundManager::setSoundPosition(const std::string& name, } } - void SoundManager::setVolume(float vol) { _volume = vol; } diff --git a/rwengine/src/audio/SoundManager.hpp b/rwengine/src/audio/SoundManager.hpp index c304a9b3..63637796 100644 --- a/rwengine/src/audio/SoundManager.hpp +++ b/rwengine/src/audio/SoundManager.hpp @@ -29,7 +29,7 @@ public: ~SoundManager(); /// Load sound from file and store it with selected name - bool loadSound(const std::string& name, const std::string& fileName); + bool loadSound(const std::string& name, const std::string& fileName, bool streamed = true); /// Load selected sfx sound void loadSound(size_t index); diff --git a/rwengine/src/audio/SoundSource.cpp b/rwengine/src/audio/SoundSource.cpp index bebbd439..871ba41d 100644 --- a/rwengine/src/audio/SoundSource.cpp +++ b/rwengine/src/audio/SoundSource.cpp @@ -60,7 +60,7 @@ int read_packet(void* opaque, uint8_t* buf, int buf_size) { } // namespace bool SoundSource::prepareFormatContextSfx(LoaderSDT& sdt, size_t index, - bool asWave) { + bool asWave) { /// Now we need to prepare "custom" format context /// We need sdt loader for that purpose raw_sound = sdt.loadToMemory(index, asWave); @@ -288,6 +288,7 @@ void SoundSource::decodeFramesLegacy(size_t framesToDecode) { // Write samples to audio buffer for (size_t i = 0; i < static_cast(frame->nb_samples); i++) { + std::lock_guard lock(mutex); // Interleave left/right channels for (size_t channel = 0; channel < channels; channel++) { @@ -337,6 +338,7 @@ void SoundSource::decodeFrames(size_t framesToDecode) { for (size_t i = 0; i < static_cast(frame->nb_samples); i++) { + std::lock_guard lock(mutex); // Interleave left/right channels for (size_t channel = 0; channel < channels; channel++) { @@ -417,6 +419,7 @@ void SoundSource::decodeAndResampleFrames(const rwfs::path& filePath, RW_ERROR("Error resampling " << filePath << '\n'); } + std::lock_guard lock(mutex); for (size_t i = 0; i < static_cast(resampled->nb_samples) * channels; @@ -512,7 +515,7 @@ void SoundSource::loadFromFile(const rwfs::path& filePath, bool streaming) { decodeFramesWrap(filePath); if (streaming) { - auto loadingThread = std::async( + loadingThread = std::async( std::launch::async, &SoundSource::decodeRestSoundFramesAndCleanup, this, filePath); } else { @@ -531,7 +534,7 @@ void SoundSource::loadSfx(LoaderSDT& sdt, size_t index, bool asWave, decodeFramesSfxWrap(); if (streaming) { - auto loadingThread = + loadingThread = std::async(std::launch::async, &SoundSource::decodeRestSfxFramesAndCleanup, this); } else { diff --git a/rwengine/src/audio/SoundSource.hpp b/rwengine/src/audio/SoundSource.hpp index a22993a1..4cb977d0 100644 --- a/rwengine/src/audio/SoundSource.hpp +++ b/rwengine/src/audio/SoundSource.hpp @@ -10,6 +10,7 @@ extern "C" { #include #include +#include /// Structure for input data struct InputData { @@ -31,6 +32,7 @@ class LoaderSDT; class SoundSource { friend class SoundManager; friend struct SoundBuffer; + friend struct SoundBufferStreamed; public: bool allocateAudioFrame(); @@ -59,7 +61,8 @@ public: void decodeFramesWrap(const rwfs::path& filePath); void decodeFramesSfxWrap(); void decodeFrames(size_t framesToDecode); - void decodeAndResampleFrames(const rwfs::path& filePath, size_t framesToDecode); + void decodeAndResampleFrames(const rwfs::path& filePath, + size_t framesToDecode); void cleanupAfterSoundLoading(); void cleanupAfterSfxLoading(); @@ -99,6 +102,9 @@ private: std::unique_ptr raw_sound; std::unique_ptr inputDataStart; InputData input{}; + + std::mutex mutex; + std::future loadingThread; }; #endif