From 8bd43098d59c7c5336b572fd835de0b25cbe2c13 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 13:21:58 +0300 Subject: [PATCH] ALStream WIP --- src/audio/AL/ALAudio.cpp | 48 ++++++++++++++++++++ src/audio/AL/ALAudio.h | 19 ++++++++ src/audio/NoAudio.cpp | 4 ++ src/audio/NoAudio.h | 34 +++++++++++++++ src/audio/audio.cpp | 94 +++++++++++++++++++++++++++++++++++++++- src/audio/audio.h | 36 ++++++++++++--- src/coders/ogg.cpp | 20 ++++++--- src/coders/wav.cpp | 2 +- 8 files changed, 245 insertions(+), 12 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 22e418e7..92eb5441 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -28,6 +28,50 @@ Speaker* ALSound::newInstance(int priority) const { return new ALSpeaker(al, source, priority); } +ALStream::ALStream(ALAudio* al, std::shared_ptr source, bool keepSource) +: al(al), source(source), keepSource(keepSource) { +} + +ALStream::~ALStream() { + bindSpeaker(0); + source = nullptr; +} + +std::shared_ptr ALStream::getSource() const { + if (keepSource) { + return source; + } else { + return nullptr; + } +} + +Speaker* ALStream::createSpeaker() { + uint source = al->getFreeSource(); + // TODO: prepare source and enqueue buffers + return new ALSpeaker(al, source, PRIORITY_HIGH); +} + + +void ALStream::bindSpeaker(speakerid_t speaker) { + auto sp = audio::get(this->speaker); + if (sp) { + sp->stop(); + } + this->speaker = speaker; +} + +speakerid_t ALStream::getSpeaker() const { + return speaker; +} + +void ALStream::update(double delta) { + // TODO: implement +} + +void ALStream::setTime(duration_t time) { + // TODO: implement +} + ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority) : al(al), source(source), priority(priority) { } @@ -162,6 +206,10 @@ Sound* ALAudio::createSound(std::shared_ptr pcm, bool keepPCM) { return new ALSound(this, buffer, pcm, keepPCM); } +Stream* ALAudio::openStream(std::shared_ptr stream, bool keepSource) { + return new ALStream(this, stream, keepSource); +} + ALAudio* ALAudio::create() { ALCdevice* device = alcOpenDevice(nullptr); if (device == nullptr) diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.h index 7389b76e..b37a602d 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -20,6 +20,7 @@ namespace audio { struct ALBuffer; class ALAudio; + class PCMStream; class ALSound : public Sound { ALAudio* al; @@ -41,6 +42,23 @@ namespace audio { Speaker* newInstance(int priority) const override; }; + class ALStream : public Stream { + ALAudio* al; + std::shared_ptr source; + speakerid_t speaker = 0; + bool keepSource; + public: + ALStream(ALAudio* al, std::shared_ptr source, bool keepSource); + ~ALStream(); + + std::shared_ptr getSource() const override; + void bindSpeaker(speakerid_t speaker) override; + Speaker* createSpeaker() override; + speakerid_t getSpeaker() const override; + void update(double delta) override; + void setTime(duration_t time) override; + }; + /// @brief AL source adapter class ALSpeaker : public Speaker { ALAudio* al; @@ -101,6 +119,7 @@ namespace audio { std::vector getAvailableDevices() const; Sound* createSound(std::shared_ptr pcm, bool keepPCM) override; + Stream* openStream(std::shared_ptr stream, bool keepSource) override; void setListener( glm::vec3 position, diff --git a/src/audio/NoAudio.cpp b/src/audio/NoAudio.cpp index 4a3a8e45..b6193fd0 100644 --- a/src/audio/NoAudio.cpp +++ b/src/audio/NoAudio.cpp @@ -13,6 +13,10 @@ Sound* NoAudio::createSound(std::shared_ptr pcm, bool keepPCM) { return new NoSound(pcm, keepPCM); } +Stream* NoAudio::openStream(std::shared_ptr stream, bool keepSource) { + return new NoStream(stream, keepSource); +} + NoAudio* NoAudio::create() { return new NoAudio(); } diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h index 4c9cee4f..e2a61881 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.h @@ -24,11 +24,45 @@ namespace audio { } }; + class NoStream : public Stream { + std::shared_ptr source; + duration_t duration; + public: + NoStream(std::shared_ptr source, bool keepSource) { + duration = source->getTotalDuration(); + if (keepSource) { + this->source = source; + } + } + + std::shared_ptr getSource() const { + return source; + } + + void bindSpeaker(speakerid_t speaker) { + } + + Speaker* createSpeaker() { + return nullptr; + } + + speakerid_t getSpeaker() const { + return 0; + } + + void update(double delta) { + } + + void setTime(duration_t time) { + } + }; + class NoAudio : public Backend { public: ~NoAudio() {} Sound* createSound(std::shared_ptr pcm, bool keepPCM) override; + Stream* openStream(std::shared_ptr stream, bool keepSource) override; void setListener( glm::vec3 position, diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index aef28d5f..37b06b7d 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -17,6 +17,75 @@ namespace audio { using namespace audio; +/// @brief pcm source that does not initialize buffer +class PCMVoidSource : public PCMStream { + size_t totalSamples; + size_t remain; + uint sampleRate; + bool seekable; + bool closed = false; +public: + PCMVoidSource(size_t totalSamples, uint sampleRate, bool seekable) + : totalSamples(totalSamples), + remain(totalSamples), + sampleRate(sampleRate), + seekable(seekable) + {} + + size_t read(char* buffer, size_t bufferSize, bool loop) override { + if (closed) { + return 0; + } + if (!seekable || loop) { + return bufferSize; + } + size_t n = std::min(bufferSize, totalSamples); + remain -= n; + return n; + } + + void close() override { + closed = true; + } + + bool isOpen() const override { + return !closed; + } + + size_t getTotalSamples() const override { + return totalSamples; + } + + duration_t getTotalDuration() const { + return static_cast(totalSamples) / + static_cast(sampleRate); + } + + uint getChannels() const override { + return 1; + } + + uint getSampleRate() const override { + return sampleRate; + } + + uint getBitsPerSample() const override { + return 8; + } + + bool isSeekable() const override { + return seekable; + } + + void seek(size_t position) override { + if (closed || !seekable) { + return; + } + position %= totalSamples; + remain = totalSamples - position; + } +}; + void audio::initialize(bool enabled) { if (enabled) { backend = ALAudio::create(); @@ -28,6 +97,9 @@ void audio::initialize(bool enabled) { } PCM* audio::loadPCM(const fs::path& file, bool headerOnly) { + if (!fs::exists(file)) { + throw std::runtime_error("file not found '"+file.u8string()+"'"); + } std::string ext = file.extension().u8string(); if (ext == ".wav" || ext == ".WAV") { return wav::load_pcm(file, headerOnly); @@ -39,7 +111,7 @@ PCM* audio::loadPCM(const fs::path& file, bool headerOnly) { Sound* audio::loadSound(const fs::path& file, bool keepPCM) { std::shared_ptr pcm(loadPCM(file, !keepPCM && backend->isDummy())); - return backend->createSound(pcm, keepPCM); + return createSound(pcm, keepPCM); } Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { @@ -54,6 +126,26 @@ PCMStream* audio::openPCMStream(const fs::path& file) { throw std::runtime_error("unsupported audio stream format"); } +Stream* audio::openStream(const fs::path& file, bool keepSource) { + if (!keepSource && backend->isDummy()) { + auto header = loadPCM(file, true); + // using void source sized as audio instead of actual audio file + return openStream( + std::make_shared(header->totalSamples, header->sampleRate, header->seekable), + keepSource + ); + } + return openStream( + std::shared_ptr(openPCMStream(file)), + keepSource + ); +} + +Stream* audio::openStream(std::shared_ptr stream, bool keepSource) { + return backend->openStream(stream, keepSource); +} + + void audio::setListener( glm::vec3 position, glm::vec3 velocity, diff --git a/src/audio/audio.h b/src/audio/audio.h index 93ff6825..ac6cf4b9 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -35,18 +35,21 @@ namespace audio { uint8_t channels; uint8_t bitsPerSample; uint sampleRate; + bool seekable; PCM( std::vector data, size_t totalSamples, uint8_t channels, uint8_t bitsPerSample, - uint sampleRate + uint sampleRate, + bool seekable ) : data(std::move(data)), totalSamples(totalSamples), channels(channels), bitsPerSample(bitsPerSample), - sampleRate(sampleRate) {} + sampleRate(sampleRate), + seekable(seekable) {} inline size_t countSamplesMono() const { return totalSamples / channels; @@ -74,6 +77,9 @@ namespace audio { /// @brief Close stream virtual void close()=0; + /// @brief Check if stream is open + virtual bool isOpen() const=0; + /// @brief Get total samples number if seekable or 0 virtual size_t getTotalSamples() const=0; @@ -105,10 +111,19 @@ namespace audio { public: virtual ~Stream() {}; + /// @brief Get pcm data source + /// @return PCM stream or nullptr if audio::openStream + /// keepSource argument is set to false + virtual std::shared_ptr getSource() const = 0; + /// @brief Create new speaker bound to the Stream /// and having high priority /// @return speaker id or 0 - virtual speakerid_t createSpeaker() = 0; + virtual Speaker* createSpeaker() = 0; + + /// @brief Unbind previous speaker and bind new speaker to the stream + /// @param speaker speaker id or 0 if all you need is unbind speaker + virtual void bindSpeaker(speakerid_t speaker) = 0; /// @brief Get id of the bound speaker /// @return speaker id or 0 if no speaker bound @@ -230,14 +245,13 @@ namespace audio { virtual ~Backend() {}; virtual Sound* createSound(std::shared_ptr pcm, bool keepPCM) = 0; - + virtual Stream* openStream(std::shared_ptr stream, bool keepSource) = 0; virtual void setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up ) = 0; - virtual void update(double delta) = 0; /// @brief Check if backend is an abstraction that does not internally @@ -275,6 +289,18 @@ namespace audio { /// @return new PCMStream instance extern PCMStream* openPCMStream(const fs::path& file); + /// @brief Open new audio stream from file + /// @param file audio file path + /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource + /// @return new Stream instance + extern Stream* openStream(const fs::path& file, bool keepSource); + + /// @brief Open new audio stream from source + /// @param stream PCM data source + /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource + /// @return new Stream instance + extern Stream* openStream(std::shared_ptr stream, bool keepSource); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index 6e94cdd1..dfe60177 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -33,7 +33,8 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { vorbis_info* info = ov_info(&vf, -1); uint channels = info->channels; uint sampleRate = info->rate; - size_t totalSamples = ov_seekable(&vf) ? ov_pcm_total(&vf, -1) : 0; + bool seekable = ov_seekable(&vf); + size_t totalSamples = seekable ? ov_pcm_total(&vf, -1) : 0; if (!headerOnly) { const int bufferSize = 4096; @@ -54,7 +55,7 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { totalSamples = data.size(); } ov_clear(&vf); - return new PCM(std::move(data), totalSamples, channels, 16, sampleRate); + return new PCM(std::move(data), totalSamples, channels, 16, sampleRate, seekable); } class OggStream : public PCMStream { @@ -82,6 +83,9 @@ public: } size_t read(char* buffer, size_t bufferSize, bool loop) { + if (closed) { + return 0; + } int bitstream; long bytes = 0; size_t size = 0; @@ -108,8 +112,14 @@ public: } void close() { - ov_clear(&vf); - closed = true; + if (!closed) { + ov_clear(&vf); + closed = true; + } + } + + bool isOpen() const { + return !closed; } size_t getTotalSamples() const { @@ -138,7 +148,7 @@ public: } void seek(size_t position) { - if (seekable) { + if (!closed && seekable) { ov_raw_seek(&vf, position); } } diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index f08804d9..578ddb21 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -114,5 +114,5 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) { throw std::runtime_error("could not load wav data of '"+file.u8string()+"'"); } } - return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate); + return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate, true); }