1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-25 03:42:48 +01:00

Streaming implementation

This commit is contained in:
Filip Gawin 2019-04-11 23:32:47 +02:00
parent 76fc914695
commit 139034c53b
11 changed files with 196 additions and 31 deletions

View File

@ -20,6 +20,8 @@ set(RWENGINE_SOURCES
src/audio/Sound.hpp src/audio/Sound.hpp
src/audio/SoundBuffer.cpp src/audio/SoundBuffer.cpp
src/audio/SoundBuffer.hpp src/audio/SoundBuffer.hpp
src/audio/SoundBufferStreamed.cpp
src/audio/SoundBufferStreamed.hpp
src/audio/SoundManager.cpp src/audio/SoundManager.cpp
src/audio/SoundManager.hpp src/audio/SoundManager.hpp
src/audio/SoundSource.cpp src/audio/SoundSource.cpp

View File

@ -2,6 +2,12 @@
#include "audio/SoundBuffer.hpp" #include "audio/SoundBuffer.hpp"
Sound::~Sound() {
if (buffer) {
stop();
}
}
bool Sound::isPlaying() const { bool Sound::isPlaying() const {
return buffer->isPlaying(); return buffer->isPlaying();
} }

View File

@ -19,7 +19,7 @@ struct Sound {
std::unique_ptr<SoundBuffer> buffer; std::unique_ptr<SoundBuffer> buffer;
Sound() = default; Sound() = default;
~Sound() = default; ~Sound();
bool isPlaying() const; bool isPlaying() const;

View File

@ -2,8 +2,13 @@
#include <rw/types.hpp> #include <rw/types.hpp>
#include "audio/alCheck.hpp"
#include "audio/SoundSource.hpp" #include "audio/SoundSource.hpp"
#include "audio/alCheck.hpp"
namespace {
constexpr int kNrBuffersStreaming = 4;
constexpr int kSizeOfChunk = 4;
} // namespace
SoundBuffer::SoundBuffer() { SoundBuffer::SoundBuffer() {
alCheck(alGenSources(1, &source)); alCheck(alGenSources(1, &source));
@ -29,7 +34,6 @@ bool SoundBuffer::bufferData(SoundSource& soundSource) {
static_cast<ALsizei>(soundSource.data.size() * sizeof(int16_t)), static_cast<ALsizei>(soundSource.data.size() * sizeof(int16_t)),
soundSource.sampleRate)); soundSource.sampleRate));
alCheck(alSourcei(source, AL_BUFFER, buffer)); alCheck(alSourcei(source, AL_BUFFER, buffer));
return true; return true;
} }
@ -53,12 +57,18 @@ bool SoundBuffer::isStopped() const {
void SoundBuffer::play() { void SoundBuffer::play() {
alCheck(alSourcePlay(source)); alCheck(alSourcePlay(source));
running = true;
} }
void SoundBuffer::pause() { void SoundBuffer::pause() {
alCheck(alSourcePause(source)); alCheck(alSourcePause(source));
running = false;
} }
void SoundBuffer::stop() { void SoundBuffer::stop() {
alCheck(alSourceStop(source)); alCheck(alSourceStop(source));
running = false;
} }
void SoundBuffer::setPosition(const glm::vec3& position) { void SoundBuffer::setPosition(const glm::vec3& position) {

View File

@ -4,20 +4,22 @@
#include <al.h> #include <al.h>
#include <glm/vec3.hpp> #include <glm/vec3.hpp>
#include <vector>
class SoundSource; class SoundSource;
/// OpenAL tool for playing /// OpenAL tool for playing
/// sound instance. /// sound instance.
struct SoundBuffer { struct SoundBuffer {
SoundBuffer(); SoundBuffer();
~SoundBuffer(); virtual ~SoundBuffer();
bool bufferData(SoundSource& soundSource); virtual bool bufferData(SoundSource& soundSource);
bool isPlaying() const; bool isPlaying() const;
bool isPaused() const; bool isPaused() const;
bool isStopped() const; bool isStopped() const;
void play(); virtual void play();
void pause(); void pause();
void stop(); void stop();
@ -28,6 +30,8 @@ struct SoundBuffer {
void setMaxDistance(float maxDist); void setMaxDistance(float maxDist);
ALuint source; ALuint source;
bool running = false;
private:
ALuint buffer; ALuint buffer;
}; };

View File

@ -0,0 +1,108 @@
#include "audio/SoundBufferStreamed.hpp"
#include <rw/types.hpp>
#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<std::mutex> 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<size_t>(kSizeOfChunk),
soundSource.data.size());
alCheck(alBufferData(
buffers[i],
soundSource.channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
&soundSource.data[streamedData * kSizeOfChunk],
static_cast<ALsizei>(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<std::mutex> 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<size_t>(kSizeOfChunk),
soundSource->data.size() -
static_cast<size_t>(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);
}
}
}

View File

@ -0,0 +1,27 @@
#ifndef _RWENGINE_SOUND_BUFFER_STREAMED_HPP_
#define _RWENGINE_SOUND_BUFFER_STREAMED_HPP_
#include "audio/SoundBuffer.hpp"
#include <array>
#include <future>
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<ALuint, kNrBuffersStreaming> buffers;
std::future<void> loadingThread;
};
#endif

View File

@ -7,10 +7,11 @@ extern "C" {
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
} }
#include "audio/alCheck.hpp"
#include "audio/Sound.hpp" #include "audio/Sound.hpp"
#include "audio/SoundBuffer.hpp" #include "audio/SoundBuffer.hpp"
#include "audio/SoundBufferStreamed.hpp"
#include "audio/SoundSource.hpp" #include "audio/SoundSource.hpp"
#include "audio/alCheck.hpp"
#include "engine/GameData.hpp" #include "engine/GameData.hpp"
#include "engine/GameWorld.hpp" #include "engine/GameWorld.hpp"
#include "render/ViewCamera.hpp" #include "render/ViewCamera.hpp"
@ -98,19 +99,19 @@ void SoundManager::deinitializeOpenAL() {
// De-initialize OpenAL // De-initialize OpenAL
if (alContext) { if (alContext) {
alcMakeContextCurrent(nullptr); alcMakeContextCurrent(nullptr);
alcDestroyContext(alContext); alcDestroyContext(alContext);
} }
alContext = nullptr; alContext = nullptr;
if (alDevice) { if (alDevice) {
alcCloseDevice(alDevice); alcCloseDevice(alDevice);
} }
alDevice = nullptr; alDevice = nullptr;
} }
bool SoundManager::loadSound(const std::string& name, bool SoundManager::loadSound(const std::string& name,
const std::string& fileName) { const std::string& fileName, bool streamed) {
Sound* sound = nullptr; Sound* sound = nullptr;
auto sound_iter = sounds.find(name); auto sound_iter = sounds.find(name);
@ -118,14 +119,14 @@ bool SoundManager::loadSound(const std::string& name,
sound = &sound_iter->second; sound = &sound_iter->second;
} else { } else {
auto [it, emplaced] = sounds.emplace(std::piecewise_construct, auto [it, emplaced] = sounds.emplace(std::piecewise_construct,
std::forward_as_tuple(name), std::forward_as_tuple(name),
std::forward_as_tuple()); std::forward_as_tuple());
sound = &it->second; sound = &it->second;
sound->source = std::make_shared<SoundSource>(); sound->source = std::make_shared<SoundSource>();
sound->buffer = std::make_unique<SoundBuffer>(); sound->buffer = streamed ? std::make_unique<SoundBufferStreamed>() : std::make_unique<SoundBuffer>();
sound->source->loadFromFile(fileName); sound->source->loadFromFile(fileName, streamed);
sound->isLoaded = sound->buffer->bufferData(*sound->source); sound->isLoaded = sound->buffer->bufferData(*sound->source);
} }
@ -148,7 +149,7 @@ size_t SoundManager::createSfxInstance(size_t index) {
Sound* sound = nullptr; Sound* sound = nullptr;
auto soundRef = sfx.find(index); auto soundRef = sfx.find(index);
if(soundRef == sfx.end()) { if (soundRef == sfx.end()) {
// Sound source is not loaded yet // Sound source is not loaded yet
loadSound(index); loadSound(index);
soundRef = sfx.find(index); soundRef = sfx.find(index);
@ -161,16 +162,15 @@ size_t SoundManager::createSfxInstance(size_t index) {
// Let's use this buffer // Let's use this buffer
sound.buffer = std::make_unique<SoundBuffer>(); sound.buffer = std::make_unique<SoundBuffer>();
sound.source = soundRef->second.source; sound.source = soundRef->second.source;
sound.isLoaded = sound.isLoaded = sound.buffer->bufferData(*sound.source);
sound.buffer->bufferData(*sound.source);
return id; return id;
} }
} }
// There's no available free buffer, so // There's no available free buffer, so
// we should create a new one. // we should create a new one.
auto [it, emplaced] = buffers.emplace(std::piecewise_construct, auto [it, emplaced] = buffers.emplace(std::piecewise_construct,
std::forward_as_tuple(bufferNr), std::forward_as_tuple(bufferNr),
std::forward_as_tuple()); std::forward_as_tuple());
sound = &it->second; sound = &it->second;
sound->id = bufferNr; sound->id = bufferNr;
@ -341,7 +341,6 @@ void SoundManager::setSoundPosition(const std::string& name,
} }
} }
void SoundManager::setVolume(float vol) { void SoundManager::setVolume(float vol) {
_volume = vol; _volume = vol;
} }

View File

@ -29,7 +29,7 @@ public:
~SoundManager(); ~SoundManager();
/// Load sound from file and store it with selected name /// 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 /// Load selected sfx sound
void loadSound(size_t index); void loadSound(size_t index);

View File

@ -60,7 +60,7 @@ int read_packet(void* opaque, uint8_t* buf, int buf_size) {
} // namespace } // namespace
bool SoundSource::prepareFormatContextSfx(LoaderSDT& sdt, size_t index, bool SoundSource::prepareFormatContextSfx(LoaderSDT& sdt, size_t index,
bool asWave) { bool asWave) {
/// Now we need to prepare "custom" format context /// Now we need to prepare "custom" format context
/// We need sdt loader for that purpose /// We need sdt loader for that purpose
raw_sound = sdt.loadToMemory(index, asWave); raw_sound = sdt.loadToMemory(index, asWave);
@ -288,6 +288,7 @@ void SoundSource::decodeFramesLegacy(size_t framesToDecode) {
// Write samples to audio buffer // Write samples to audio buffer
for (size_t i = 0; for (size_t i = 0;
i < static_cast<size_t>(frame->nb_samples); i++) { i < static_cast<size_t>(frame->nb_samples); i++) {
std::lock_guard<std::mutex> lock(mutex);
// Interleave left/right channels // Interleave left/right channels
for (size_t channel = 0; channel < channels; for (size_t channel = 0; channel < channels;
channel++) { channel++) {
@ -337,6 +338,7 @@ void SoundSource::decodeFrames(size_t framesToDecode) {
for (size_t i = 0; for (size_t i = 0;
i < static_cast<size_t>(frame->nb_samples); i++) { i < static_cast<size_t>(frame->nb_samples); i++) {
std::lock_guard<std::mutex> lock(mutex);
// Interleave left/right channels // Interleave left/right channels
for (size_t channel = 0; channel < channels; for (size_t channel = 0; channel < channels;
channel++) { channel++) {
@ -417,6 +419,7 @@ void SoundSource::decodeAndResampleFrames(const rwfs::path& filePath,
RW_ERROR("Error resampling " << filePath << '\n'); RW_ERROR("Error resampling " << filePath << '\n');
} }
std::lock_guard<std::mutex> lock(mutex);
for (size_t i = 0; for (size_t i = 0;
i < i <
static_cast<size_t>(resampled->nb_samples) * channels; static_cast<size_t>(resampled->nb_samples) * channels;
@ -512,7 +515,7 @@ void SoundSource::loadFromFile(const rwfs::path& filePath, bool streaming) {
decodeFramesWrap(filePath); decodeFramesWrap(filePath);
if (streaming) { if (streaming) {
auto loadingThread = std::async( loadingThread = std::async(
std::launch::async, std::launch::async,
&SoundSource::decodeRestSoundFramesAndCleanup, this, filePath); &SoundSource::decodeRestSoundFramesAndCleanup, this, filePath);
} else { } else {
@ -531,7 +534,7 @@ void SoundSource::loadSfx(LoaderSDT& sdt, size_t index, bool asWave,
decodeFramesSfxWrap(); decodeFramesSfxWrap();
if (streaming) { if (streaming) {
auto loadingThread = loadingThread =
std::async(std::launch::async, std::async(std::launch::async,
&SoundSource::decodeRestSfxFramesAndCleanup, this); &SoundSource::decodeRestSfxFramesAndCleanup, this);
} else { } else {

View File

@ -10,6 +10,7 @@ extern "C" {
#include <cstdint> #include <cstdint>
#include <future> #include <future>
#include <mutex>
/// Structure for input data /// Structure for input data
struct InputData { struct InputData {
@ -31,6 +32,7 @@ class LoaderSDT;
class SoundSource { class SoundSource {
friend class SoundManager; friend class SoundManager;
friend struct SoundBuffer; friend struct SoundBuffer;
friend struct SoundBufferStreamed;
public: public:
bool allocateAudioFrame(); bool allocateAudioFrame();
@ -59,7 +61,8 @@ public:
void decodeFramesWrap(const rwfs::path& filePath); void decodeFramesWrap(const rwfs::path& filePath);
void decodeFramesSfxWrap(); void decodeFramesSfxWrap();
void decodeFrames(size_t framesToDecode); 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 cleanupAfterSoundLoading();
void cleanupAfterSfxLoading(); void cleanupAfterSfxLoading();
@ -99,6 +102,9 @@ private:
std::unique_ptr<char[]> raw_sound; std::unique_ptr<char[]> raw_sound;
std::unique_ptr<uint8_t[]> inputDataStart; std::unique_ptr<uint8_t[]> inputDataStart;
InputData input{}; InputData input{};
std::mutex mutex;
std::future<void> loadingThread;
}; };
#endif #endif