diff --git a/build/android/app/CMakeLists.txt b/build/android/app/CMakeLists.txt index 5a795ea..147351e 100644 --- a/build/android/app/CMakeLists.txt +++ b/build/android/app/CMakeLists.txt @@ -69,6 +69,7 @@ add_library(kaliber SHARED ../../../src/engine/audio/audio_bus.cc ../../../src/engine/audio/audio_mixer.cc ../../../src/engine/audio/audio_sink_oboe.cc + ../../../src/engine/audio/mixer_input.cc ../../../src/engine/audio/sinc_resampler.cc ../../../src/engine/drawable.cc ../../../src/engine/engine.cc diff --git a/build/linux/Makefile b/build/linux/Makefile index 4201ea1..7516917 100644 --- a/build/linux/Makefile +++ b/build/linux/Makefile @@ -102,6 +102,7 @@ ENGINE_SRC := \ $(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/mixer_input.cc \ $(SRC_ROOT)/engine/audio/sinc_resampler.cc \ $(SRC_ROOT)/engine/drawable.cc \ $(SRC_ROOT)/engine/engine.cc \ diff --git a/src/engine/audio/audio_mixer.cc b/src/engine/audio/audio_mixer.cc index 8b51b57..c14340a 100644 --- a/src/engine/audio/audio_mixer.cc +++ b/src/engine/audio/audio_mixer.cc @@ -6,6 +6,7 @@ #include "base/task_runner.h" #include "base/thread_pool.h" #include "engine/audio/audio_bus.h" +#include "engine/audio/mixer_input.h" #if defined(__ANDROID__) #include "engine/audio/audio_sink_oboe.h" @@ -17,10 +18,6 @@ using namespace base; namespace eng { -AudioMixer::Resource::~Resource() { - DLOG(1) << "Destroying audio resource: " << uintptr_t(this); -} - AudioMixer::AudioMixer() : main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()), #if defined(__ANDROID__) @@ -36,98 +33,11 @@ AudioMixer::~AudioMixer() { audio_sink_.reset(); } -std::shared_ptr AudioMixer::CreateResource() { - auto resource = std::make_shared(); - DLOG(1) << "Audio resource created: " << uintptr_t(resource.get()); - return resource; -} - -void AudioMixer::Play(std::shared_ptr resource, - std::shared_ptr audio_bus, - float amplitude, - bool reset_pos) { - if (!audio_enabled_) - return; - - auto resource_ptr = std::static_pointer_cast(resource); - - if (resource_ptr->active) { - if (reset_pos) - resource_ptr->flags.fetch_or(kStopped, std::memory_order_relaxed); - - if (resource_ptr->flags.load(std::memory_order_relaxed) & kStopped) - resource_ptr->restart_cb = [&, resource, audio_bus, amplitude, - reset_pos]() -> void { - Play(resource, audio_bus, amplitude, reset_pos); - }; - - return; - } - - if (reset_pos) { - resource_ptr->src_index = 0; - resource_ptr->accumulator = 0; - audio_bus->ResetStream(); - } else if (resource_ptr->src_index >= audio_bus->samples_per_channel()) { - return; - } - - resource_ptr->active = true; - resource_ptr->flags.fetch_and(~kStopped, std::memory_order_relaxed); - resource_ptr->audio_bus = audio_bus; - if (amplitude >= 0) - resource_ptr->amplitude = amplitude; +void AudioMixer::AddInput(std::shared_ptr input) { + DCHECK(audio_enabled_); std::lock_guard scoped_lock(lock_); - play_list_[0].push_back(resource_ptr); -} - -void AudioMixer::Stop(std::shared_ptr resource) { - auto resource_ptr = std::static_pointer_cast(resource); - if (resource_ptr->active) { - resource_ptr->restart_cb = nullptr; - resource_ptr->flags.fetch_or(kStopped, std::memory_order_relaxed); - } -} - -void AudioMixer::SetLoop(std::shared_ptr resource, bool loop) { - auto resource_ptr = std::static_pointer_cast(resource); - if (loop) - resource_ptr->flags.fetch_or(kLoop, std::memory_order_relaxed); - else - resource_ptr->flags.fetch_and(~kLoop, std::memory_order_relaxed); -} - -void AudioMixer::SetSimulateStereo(std::shared_ptr resource, - bool simulate) { - auto resource_ptr = std::static_pointer_cast(resource); - if (simulate) - resource_ptr->flags.fetch_or(kSimulateStereo, std::memory_order_relaxed); - else - resource_ptr->flags.fetch_and(~kSimulateStereo, std::memory_order_relaxed); -} - -void AudioMixer::SetResampleStep(std::shared_ptr resource, size_t step) { - auto resource_ptr = std::static_pointer_cast(resource); - resource_ptr->step.store(step + 100, std::memory_order_relaxed); -} - -void AudioMixer::SetMaxAmplitude(std::shared_ptr resource, - float max_amplitude) { - auto resource_ptr = std::static_pointer_cast(resource); - resource_ptr->max_amplitude.store(max_amplitude, std::memory_order_relaxed); -} - -void AudioMixer::SetAmplitudeInc(std::shared_ptr resource, - float amplitude_inc) { - auto resource_ptr = std::static_pointer_cast(resource); - resource_ptr->amplitude_inc.store(amplitude_inc, std::memory_order_relaxed); -} - -void AudioMixer::SetEndCallback(std::shared_ptr resource, - base::Closure cb) { - auto resource_ptr = std::static_pointer_cast(resource); - resource_ptr->end_cb = std::move(cb); + inputs_[0].push_back(input); } void AudioMixer::Suspend() { @@ -146,17 +56,17 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { { std::unique_lock scoped_lock(lock_, std::try_to_lock); if (scoped_lock) - play_list_[1].splice(play_list_[1].end(), play_list_[0]); + inputs_[1].splice(inputs_[1].end(), inputs_[0]); } memset(output_buffer, 0, sizeof(float) * num_frames * kChannelCount); - for (auto it = play_list_[1].begin(); it != play_list_[1].end();) { + for (auto it = inputs_[1].begin(); it != inputs_[1].end();) { auto audio_bus = (*it)->audio_bus.get(); unsigned flags = (*it)->flags.load(std::memory_order_relaxed); bool marked_for_removal = false; - if (flags & kStopped) { + if (flags & MixerInput::kStopped) { marked_for_removal = true; } else { const float* src[2] = {audio_bus->GetChannelData(0), @@ -173,8 +83,9 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { (*it)->amplitude_inc.load(std::memory_order_relaxed); float max_amplitude = (*it)->max_amplitude.load(std::memory_order_relaxed); - size_t channel_offset = - (flags & kSimulateStereo) ? audio_bus->sample_rate() / 10 : 0; + size_t channel_offset = (flags & MixerInput::kSimulateStereo) + ? audio_bus->sample_rate() / 10 + : 0; DCHECK(num_samples > 0); @@ -187,7 +98,7 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { size_t ind = channel_offset + src_index; if (ind < num_samples) output_buffer[i++] += src[1][ind] * amplitude; - else if (flags & kLoop) + else if (flags & MixerInput::kLoop) output_buffer[i++] += src[1][ind % num_samples] * amplitude; else i++; @@ -208,7 +119,7 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { } else { if (audio_bus->EndOfStream()) { src_index %= num_samples; - marked_for_removal = !(flags & kLoop); + marked_for_removal = !(flags & MixerInput::kLoop); break; } @@ -226,7 +137,8 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { ThreadPool::Get().PostTask( HERE, - std::bind(&AudioMixer::DoStream, this, *it, flags & kLoop), + std::bind(&AudioMixer::DoStream, this, *it, + flags & MixerInput::kLoop), true); } else { DLOG(0) << "Mixer buffer underrun!"; @@ -240,37 +152,37 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { } if (marked_for_removal) { - end_list_.push_back(*it); - it = play_list_[1].erase(it); + removed_inputs_.push_back(*it); + it = inputs_[1].erase(it); } else { ++it; } } - for (auto it = end_list_.begin(); it != end_list_.end();) { + for (auto it = removed_inputs_.begin(); it != removed_inputs_.end();) { if (!(*it)->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); + it = removed_inputs_.erase(it); } else { ++it; } } } -void AudioMixer::DoStream(std::shared_ptr resource, bool loop) { - resource->audio_bus->Stream(loop); - resource->streaming_in_progress.store(false, std::memory_order_release); +void AudioMixer::DoStream(std::shared_ptr input, bool loop) { + input->audio_bus->Stream(loop); + input->streaming_in_progress.store(false, std::memory_order_release); } -void AudioMixer::EndCallback(std::shared_ptr resource) { - resource->active = false; +void AudioMixer::EndCallback(std::shared_ptr input) { + input->active = false; - if (resource->end_cb) - resource->end_cb(); - if (resource->restart_cb) { - resource->restart_cb(); - resource->restart_cb = nullptr; + if (input->end_cb) + input->end_cb(); + if (input->restart_cb) { + input->restart_cb(); + input->restart_cb = nullptr; } } diff --git a/src/engine/audio/audio_mixer.h b/src/engine/audio/audio_mixer.h index 6e82c35..f86a721 100644 --- a/src/engine/audio/audio_mixer.h +++ b/src/engine/audio/audio_mixer.h @@ -1,7 +1,6 @@ #ifndef ENGINE_AUDIO_AUDIO_MIXER_H #define ENGINE_AUDIO_AUDIO_MIXER_H -#include #include #include #include @@ -16,31 +15,24 @@ class TaskRunner; namespace eng { class AudioBus; +struct MixerInput; -// Mix and render audio with low overhead. A platform specific AudioSink -// implementation is expected to periodically call RenderAudio() in a background -// thread. +// Mix and render audio with low overhead. The mixer has zero or more inputs +// which can be added at any time. The mixer will pull from each input source +// when it needs more data. Input source will be removed once end-of-stream is +// reached. Any unfilled frames will be filled with silence. The mixer always +// outputs audio when active, even if input sources underflow. A platform +// specific AudioSink implementation is expected to periodically call +// RenderAudio() in a background thread. class AudioMixer : public AudioSink::Delegate { public: AudioMixer(); ~AudioMixer(); - std::shared_ptr CreateResource(); - - void Play(std::shared_ptr resource, - std::shared_ptr audio_bus, - float amplitude, - bool reset_pos); - void Stop(std::shared_ptr resource); - - void SetLoop(std::shared_ptr resource, bool loop); - void SetSimulateStereo(std::shared_ptr resource, bool simulate); - void SetResampleStep(std::shared_ptr resource, size_t step); - void SetMaxAmplitude(std::shared_ptr resource, float max_amplitude); - void SetAmplitudeInc(std::shared_ptr resource, float amplitude_inc); - void SetEndCallback(std::shared_ptr resource, base::Closure cb); + void AddInput(std::shared_ptr input); void SetEnableAudio(bool enable) { audio_enabled_ = enable; } + bool IsAudioEnabled() const { return audio_enabled_; } void Suspend(); void Resume(); @@ -48,38 +40,12 @@ class AudioMixer : public AudioSink::Delegate { size_t GetHardwareSampleRate(); private: - enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 }; static constexpr int kChannelCount = 2; - // An audio resource that gets mixed and rendered to the audio sink. - struct Resource { - // Accessed by main thread only. - bool active = false; - base::Closure end_cb; - base::Closure restart_cb; - - // Initialized by main thread, used by audio thread. - std::shared_ptr audio_bus; - size_t src_index = 0; - size_t accumulator = 0; - float amplitude = 1.0f; - - // Write accessed by main thread, read-only accessed by audio thread. - std::atomic flags{0}; - std::atomic step{100}; - std::atomic amplitude_inc{0}; - std::atomic max_amplitude{1.0f}; - - // Accessed by audio thread and decoder thread. - std::atomic streaming_in_progress{false}; - - ~Resource(); - }; - - std::list> play_list_[2]; + std::list> inputs_[2]; std::mutex lock_; - std::list> end_list_; + std::list> removed_inputs_; std::shared_ptr main_thread_task_runner_; @@ -91,9 +57,9 @@ class AudioMixer : public AudioSink::Delegate { int GetChannelCount() final { return kChannelCount; } void RenderAudio(float* output_buffer, size_t num_frames) final; - void DoStream(std::shared_ptr sample, bool loop); + void DoStream(std::shared_ptr sample, bool loop); - void EndCallback(std::shared_ptr sample); + void EndCallback(std::shared_ptr sample); AudioMixer(const AudioMixer&) = delete; AudioMixer& operator=(const AudioMixer&) = delete; diff --git a/src/engine/audio/mixer_input.cc b/src/engine/audio/mixer_input.cc new file mode 100644 index 0000000..a993d20 --- /dev/null +++ b/src/engine/audio/mixer_input.cc @@ -0,0 +1,98 @@ +#include "engine/audio/mixer_input.h" + +#include "base/log.h" +#include "engine/audio/audio_bus.h" +#include "engine/audio/audio_mixer.h" + +using namespace base; + +namespace eng { + +MixerInput::MixerInput() { + DLOG(1) << "MixerInput created: " << uintptr_t(this); +} + +MixerInput::~MixerInput() { + DLOG(1) << "Destroying MixerInput: " << uintptr_t(this); +} + +// static +std::shared_ptr MixerInput::Create() { + return std::shared_ptr(new MixerInput()); +} + +void MixerInput::Play(AudioMixer* mixer, + std::shared_ptr bus, + float amp, + bool restart) { + if (!mixer->IsAudioEnabled()) + return; + + if (bus != audio_bus || src_index >= bus->samples_per_channel()) + restart = true; + + if (active) { + if (restart) + flags.fetch_or(kStopped, std::memory_order_relaxed); + + if (flags.load(std::memory_order_relaxed) & kStopped) + restart_cb = [&, mixer, bus, amp, restart]() -> void { + Play(mixer, bus, amp, restart); + }; + + return; + } + + if (restart) { + src_index = 0; + accumulator = 0; + bus->ResetStream(); + } + + active = true; + flags.fetch_and(~kStopped, std::memory_order_relaxed); + audio_bus = bus; + if (amp >= 0) + amplitude = amp; + + mixer->AddInput(shared_from_this()); +} + +void MixerInput::Stop() { + if (active) { + restart_cb = nullptr; + flags.fetch_or(kStopped, std::memory_order_relaxed); + } +} + +void MixerInput::SetLoop(bool loop) { + if (loop) + flags.fetch_or(kLoop, std::memory_order_relaxed); + else + flags.fetch_and(~kLoop, std::memory_order_relaxed); +} + +void MixerInput::SetSimulateStereo(bool simulate) { + if (simulate) + flags.fetch_or(kSimulateStereo, std::memory_order_relaxed); + else + flags.fetch_and(~kSimulateStereo, std::memory_order_relaxed); +} + +void MixerInput::SetResampleStep(size_t value) { + step.store(value + 100, std::memory_order_relaxed); +} + +void MixerInput::SetMaxAmplitude(float value) { + max_amplitude.store(value, std::memory_order_relaxed); +} + +void MixerInput::SetAmplitudeInc(float value) { + amplitude_inc.store(value, std::memory_order_relaxed); +} + +void MixerInput::SetEndCallback(base::Closure cb) { + end_cb = std::move(cb); +} + +} // namespace eng diff --git a/src/engine/audio/mixer_input.h b/src/engine/audio/mixer_input.h new file mode 100644 index 0000000..dee474b --- /dev/null +++ b/src/engine/audio/mixer_input.h @@ -0,0 +1,62 @@ +#ifndef ENGINE_AUDIO_MIXER_INPUT_H +#define ENGINE_AUDIO_MIXER_INPUT_H + +#include +#include + +#include "base/closure.h" + +namespace eng { + +class AudioBus; +class AudioMixer; + +// An audio input stream that gets mixed and rendered to the audio sink. Handles +// playback and volume control. +struct MixerInput : public std::enable_shared_from_this { + enum Flags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 }; + + ~MixerInput(); + + static std::shared_ptr Create(); + + void Play(AudioMixer* mixer, + std::shared_ptr bus, + float amp, + bool restart); + void Stop(); + + void SetLoop(bool loop); + void SetSimulateStereo(bool simulate); + void SetResampleStep(size_t value); + void SetMaxAmplitude(float value); + void SetAmplitudeInc(float value); + void SetEndCallback(base::Closure cb); + + // Accessed by main thread only. + bool active = false; + base::Closure end_cb; + base::Closure restart_cb; + + // Initialized by main thread, used by audio thread. + std::shared_ptr audio_bus; + size_t src_index = 0; + size_t accumulator = 0; + float amplitude = 1.0f; + + // Write accessed by main thread, read-only accessed by audio thread. + std::atomic flags{0}; + std::atomic step{100}; + std::atomic amplitude_inc{0}; + std::atomic max_amplitude{1.0f}; + + // Accessed by audio thread and decoder thread. + std::atomic streaming_in_progress{false}; + + private: + MixerInput(); +}; + +} // namespace eng + +#endif // ENGINE_AUDIO_MIXER_INPUT_H diff --git a/src/engine/sound_player.cc b/src/engine/sound_player.cc index 44b2067..29fea8a 100644 --- a/src/engine/sound_player.cc +++ b/src/engine/sound_player.cc @@ -4,17 +4,17 @@ #include "base/log.h" #include "engine/audio/audio_bus.h" #include "engine/audio/audio_mixer.h" +#include "engine/audio/mixer_input.h" #include "engine/engine.h" using namespace base; namespace eng { -SoundPlayer::SoundPlayer() - : resource_(Engine::Get().GetAudioMixer()->CreateResource()) {} +SoundPlayer::SoundPlayer() : input_{MixerInput::Create()} {} SoundPlayer::~SoundPlayer() { - Engine::Get().GetAudioMixer()->Stop(resource_); + input_->Stop(); } void SoundPlayer::SetSound(const std::string& asset_name) { @@ -30,15 +30,14 @@ void SoundPlayer::Play(bool loop, float fade_in_duration) { return; int step = variate_ ? Engine::Get().GetRandomGenerator().Roll(3) - 2 : 0; - Engine::Get().GetAudioMixer()->SetResampleStep(resource_, step * 12); - Engine::Get().GetAudioMixer()->SetLoop(resource_, loop); + input_->SetResampleStep(step * 12); + input_->SetLoop(loop); if (fade_in_duration > 0) - Engine::Get().GetAudioMixer()->SetAmplitudeInc( - resource_, 1.0f / (sound_->sample_rate() * fade_in_duration)); + input_->SetAmplitudeInc(1.0f / (sound_->sample_rate() * fade_in_duration)); else - Engine::Get().GetAudioMixer()->SetAmplitudeInc(resource_, 0); - Engine::Get().GetAudioMixer()->Play( - resource_, sound_, fade_in_duration > 0 ? 0 : max_amplitude_, true); + input_->SetAmplitudeInc(0); + input_->Play(Engine::Get().GetAudioMixer(), sound_, + fade_in_duration > 0 ? 0 : max_amplitude_, true); } void SoundPlayer::Resume(float fade_in_duration) { @@ -46,10 +45,9 @@ void SoundPlayer::Resume(float fade_in_duration) { return; if (fade_in_duration > 0) - Engine::Get().GetAudioMixer()->SetAmplitudeInc( - resource_, 1.0f / (sound_->sample_rate() * fade_in_duration)); - Engine::Get().GetAudioMixer()->Play(resource_, sound_, - fade_in_duration > 0 ? 0 : -1, false); + input_->SetAmplitudeInc(1.0f / (sound_->sample_rate() * fade_in_duration)); + input_->Play(Engine::Get().GetAudioMixer(), sound_, + fade_in_duration > 0 ? 0 : -1, false); } void SoundPlayer::Stop(float fade_out_duration) { @@ -57,10 +55,10 @@ void SoundPlayer::Stop(float fade_out_duration) { return; if (fade_out_duration > 0) - Engine::Get().GetAudioMixer()->SetAmplitudeInc( - resource_, -1.0f / (sound_->sample_rate() * fade_out_duration)); + input_->SetAmplitudeInc(-1.0f / + (sound_->sample_rate() * fade_out_duration)); else - Engine::Get().GetAudioMixer()->Stop(resource_); + input_->Stop(); } void SoundPlayer::SetVariate(bool variate) { @@ -68,16 +66,16 @@ void SoundPlayer::SetVariate(bool variate) { } void SoundPlayer::SetSimulateStereo(bool simulate) { - Engine::Get().GetAudioMixer()->SetSimulateStereo(resource_, simulate); + input_->SetSimulateStereo(simulate); } void SoundPlayer::SetMaxAplitude(float max_amplitude) { max_amplitude_ = max_amplitude; - Engine::Get().GetAudioMixer()->SetMaxAmplitude(resource_, max_amplitude); + input_->SetMaxAmplitude(max_amplitude); } void SoundPlayer::SetEndCallback(base::Closure cb) { - Engine::Get().GetAudioMixer()->SetEndCallback(resource_, cb); + input_->SetEndCallback(cb); } } // namespace eng diff --git a/src/engine/sound_player.h b/src/engine/sound_player.h index 808fa12..19d3d41 100644 --- a/src/engine/sound_player.h +++ b/src/engine/sound_player.h @@ -9,6 +9,7 @@ namespace eng { class AudioBus; +struct MixerInput; class SoundPlayer { public: @@ -31,13 +32,13 @@ class SoundPlayer { // Enable or disable stereo simulation effect. Disabled by default. void SetSimulateStereo(bool simulate); - void SetMaxAplitude(float max_amplitude); + void SetMaxAplitude(float max_amplitude); // TODO: typo // Set callback to be called once playback stops. void SetEndCallback(base::Closure cb); private: - std::shared_ptr resource_; + std::shared_ptr input_; std::shared_ptr sound_; float max_amplitude_ = 1.0f;