From 7b637a95d90da3046b76e37eb54bae9045e3cb94 Mon Sep 17 00:00:00 2001 From: Attila Uygun Date: Sat, 27 May 2023 22:28:38 +0200 Subject: [PATCH] Refactoring audio code - Add AudioBus - Move resampling to AudioBus - Move SincResampler to engine/audio --- build/android/app/CMakeLists.txt | 3 +- build/linux/Makefile | 3 +- src/engine/audio/audio_bus.cc | 112 +++++++++++ src/engine/audio/audio_bus.h | 45 +++++ src/engine/audio/audio_mixer.cc | 96 +++++----- src/engine/audio/audio_mixer.h | 8 +- src/engine/audio/audio_sink.h | 2 +- src/engine/audio/audio_sink_alsa.cc | 2 +- src/engine/audio/audio_sink_alsa.h | 4 +- src/engine/audio/audio_sink_oboe.cc | 2 +- src/engine/audio/audio_sink_oboe.h | 2 +- src/{base => engine/audio}/sinc_resampler.cc | 14 +- src/{base => engine/audio}/sinc_resampler.h | 20 +- src/engine/engine.cc | 2 +- src/engine/engine.h | 2 +- src/engine/sound.cc | 190 ++++--------------- src/engine/sound.h | 61 ++---- src/engine/sound_player.cc | 10 +- src/engine/sound_player.h | 7 +- 19 files changed, 294 insertions(+), 291 deletions(-) create mode 100644 src/engine/audio/audio_bus.cc create mode 100644 src/engine/audio/audio_bus.h rename src/{base => engine/audio}/sinc_resampler.cc (98%) rename src/{base => engine/audio}/sinc_resampler.h (95%) diff --git a/build/android/app/CMakeLists.txt b/build/android/app/CMakeLists.txt index e10cd65..a0bdd94 100644 --- a/build/android/app/CMakeLists.txt +++ b/build/android/app/CMakeLists.txt @@ -49,7 +49,6 @@ project(kaliber) add_library(kaliber SHARED ../../../src/base/collusion_test.cc ../../../src/base/log.cc - ../../../src/base/sinc_resampler.cc ../../../src/base/task_runner.cc ../../../src/base/thread_pool.cc ../../../src/base/timer.cc @@ -62,8 +61,10 @@ add_library(kaliber SHARED ../../../src/demo/sky_quad.cc ../../../src/engine/animatable.cc ../../../src/engine/animator.cc + ../../../src/engine/audio/audio_bus.cc ../../../src/engine/audio/audio_mixer.cc ../../../src/engine/audio/audio_sink_oboe.cc + ../../../src/engine/audio/sinc_resampler.cc ../../../src/engine/drawable.cc ../../../src/engine/engine.cc ../../../src/engine/font.cc diff --git a/build/linux/Makefile b/build/linux/Makefile index 8da112c..e0384d9 100644 --- a/build/linux/Makefile +++ b/build/linux/Makefile @@ -78,7 +78,6 @@ objs_from_src_in = $(call objs_from_src, $(shell find $(1) -name "*.cc" -o -name BASE_SRC := \ $(SRC_ROOT)/base/collusion_test.cc \ $(SRC_ROOT)/base/log.cc \ - $(SRC_ROOT)/base/sinc_resampler.cc \ $(SRC_ROOT)/base/task_runner.cc \ $(SRC_ROOT)/base/thread_pool.cc \ $(SRC_ROOT)/base/timer.cc @@ -95,8 +94,10 @@ $(BASE_LIB): $(BASE_OBJS) ENGINE_SRC := \ $(SRC_ROOT)/engine/animatable.cc \ $(SRC_ROOT)/engine/animator.cc \ + $(SRC_ROOT)/engine/audio/audio_bus.cc \ $(SRC_ROOT)/engine/audio/audio_mixer.cc \ $(SRC_ROOT)/engine/audio/audio_sink_alsa.cc \ + $(SRC_ROOT)/engine/audio/sinc_resampler.cc \ $(SRC_ROOT)/engine/drawable.cc \ $(SRC_ROOT)/engine/engine.cc \ $(SRC_ROOT)/engine/font.cc \ diff --git a/src/engine/audio/audio_bus.cc b/src/engine/audio/audio_bus.cc new file mode 100644 index 0000000..78c7529 --- /dev/null +++ b/src/engine/audio/audio_bus.cc @@ -0,0 +1,112 @@ +#include "engine/audio/audio_bus.h" + +#include "base/log.h" +#include "engine/audio/sinc_resampler.h" +#include "engine/engine.h" + +using namespace base; + +namespace eng { + +namespace { + +template +std::array, 2> Deinterleave(size_t num_channels, + size_t num_samples, + float* input_buffer) { + std::array, 2> channels; + + if (num_channels == 1) { + // Single channel. + if constexpr (std::is_same::value) { + channels[0] = std::make_unique(num_samples); + memcpy(channels[0].get(), input_buffer, num_samples * sizeof(float)); + } else { + channels[0] = std::make_unique(num_samples); + for (int i = 0; i < num_samples; ++i) + channels[0].get()[i] = static_cast(input_buffer[i]); + } + } else { + // Deinterleave into separate channels. + channels[0] = std::make_unique(num_samples); + channels[1] = std::make_unique(num_samples); + for (size_t i = 0, j = 0; i < num_samples * 2; i += 2) { + channels[0].get()[j] = static_cast(input_buffer[i]); + channels[1].get()[j++] = static_cast(input_buffer[i + 1]); + } + } + + return channels; +} + +std::unique_ptr CreateResampler(int src_samle_rate, + int dst_sample_rate, + size_t num_samples) { + const double io_ratio = static_cast(src_samle_rate) / + static_cast(dst_sample_rate); + auto resampler = std::make_unique(io_ratio, num_samples); + resampler->PrimeWithSilence(); + return resampler; +} + +} // namespace + +AudioBus::AudioBus() = default; +AudioBus::~AudioBus() = default; + +void AudioBus::SetAudioConfig(size_t num_channels, size_t sample_rate) { + num_channels_ = num_channels; + sample_rate_ = sample_rate; +} + +void AudioBus::FromInterleaved(std::unique_ptr input_buffer, + size_t samples_per_channel) { + auto channels = Deinterleave(num_channels_, samples_per_channel, + input_buffer.get()); + + size_t hw_sample_rate = Engine::Get().GetAudioHardwareSampleRate(); + + if (hw_sample_rate == sample_rate_) { + // No need for resmapling. + channel_data_[0] = std::move(channels[0]); + if (num_channels_ == 2) + channel_data_[1] = std::move(channels[1]); + samples_per_channel_ = samples_per_channel; + } else { + if (!resampler_[0]) { + for (size_t i = 0; i < num_channels_; ++i) { + resampler_[i] = + CreateResampler(sample_rate_, hw_sample_rate, samples_per_channel); + } + } + + size_t num_resampled_samples = + (size_t)(((float)hw_sample_rate / (float)sample_rate_) * + samples_per_channel); + DCHECK(num_resampled_samples <= (size_t)resampler_[0]->ChunkSize()); + + if (!channel_data_[0]) { + channel_data_[0] = std::make_unique(num_resampled_samples); + if (num_channels_ == 2) + channel_data_[1] = std::make_unique(num_resampled_samples); + } + samples_per_channel_ = num_resampled_samples; + + // Resample to match the system sample rate. + for (size_t i = 0; i < num_channels_; ++i) { + resampler_[i]->Resample(num_resampled_samples, channel_data_[i].get(), + [&](int frames, float* destination) { + memcpy(destination, channels[i].get(), + frames * sizeof(float)); + }); + } + + if (IsEndOfStream()) { + // We are done with the resampler. + for (size_t i = 0; i < num_channels_; ++i) + resampler_[i].reset(); + } + } +} + +} // namespace eng \ No newline at end of file diff --git a/src/engine/audio/audio_bus.h b/src/engine/audio/audio_bus.h new file mode 100644 index 0000000..f760f45 --- /dev/null +++ b/src/engine/audio/audio_bus.h @@ -0,0 +1,45 @@ +#ifndef ENGINE_AUDIO_AUDIO_BUS_H +#define ENGINE_AUDIO_AUDIO_BUS_H + +#include + +namespace eng { + +class SincResampler; + +class AudioBus { + public: + AudioBus(); + virtual ~AudioBus(); + + virtual void Stream(bool loop) = 0; + virtual void SwapBuffers() = 0; + virtual void ResetStream() = 0; + virtual bool IsEndOfStream() const = 0; + + float* GetChannelData(int channel) const { + return channel_data_[channel].get(); + } + + size_t samples_per_channel() const { return samples_per_channel_; } + int sample_rate() const { return sample_rate_; } + int num_channels() const { return num_channels_; } + + protected: + void SetAudioConfig(size_t num_channels, size_t sample_rate); + + void FromInterleaved(std::unique_ptr input_buffer, + size_t samples_per_channel); + + private: + std::unique_ptr channel_data_[2]; + size_t samples_per_channel_ = 0; + size_t sample_rate_ = 0; + size_t num_channels_ = 0; + + std::unique_ptr resampler_[2]; +}; + +} // namespace eng + +#endif // ENGINE_AUDIO_AUDIO_BUS_H diff --git a/src/engine/audio/audio_mixer.cc b/src/engine/audio/audio_mixer.cc index c2ea4fd..55d32e6 100644 --- a/src/engine/audio/audio_mixer.cc +++ b/src/engine/audio/audio_mixer.cc @@ -5,7 +5,7 @@ #include "base/log.h" #include "base/task_runner.h" #include "base/thread_pool.h" -#include "engine/sound.h" +#include "engine/audio/audio_bus.h" #if defined(__ANDROID__) #include "engine/audio/audio_sink_oboe.h" @@ -49,7 +49,7 @@ void AudioMixer::DestroyResource(uint64_t resource_id) { } void AudioMixer::Play(uint64_t resource_id, - std::shared_ptr sound, + std::shared_ptr sound, float amplitude, bool reset_pos) { if (!audio_enabled_) @@ -76,6 +76,8 @@ void AudioMixer::Play(uint64_t resource_id, it->second->src_index = 0; it->second->accumulator = 0; sound->ResetStream(); + } else if (it->second->src_index >= sound->samples_per_channel()) { + return; } it->second->active = true; @@ -161,7 +163,7 @@ void AudioMixer::Resume() { audio_sink_->Resume(); } -int AudioMixer::GetHardwareSampleRate() { +size_t AudioMixer::GetHardwareSampleRate() { return audio_sink_->GetHardwareSampleRate(); } @@ -182,11 +184,12 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { if (flags & kStopped) { marked_for_removal = true; } else { - const float* src[2] = {sound->GetBuffer(0), sound->GetBuffer(1)}; + const float* src[2] = {sound->GetChannelData(0), + sound->GetChannelData(1)}; if (!src[1]) src[1] = src[0]; // mono. - size_t num_samples = sound->GetNumSamples(); + size_t num_samples = sound->samples_per_channel(); size_t src_index = it->get()->src_index; size_t step = it->get()->step.load(std::memory_order_relaxed); size_t accumulator = it->get()->accumulator; @@ -195,71 +198,59 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { it->get()->amplitude_inc.load(std::memory_order_relaxed); float max_amplitude = it->get()->max_amplitude.load(std::memory_order_relaxed); - size_t channel_offset = - (flags & kSimulateStereo) && !sound->is_streaming_sound() - ? sound->sample_rate() / 10 - : 0; + (flags & kSimulateStereo) ? sound->sample_rate() / 10 : 0; - DCHECK(num_samples || sound->is_streaming_sound()); + DCHECK(num_samples > 0); for (size_t i = 0; i < num_frames * kChannelCount;) { - if (num_samples) { - // Mix the 1st channel. - output_buffer[i++] += src[0][src_index] * amplitude; + // Mix the 1st channel. + output_buffer[i++] += src[0][src_index] * amplitude; - // Mix the 2nd channel. Offset the source index for stereo simulation. - size_t ind = channel_offset + src_index; - if (ind < num_samples) - output_buffer[i++] += src[1][ind] * amplitude; - else if (flags & kLoop) - output_buffer[i++] += src[1][ind % num_samples] * amplitude; - else - i++; + // Mix the 2nd channel. Offset the source index for stereo simulation. + size_t ind = channel_offset + src_index; + if (ind < num_samples) + output_buffer[i++] += src[1][ind] * amplitude; + else if (flags & kLoop) + output_buffer[i++] += src[1][ind % num_samples] * amplitude; + else + i++; - // Apply amplitude modification. - amplitude += amplitude_inc; - if (amplitude <= 0) { - marked_for_removal = true; - break; - } else if (amplitude > max_amplitude) { - amplitude = max_amplitude; - } - - // Advance source index. Apply basic resampling for variations. - accumulator += step; - src_index += accumulator / 100; - accumulator %= 100; + // Apply amplitude modification. + amplitude += amplitude_inc; + if (amplitude <= 0) { + marked_for_removal = true; + break; + } else if (amplitude > max_amplitude) { + amplitude = max_amplitude; } + // Advance source index. Apply basic resampling for variations. + accumulator += step; + src_index += accumulator / 100; + accumulator %= 100; + // Remove, loop or stream if the source data is consumed if (src_index >= num_samples) { - if (num_samples) - src_index %= num_samples; + src_index %= num_samples; - if (!sound->is_streaming_sound()) { - if (!(flags & kLoop)) { - marked_for_removal = true; - break; - } - } else if (!it->get()->streaming_in_progress.load( - std::memory_order_acquire)) { - if (sound->eof()) { - DCHECK((flags & kLoop) == 0); - marked_for_removal = true; - break; - } + if (sound->IsEndOfStream()) { + marked_for_removal = true; + break; + } + if (!it->get()->streaming_in_progress.load( + std::memory_order_acquire)) { it->get()->streaming_in_progress.store(true, std::memory_order_relaxed); // Swap buffers and start streaming in background. sound->SwapBuffers(); - src[0] = sound->GetBuffer(0); - src[1] = sound->GetBuffer(1); + src[0] = sound->GetChannelData(0); + src[1] = sound->GetChannelData(1); if (!src[1]) src[1] = src[0]; // mono. - num_samples = sound->GetNumSamples(); + num_samples = sound->samples_per_channel(); ThreadPool::Get().PostTask( HERE, @@ -284,8 +275,7 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { } for (auto it = end_list_.begin(); it != end_list_.end();) { - if ((!it->get()->sound->is_streaming_sound() || - !it->get()->streaming_in_progress.load(std::memory_order_relaxed))) { + if (!it->get()->streaming_in_progress.load(std::memory_order_relaxed)) { main_thread_task_runner_->PostTask( HERE, std::bind(&AudioMixer::EndCallback, this, *it)); it = end_list_.erase(it); diff --git a/src/engine/audio/audio_mixer.h b/src/engine/audio/audio_mixer.h index d60e082..7bcc2aa 100644 --- a/src/engine/audio/audio_mixer.h +++ b/src/engine/audio/audio_mixer.h @@ -17,7 +17,7 @@ class TaskRunner; namespace eng { class AudioSink; -class Sound; +class AudioBus; // Mix and render audio with low overhead. A platform specific AudioSink // implementation is expected to periodically call RenderAudio() in a background @@ -31,7 +31,7 @@ class AudioMixer : public AudioSinkDelegate { void DestroyResource(uint64_t resource_id); void Play(uint64_t resource_id, - std::shared_ptr sound, + std::shared_ptr sound, float amplitude, bool reset_pos); void Stop(uint64_t resource_id); @@ -48,7 +48,7 @@ class AudioMixer : public AudioSinkDelegate { void Suspend(); void Resume(); - int GetHardwareSampleRate(); + size_t GetHardwareSampleRate(); private: enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 }; @@ -62,7 +62,7 @@ class AudioMixer : public AudioSinkDelegate { base::Closure restart_cb; // Initialized by main thread, used by audio thread. - std::shared_ptr sound; + std::shared_ptr sound; size_t src_index = 0; size_t accumulator = 0; float amplitude = 1.0f; diff --git a/src/engine/audio/audio_sink.h b/src/engine/audio/audio_sink.h index 3b26632..d346ed6 100644 --- a/src/engine/audio/audio_sink.h +++ b/src/engine/audio/audio_sink.h @@ -15,7 +15,7 @@ class AudioSink { virtual void Suspend() = 0; virtual void Resume() = 0; - virtual int GetHardwareSampleRate() = 0; + virtual size_t GetHardwareSampleRate() = 0; private: AudioSink(const AudioSink&) = delete; diff --git a/src/engine/audio/audio_sink_alsa.cc b/src/engine/audio/audio_sink_alsa.cc index 6bf47fb..55b6bf1 100644 --- a/src/engine/audio/audio_sink_alsa.cc +++ b/src/engine/audio/audio_sink_alsa.cc @@ -152,7 +152,7 @@ void AudioSinkAlsa::Resume() { suspend_audio_thread_.store(false, std::memory_order_relaxed); } -int AudioSinkAlsa::GetHardwareSampleRate() { +size_t AudioSinkAlsa::GetHardwareSampleRate() { return sample_rate_; } diff --git a/src/engine/audio/audio_sink_alsa.h b/src/engine/audio/audio_sink_alsa.h index a115468..907fedf 100644 --- a/src/engine/audio/audio_sink_alsa.h +++ b/src/engine/audio/audio_sink_alsa.h @@ -22,7 +22,7 @@ class AudioSinkAlsa final : public AudioSink { void Suspend() final; void Resume() final; - int GetHardwareSampleRate() final; + size_t GetHardwareSampleRate() final; private: // Handle for the PCM device. @@ -33,7 +33,7 @@ class AudioSinkAlsa final : public AudioSink { std::atomic suspend_audio_thread_{false}; size_t num_channels_ = 0; - int sample_rate_ = 0; + size_t sample_rate_ = 0; size_t period_size_ = 0; AudioSinkDelegate* delegate_ = nullptr; diff --git a/src/engine/audio/audio_sink_oboe.cc b/src/engine/audio/audio_sink_oboe.cc index 5e8edfe..570543f 100644 --- a/src/engine/audio/audio_sink_oboe.cc +++ b/src/engine/audio/audio_sink_oboe.cc @@ -29,7 +29,7 @@ void AudioSinkOboe::Resume() { stream_->start(); } -int AudioSinkOboe::GetHardwareSampleRate() { +size_t AudioSinkOboe::GetHardwareSampleRate() { return stream_->getSampleRate(); } diff --git a/src/engine/audio/audio_sink_oboe.h b/src/engine/audio/audio_sink_oboe.h index 7d02f7f..2470edd 100644 --- a/src/engine/audio/audio_sink_oboe.h +++ b/src/engine/audio/audio_sink_oboe.h @@ -22,7 +22,7 @@ class AudioSinkOboe final : public AudioSink { void Suspend() final; void Resume() final; - int GetHardwareSampleRate() final; + size_t GetHardwareSampleRate() final; private: class StreamCallback final : public oboe::AudioStreamCallback { diff --git a/src/base/sinc_resampler.cc b/src/engine/audio/sinc_resampler.cc similarity index 98% rename from src/base/sinc_resampler.cc rename to src/engine/audio/sinc_resampler.cc index 140dc90..c8465db 100644 --- a/src/base/sinc_resampler.cc +++ b/src/engine/audio/sinc_resampler.cc @@ -74,7 +74,7 @@ // Note: we're glossing over how the sub-sample handling works with // |virtual_source_idx_|, etc. -#include "base/sinc_resampler.h" +#include "engine/audio/sinc_resampler.h" #include #include @@ -94,7 +94,9 @@ #include #endif -namespace base { +using namespace base; + +namespace eng { namespace { @@ -257,14 +259,14 @@ void SincResampler::InitializeKernel() { for (int i = 0; i < kernel_size_; ++i) { const int idx = i + offset_idx * kernel_size_; const float pre_sinc = - base::kPiFloat * (i - kernel_size_ / 2 - subsample_offset); + kPiFloat * (i - kernel_size_ / 2 - subsample_offset); kernel_pre_sinc_storage_[idx] = pre_sinc; // Compute Blackman window, matching the offset of the sinc(). const float x = (i - subsample_offset) / kernel_size_; const float window = - static_cast(kA0 - kA1 * cos(2.0 * base::kPiDouble * x) + - kA2 * cos(4.0 * base::kPiDouble * x)); + static_cast(kA0 - kA1 * cos(2.0 * kPiDouble * x) + + kA2 * cos(4.0 * kPiDouble * x)); kernel_window_storage_[idx] = window; // Compute the sinc with offset, then window the sinc() function and store @@ -545,4 +547,4 @@ float SincResampler::Convolve_NEON(const int kernel_size, } #endif -} // namespace base +} // namespace eng diff --git a/src/base/sinc_resampler.h b/src/engine/audio/sinc_resampler.h similarity index 95% rename from src/base/sinc_resampler.h rename to src/engine/audio/sinc_resampler.h index b94f398..14cb174 100644 --- a/src/base/sinc_resampler.h +++ b/src/engine/audio/sinc_resampler.h @@ -1,16 +1,18 @@ // Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// +// Modified for Kaliber engine. -#ifndef BASE_SINC_RESAMPLER_H -#define BASE_SINC_RESAMPLER_H +#ifndef ENGINE_AUDIO__SINC_RESAMPLER_H +#define ENGINE_AUDIO__SINC_RESAMPLER_H #include #include #include "base/mem.h" -namespace base { +namespace eng { // SincResampler is a high-quality single-channel sample-rate converter. class SincResampler { @@ -163,12 +165,12 @@ class SincResampler { // Contains kKernelOffsetCount kernels back-to-back, each of size // `kernel_size_`. The kernel offsets are sub-sample shifts of a windowed sinc // shifted from 0.0 to 1.0 sample. - AlignedMemPtr kernel_storage_; - AlignedMemPtr kernel_pre_sinc_storage_; - AlignedMemPtr kernel_window_storage_; + base::AlignedMemPtr kernel_storage_; + base::AlignedMemPtr kernel_pre_sinc_storage_; + base::AlignedMemPtr kernel_window_storage_; // Data from the source is copied into this buffer for each processing pass. - AlignedMemPtr input_buffer_; + base::AlignedMemPtr input_buffer_; // Stores the runtime selection of which Convolve function to use. using ConvolveProc = @@ -184,6 +186,6 @@ class SincResampler { float* r4_; }; -} // namespace base +} // namespace eng -#endif // BASE_SINC_RESAMPLER_H +#endif // ENGINE_AUDIO__SINC_RESAMPLER_H diff --git a/src/engine/engine.cc b/src/engine/engine.cc index 91d1994..fc7e0e8 100644 --- a/src/engine/engine.cc +++ b/src/engine/engine.cc @@ -474,7 +474,7 @@ const std::string& Engine::GetSharedDataPath() const { return platform_->GetSharedDataPath(); } -int Engine::GetAudioHardwareSampleRate() { +size_t Engine::GetAudioHardwareSampleRate() { return audio_mixer_->GetHardwareSampleRate(); } diff --git a/src/engine/engine.h b/src/engine/engine.h index 3154388..d694e00 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -137,7 +137,7 @@ class Engine : public PlatformObserver { const std::string& GetSharedDataPath() const; - int GetAudioHardwareSampleRate(); + size_t GetAudioHardwareSampleRate(); bool IsMobile() const; diff --git a/src/engine/sound.cc b/src/engine/sound.cc index ba6edfe..316b097 100644 --- a/src/engine/sound.cc +++ b/src/engine/sound.cc @@ -3,7 +3,6 @@ #include #include "base/log.h" -#include "base/sinc_resampler.h" #define MINIMP3_ONLY_MP3 #define MINIMP3_ONLY_SIMD #define MINIMP3_FLOAT_OUTPUT @@ -15,53 +14,10 @@ using namespace base; -namespace { - -constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME * 10; - -template -std::array, 2> Deinterleave(size_t num_channels, - size_t num_samples, - float* input_buffer) { - std::array, 2> channels; - - if (num_channels == 1) { - // Single channel. - if constexpr (std::is_same::value) { - channels[0] = std::make_unique(num_samples); - memcpy(channels[0].get(), input_buffer, num_samples * sizeof(float)); - } else { - channels[0] = std::make_unique(num_samples); - for (int i = 0; i < num_samples; ++i) - channels[0].get()[i] = static_cast(input_buffer[i]); - } - } else { - // Deinterleave into separate channels. - channels[0] = std::make_unique(num_samples); - channels[1] = std::make_unique(num_samples); - for (size_t i = 0, j = 0; i < num_samples * 2; i += 2) { - channels[0].get()[j] = static_cast(input_buffer[i]); - channels[1].get()[j++] = static_cast(input_buffer[i + 1]); - } - } - - return channels; -} - -std::unique_ptr CreateResampler(int src_samle_rate, - int dst_sample_rate, - size_t num_samples) { - const double io_ratio = static_cast(src_samle_rate) / - static_cast(dst_sample_rate); - auto resampler = std::make_unique(io_ratio, num_samples); - resampler->PrimeWithSilence(); - return resampler; -} - -} // namespace - namespace eng { +constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME; + Sound::Sound() = default; Sound::~Sound() { @@ -92,164 +48,98 @@ bool Sound::Load(const std::string& file_name, bool stream) { return false; } - is_streaming_sound_ = stream; - - LOG << (is_streaming_sound_ ? "Streaming " : "Loaded ") << file_name << ". " + LOG << (stream ? "Streaming " : "Loaded ") << file_name << ". " << mp3_dec_->samples << " samples, " << mp3_dec_->info.channels << " channels, " << mp3_dec_->info.hz << " hz, " << "layer " << mp3_dec_->info.layer << ", " << "avg_bitrate_kbps " << mp3_dec_->info.bitrate_kbps; - num_channels_ = mp3_dec_->info.channels; - sample_rate_ = mp3_dec_->info.hz; - num_samples_back_ = cur_sample_back_ = 0; - eof_ = false; + SetAudioConfig(mp3_dec_->info.channels, mp3_dec_->info.hz); - DCHECK(num_channels_ > 0 && num_channels_ <= 2); + samples_per_channel_ = 0; + eos_ = false; - hw_sample_rate_ = Engine::Get().GetAudioHardwareSampleRate(); + DCHECK(mp3_dec_->info.channels > 0 && mp3_dec_->info.channels <= 2); - if (is_streaming_sound_) { - if (sample_rate_ != hw_sample_rate_) { - for (int i = 0; i < mp3_dec_->info.channels; ++i) { - resampler_[i] = - CreateResampler(sample_rate_, hw_sample_rate_, - (int)kMaxSamplesPerChunk / mp3_dec_->info.channels); - } - } - - // Fill up buffers. - StreamInternal(kMaxSamplesPerChunk, false); + if (stream) { + // Fill up the buffer. + StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false); SwapBuffers(); - StreamInternal(kMaxSamplesPerChunk, false); - + StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false); // No need to stream if sample fits into the buffer. - if (eof_) - is_streaming_sound_ = false; + if (eos_) + stream = false; } else { - if (sample_rate_ != hw_sample_rate_) { - for (int i = 0; i < mp3_dec_->info.channels; ++i) { - resampler_[i] = - CreateResampler(sample_rate_, hw_sample_rate_, - mp3_dec_->samples / mp3_dec_->info.channels); - } - } - // Decode entire file. StreamInternal(mp3_dec_->samples, false); SwapBuffers(); - eof_ = true; + eos_ = true; } - if (!is_streaming_sound_) { + if (!stream) { // We are done with decoding. encoded_data_.reset(); - for (int i = 0; i < mp3_dec_->info.channels; ++i) - resampler_[i].reset(); mp3dec_ex_close(mp3_dec_.get()); mp3_dec_.reset(); } + read_pos_ = 0; + return true; } -bool Sound::Stream(bool loop) { - DCHECK(is_streaming_sound_); - - return StreamInternal(kMaxSamplesPerChunk, loop); +void Sound::Stream(bool loop) { + DCHECK(mp3_dec_); + StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, loop); } void Sound::SwapBuffers() { - front_buffer_[0].swap(back_buffer_[0]); - front_buffer_[1].swap(back_buffer_[1]); - - cur_sample_front_ = cur_sample_back_; - num_samples_front_ = num_samples_back_; - num_samples_back_ = 0; + FromInterleaved(std::move(interleaved_data_), samples_per_channel_); + samples_per_channel_ = 0; } void Sound::ResetStream() { - if (is_streaming_sound_ && cur_sample_front_ != 0) { - // Seek to 0 and ivalidate decoded data. + if (mp3_dec_ && read_pos_ != 0) { + // Seek to 0 and stream. mp3dec_ex_seek(mp3_dec_.get(), 0); - eof_ = false; - num_samples_back_ = num_samples_front_ = 0; - cur_sample_front_ = cur_sample_back_ = 0; + eos_ = false; + StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false); + SwapBuffers(); + StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false); + read_pos_ = 0; } } -float* Sound::GetBuffer(int channel) const { - return front_buffer_[channel].get(); -} - -bool Sound::StreamInternal(size_t num_samples, bool loop) { +void Sound::StreamInternal(size_t num_samples, bool loop) { auto buffer = std::make_unique(num_samples); size_t samples_read_per_channel = 0; - cur_sample_back_ = mp3_dec_->cur_sample; + read_pos_ = mp3_dec_->cur_sample; for (;;) { size_t samples_read = mp3dec_ex_read(mp3_dec_.get(), buffer.get(), num_samples); if (samples_read != num_samples && mp3_dec_->last_error) { LOG << "mp3 decode error: " << mp3_dec_->last_error; - eof_ = true; - return false; + break; } - samples_read_per_channel = samples_read / mp3_dec_->info.channels; - if (!samples_read_per_channel && loop) { + if (samples_read == 0 && loop) { mp3dec_ex_seek(mp3_dec_.get(), 0); loop = false; continue; } + + samples_read_per_channel = samples_read / mp3_dec_->info.channels; break; } - if (samples_read_per_channel) { - Preprocess(std::move(buffer), samples_read_per_channel); + if (samples_read_per_channel > 0) { + interleaved_data_ = std::move(buffer); + samples_per_channel_ = samples_read_per_channel; } else { - num_samples_back_ = 0; - eof_ = true; - } - - return true; -} - -void Sound::Preprocess(std::unique_ptr input_buffer, - size_t samples_per_channel) { - auto channels = Deinterleave(num_channels_, samples_per_channel, - input_buffer.get()); - - if (hw_sample_rate_ == sample_rate_) { - // No need for resmapling. - back_buffer_[0] = std::move(channels[0]); - if (num_channels_ == 2) - back_buffer_[1] = std::move(channels[1]); - num_samples_back_ = samples_per_channel; - } else { - size_t num_resampled_samples = resampler_[0]->ChunkSize(); - DCHECK(num_resampled_samples == - (size_t)(((float)hw_sample_rate_ / (float)sample_rate_) * - samples_per_channel)); - - if (!back_buffer_[0]) { - if (max_samples_ < num_resampled_samples) - max_samples_ = num_resampled_samples; - back_buffer_[0] = std::make_unique(max_samples_); - if (num_channels_ == 2) - back_buffer_[1] = std::make_unique(max_samples_); - } - num_samples_back_ = num_resampled_samples; - - // Resample to match the system sample rate. - for (size_t i = 0; i < num_channels_; ++i) { - resampler_[i]->Resample(num_resampled_samples, back_buffer_[i].get(), - [&](int frames, float* destination) { - memcpy(destination, channels[i].get(), - frames * sizeof(float)); - }); - } + samples_per_channel_ = 0; + eos_ = true; } } diff --git a/src/engine/sound.h b/src/engine/sound.h index e6ef006..5f349ac 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -5,11 +5,9 @@ #include #include -typedef struct mp3dec_ex mp3dec_ex_t; +#include "engine/audio/audio_bus.h" -namespace base { -class SincResampler; -} // namespace base +typedef struct mp3dec_ex mp3dec_ex_t; namespace eng { @@ -17,61 +15,30 @@ namespace eng { // files. Resamples the decoded audio to match the system sample rate if // necessary. Non-streaming sounds Can be shared between multiple audio // resources and played simultaneously. -class Sound { +class Sound final : public AudioBus { public: Sound(); - ~Sound(); + ~Sound() final; bool Load(const std::string& file_name, bool stream); - bool Stream(bool loop); - - void SwapBuffers(); - - void ResetStream(); - - float* GetBuffer(int channel) const; - - size_t GetNumSamples() const { return num_samples_front_; } - - size_t num_channels() const { return num_channels_; } - int sample_rate() const { return sample_rate_; } - - bool is_streaming_sound() const { return is_streaming_sound_; } - - bool eof() const { return eof_; } + // AudioBus interface + void Stream(bool loop) final; + void SwapBuffers() final; + void ResetStream() final; + bool IsEndOfStream() const final { return eos_; } private: // Buffer holding decoded audio. - std::unique_ptr back_buffer_[2]; - std::unique_ptr front_buffer_[2]; - - size_t num_samples_back_ = 0; - size_t num_samples_front_ = 0; - size_t max_samples_ = 0; - - size_t cur_sample_front_ = 0; - size_t cur_sample_back_ = 0; - - size_t num_channels_ = 0; - int sample_rate_ = 0; - - int hw_sample_rate_ = 0; + std::unique_ptr interleaved_data_; + size_t samples_per_channel_ = 0; std::unique_ptr encoded_data_; - std::unique_ptr mp3_dec_; + uint64_t read_pos_ = 0; + bool eos_ = false; - std::unique_ptr resampler_[2]; - - bool eof_ = false; - - bool is_streaming_sound_ = false; - - bool StreamInternal(size_t num_samples, bool loop); - - void Preprocess(std::unique_ptr input_buffer, - size_t samples_per_channel); + void StreamInternal(size_t num_samples, bool loop); }; } // namespace eng diff --git a/src/engine/sound_player.cc b/src/engine/sound_player.cc index 998b189..b7e16ca 100644 --- a/src/engine/sound_player.cc +++ b/src/engine/sound_player.cc @@ -2,9 +2,9 @@ #include "base/interpolation.h" #include "base/log.h" +#include "engine/audio/audio_bus.h" #include "engine/audio/audio_mixer.h" #include "engine/engine.h" -#include "engine/sound.h" using namespace base; @@ -17,16 +17,10 @@ SoundPlayer::~SoundPlayer() { Engine::Get().GetAudioMixer()->DestroyResource(resource_id_); } -void SoundPlayer::SetSound(std::shared_ptr sound) { - CHECK(!sound->is_streaming_sound()) << "Streaming sound cannot be shared."; - +void SoundPlayer::SetSound(std::shared_ptr sound) { sound_ = sound; } -void SoundPlayer::SetSound(std::unique_ptr sound) { - sound_ = std::move(sound); -} - void SoundPlayer::Play(bool loop, float fade_in_duration) { if (!sound_) return; diff --git a/src/engine/sound_player.h b/src/engine/sound_player.h index a1adfd7..3380827 100644 --- a/src/engine/sound_player.h +++ b/src/engine/sound_player.h @@ -7,15 +7,14 @@ namespace eng { -class Sound; +class AudioBus; class SoundPlayer { public: SoundPlayer(); ~SoundPlayer(); - void SetSound(std::shared_ptr sound); - void SetSound(std::unique_ptr sound); + void SetSound(std::shared_ptr sound); void Play(bool loop, float fade_in_duration = 0); @@ -37,7 +36,7 @@ class SoundPlayer { private: uint64_t resource_id_ = 0; - std::shared_ptr sound_; + std::shared_ptr sound_; float max_amplitude_ = 1.0f;