From ee5ebaf8db894bd29e9a83ecb3fa634a8194d41e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 29 Feb 2024 00:40:03 +0300 Subject: [PATCH] audio streaming interfaces update --- src/audio/audio.cpp | 9 ++++ src/audio/audio.h | 38 +++++++++++++++- src/coders/ogg.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++-- src/coders/ogg.h | 3 +- 4 files changed, 148 insertions(+), 6 deletions(-) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 1b927b0b..aef28d5f 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -46,6 +46,14 @@ Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { return backend->createSound(pcm, keepPCM); } +PCMStream* audio::openPCMStream(const fs::path& file) { + std::string ext = file.extension().u8string(); + if (ext == ".ogg" || ext == ".OGG") { + return ogg::create_stream(file); + } + throw std::runtime_error("unsupported audio stream format"); +} + void audio::setListener( glm::vec3 position, glm::vec3 velocity, @@ -121,6 +129,7 @@ void audio::update(double delta) { } void audio::close() { + speakers.clear(); delete backend; backend = nullptr; } diff --git a/src/audio/audio.h b/src/audio/audio.h index 48857e3b..93ff6825 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -12,7 +12,7 @@ namespace fs = std::filesystem; namespace audio { using speakerid_t = int64_t; /// @brief duration unit is second - using duration_t = float; + using duration_t = double; constexpr inline int PRIORITY_LOW = 0; constexpr inline int PRIORITY_NORMAL = 5; @@ -58,18 +58,46 @@ namespace audio { } }; - /// @brief PCM data streaming interface + /// @brief audio::PCMStream is a data source for audio::Stream class PCMStream { public: virtual ~PCMStream() {}; + + /// @brief Read samples data to buffer + /// @param buffer destination buffer + /// @param bufferSize destination buffer size + /// @param loop loop stream (seek to start when end reached) + /// @return size of data received + /// (always equals bufferSize if seekable and looped) virtual size_t read(char* buffer, size_t bufferSize, bool loop)=0; + + /// @brief Close stream virtual void close()=0; + /// @brief Get total samples number if seekable or 0 virtual size_t getTotalSamples() const=0; + + /// @brief Get total audio track duration if seekable or 0.0 virtual duration_t getTotalDuration() const=0; + + /// @brief Get number of audio channels + /// @return 1 if mono, 2 if stereo virtual uint getChannels() const=0; + + /// @brief Get audio sampling frequency + /// @return number of mono samples per second virtual uint getSampleRate() const=0; + + /// @brief Get number of bits per mono sample + /// @return 8 or 16 virtual uint getBitsPerSample() const=0; + + /// @brief Check if the stream does support seek feature + virtual bool isSeekable() const=0; + + /// @brief Move playhead to the selected sample number + /// @param position selected sample number + virtual void seek(size_t position) = 0; }; /// @brief Audio streaming interface @@ -241,6 +269,12 @@ namespace audio { /// @return new Sound instance extern Sound* createSound(std::shared_ptr pcm, bool keepPCM); + /// @brief Open new PCM stream from file + /// @param file audio file path + /// @throws std::runtime_error if I/O error ocurred or format is unknown + /// @return new PCMStream instance + extern PCMStream* openPCMStream(const fs::path& file); + /// @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 d25a9bec..6e94cdd1 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -7,6 +7,8 @@ #include "../audio/audio.h" #include "../typedefs.h" +using namespace audio; + static inline const char* vorbis_error_message(int code) { switch (code) { case 0: return "no error"; @@ -31,7 +33,7 @@ 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_pcm_total(&vf, -1); + size_t totalSamples = ov_seekable(&vf) ? ov_pcm_total(&vf, -1) : 0; if (!headerOnly) { const int bufferSize = 4096; @@ -49,8 +51,104 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { data.insert(data.end(), std::begin(buffer), std::begin(buffer)+ret); } } + totalSamples = data.size(); } - ov_clear(&vf); - return new audio::PCM(std::move(data), totalSamples, channels, 16, sampleRate); + return new PCM(std::move(data), totalSamples, channels, 16, sampleRate); +} + +class OggStream : public PCMStream { + OggVorbis_File vf; + bool closed = false; + uint channels; + uint sampleRate; + size_t totalSamples = 0; + bool seekable; +public: + OggStream(OggVorbis_File vf) : vf(std::move(vf)) { + vorbis_info* info = ov_info(&vf, -1); + channels = info->channels; + sampleRate = info->rate; + seekable = ov_seekable(&vf); + if (seekable) { + totalSamples = ov_pcm_total(&vf, -1); + } + } + + ~OggStream() { + if (!closed) { + close(); + } + } + + size_t read(char* buffer, size_t bufferSize, bool loop) { + int bitstream; + long bytes = 0; + size_t size = 0; + do { + do { + bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream); + if (bytes < 0) { + std::cerr << vorbis_error_message(bytes) << std::endl; + continue; + } + size += bytes; + bufferSize -= bytes; + buffer += bytes; + } while (bytes > 0); + + if (loop) { + seek(0); + } + if (bufferSize == 0) { + return size; + } + } while (loop); + return size; + } + + void close() { + ov_clear(&vf); + closed = true; + } + + size_t getTotalSamples() const { + return totalSamples; + } + + duration_t getTotalDuration() const { + return static_cast(totalSamples) / + static_cast(sampleRate); + } + + uint getChannels() const { + return channels; + } + + uint getSampleRate() const { + return sampleRate; + } + + uint getBitsPerSample() const { + return 16; + } + + bool isSeekable() const { + return seekable; + } + + void seek(size_t position) { + if (seekable) { + ov_raw_seek(&vf, position); + } + } +}; + +PCMStream* ogg::create_stream(const std::filesystem::path& file) { + OggVorbis_File vf; + int code; + if ((code = ov_fopen(file.u8string().c_str(), &vf))) { + throw std::runtime_error(vorbis_error_message(code)); + } + return new OggStream(std::move(vf)); } diff --git a/src/coders/ogg.h b/src/coders/ogg.h index dc9cfe33..828f2be5 100644 --- a/src/coders/ogg.h +++ b/src/coders/ogg.h @@ -5,11 +5,12 @@ namespace audio { struct PCM; - class Stream; + class PCMStream; } namespace ogg { extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly); + extern audio::PCMStream* create_stream(const std::filesystem::path& file); } #endif // CODERS_OGG_H_