Refactoring audio code

- Add AudioBus
- Move resampling to AudioBus
- Move SincResampler to engine/audio
This commit is contained in:
Attila Uygun 2023-05-27 22:28:38 +02:00
parent 29ce485ca3
commit 7b637a95d9
19 changed files with 294 additions and 291 deletions

View File

@ -49,7 +49,6 @@ project(kaliber)
add_library(kaliber SHARED add_library(kaliber SHARED
../../../src/base/collusion_test.cc ../../../src/base/collusion_test.cc
../../../src/base/log.cc ../../../src/base/log.cc
../../../src/base/sinc_resampler.cc
../../../src/base/task_runner.cc ../../../src/base/task_runner.cc
../../../src/base/thread_pool.cc ../../../src/base/thread_pool.cc
../../../src/base/timer.cc ../../../src/base/timer.cc
@ -62,8 +61,10 @@ add_library(kaliber SHARED
../../../src/demo/sky_quad.cc ../../../src/demo/sky_quad.cc
../../../src/engine/animatable.cc ../../../src/engine/animatable.cc
../../../src/engine/animator.cc ../../../src/engine/animator.cc
../../../src/engine/audio/audio_bus.cc
../../../src/engine/audio/audio_mixer.cc ../../../src/engine/audio/audio_mixer.cc
../../../src/engine/audio/audio_sink_oboe.cc ../../../src/engine/audio/audio_sink_oboe.cc
../../../src/engine/audio/sinc_resampler.cc
../../../src/engine/drawable.cc ../../../src/engine/drawable.cc
../../../src/engine/engine.cc ../../../src/engine/engine.cc
../../../src/engine/font.cc ../../../src/engine/font.cc

View File

@ -78,7 +78,6 @@ objs_from_src_in = $(call objs_from_src, $(shell find $(1) -name "*.cc" -o -name
BASE_SRC := \ BASE_SRC := \
$(SRC_ROOT)/base/collusion_test.cc \ $(SRC_ROOT)/base/collusion_test.cc \
$(SRC_ROOT)/base/log.cc \ $(SRC_ROOT)/base/log.cc \
$(SRC_ROOT)/base/sinc_resampler.cc \
$(SRC_ROOT)/base/task_runner.cc \ $(SRC_ROOT)/base/task_runner.cc \
$(SRC_ROOT)/base/thread_pool.cc \ $(SRC_ROOT)/base/thread_pool.cc \
$(SRC_ROOT)/base/timer.cc $(SRC_ROOT)/base/timer.cc
@ -95,8 +94,10 @@ $(BASE_LIB): $(BASE_OBJS)
ENGINE_SRC := \ ENGINE_SRC := \
$(SRC_ROOT)/engine/animatable.cc \ $(SRC_ROOT)/engine/animatable.cc \
$(SRC_ROOT)/engine/animator.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_mixer.cc \
$(SRC_ROOT)/engine/audio/audio_sink_alsa.cc \ $(SRC_ROOT)/engine/audio/audio_sink_alsa.cc \
$(SRC_ROOT)/engine/audio/sinc_resampler.cc \
$(SRC_ROOT)/engine/drawable.cc \ $(SRC_ROOT)/engine/drawable.cc \
$(SRC_ROOT)/engine/engine.cc \ $(SRC_ROOT)/engine/engine.cc \
$(SRC_ROOT)/engine/font.cc \ $(SRC_ROOT)/engine/font.cc \

View File

@ -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 <typename T>
std::array<std::unique_ptr<T[]>, 2> Deinterleave(size_t num_channels,
size_t num_samples,
float* input_buffer) {
std::array<std::unique_ptr<T[]>, 2> channels;
if (num_channels == 1) {
// Single channel.
if constexpr (std::is_same<float, T>::value) {
channels[0] = std::make_unique<T[]>(num_samples);
memcpy(channels[0].get(), input_buffer, num_samples * sizeof(float));
} else {
channels[0] = std::make_unique<T[]>(num_samples);
for (int i = 0; i < num_samples; ++i)
channels[0].get()[i] = static_cast<T>(input_buffer[i]);
}
} else {
// Deinterleave into separate channels.
channels[0] = std::make_unique<T[]>(num_samples);
channels[1] = std::make_unique<T[]>(num_samples);
for (size_t i = 0, j = 0; i < num_samples * 2; i += 2) {
channels[0].get()[j] = static_cast<T>(input_buffer[i]);
channels[1].get()[j++] = static_cast<T>(input_buffer[i + 1]);
}
}
return channels;
}
std::unique_ptr<SincResampler> CreateResampler(int src_samle_rate,
int dst_sample_rate,
size_t num_samples) {
const double io_ratio = static_cast<double>(src_samle_rate) /
static_cast<double>(dst_sample_rate);
auto resampler = std::make_unique<SincResampler>(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<float[]> input_buffer,
size_t samples_per_channel) {
auto channels = Deinterleave<float>(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<float[]>(num_resampled_samples);
if (num_channels_ == 2)
channel_data_[1] = std::make_unique<float[]>(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

View File

@ -0,0 +1,45 @@
#ifndef ENGINE_AUDIO_AUDIO_BUS_H
#define ENGINE_AUDIO_AUDIO_BUS_H
#include <memory>
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<float[]> input_buffer,
size_t samples_per_channel);
private:
std::unique_ptr<float[]> channel_data_[2];
size_t samples_per_channel_ = 0;
size_t sample_rate_ = 0;
size_t num_channels_ = 0;
std::unique_ptr<SincResampler> resampler_[2];
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_BUS_H

View File

@ -5,7 +5,7 @@
#include "base/log.h" #include "base/log.h"
#include "base/task_runner.h" #include "base/task_runner.h"
#include "base/thread_pool.h" #include "base/thread_pool.h"
#include "engine/sound.h" #include "engine/audio/audio_bus.h"
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include "engine/audio/audio_sink_oboe.h" #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, void AudioMixer::Play(uint64_t resource_id,
std::shared_ptr<Sound> sound, std::shared_ptr<AudioBus> sound,
float amplitude, float amplitude,
bool reset_pos) { bool reset_pos) {
if (!audio_enabled_) if (!audio_enabled_)
@ -76,6 +76,8 @@ void AudioMixer::Play(uint64_t resource_id,
it->second->src_index = 0; it->second->src_index = 0;
it->second->accumulator = 0; it->second->accumulator = 0;
sound->ResetStream(); sound->ResetStream();
} else if (it->second->src_index >= sound->samples_per_channel()) {
return;
} }
it->second->active = true; it->second->active = true;
@ -161,7 +163,7 @@ void AudioMixer::Resume() {
audio_sink_->Resume(); audio_sink_->Resume();
} }
int AudioMixer::GetHardwareSampleRate() { size_t AudioMixer::GetHardwareSampleRate() {
return audio_sink_->GetHardwareSampleRate(); return audio_sink_->GetHardwareSampleRate();
} }
@ -182,11 +184,12 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
if (flags & kStopped) { if (flags & kStopped) {
marked_for_removal = true; marked_for_removal = true;
} else { } else {
const float* src[2] = {sound->GetBuffer(0), sound->GetBuffer(1)}; const float* src[2] = {sound->GetChannelData(0),
sound->GetChannelData(1)};
if (!src[1]) if (!src[1])
src[1] = src[0]; // mono. 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 src_index = it->get()->src_index;
size_t step = it->get()->step.load(std::memory_order_relaxed); size_t step = it->get()->step.load(std::memory_order_relaxed);
size_t accumulator = it->get()->accumulator; 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); it->get()->amplitude_inc.load(std::memory_order_relaxed);
float max_amplitude = float max_amplitude =
it->get()->max_amplitude.load(std::memory_order_relaxed); it->get()->max_amplitude.load(std::memory_order_relaxed);
size_t channel_offset = size_t channel_offset =
(flags & kSimulateStereo) && !sound->is_streaming_sound() (flags & kSimulateStereo) ? sound->sample_rate() / 10 : 0;
? 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;) { for (size_t i = 0; i < num_frames * kChannelCount;) {
if (num_samples) { // Mix the 1st channel.
// Mix the 1st channel. output_buffer[i++] += src[0][src_index] * amplitude;
output_buffer[i++] += src[0][src_index] * amplitude;
// Mix the 2nd channel. Offset the source index for stereo simulation. // Mix the 2nd channel. Offset the source index for stereo simulation.
size_t ind = channel_offset + src_index; size_t ind = channel_offset + src_index;
if (ind < num_samples) if (ind < num_samples)
output_buffer[i++] += src[1][ind] * amplitude; output_buffer[i++] += src[1][ind] * amplitude;
else if (flags & kLoop) else if (flags & kLoop)
output_buffer[i++] += src[1][ind % num_samples] * amplitude; output_buffer[i++] += src[1][ind % num_samples] * amplitude;
else else
i++; i++;
// Apply amplitude modification. // Apply amplitude modification.
amplitude += amplitude_inc; amplitude += amplitude_inc;
if (amplitude <= 0) { if (amplitude <= 0) {
marked_for_removal = true; marked_for_removal = true;
break; break;
} else if (amplitude > max_amplitude) { } else if (amplitude > max_amplitude) {
amplitude = max_amplitude; amplitude = max_amplitude;
}
// Advance source index. Apply basic resampling for variations.
accumulator += step;
src_index += accumulator / 100;
accumulator %= 100;
} }
// 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 // Remove, loop or stream if the source data is consumed
if (src_index >= num_samples) { if (src_index >= num_samples) {
if (num_samples) src_index %= num_samples;
src_index %= num_samples;
if (!sound->is_streaming_sound()) { if (sound->IsEndOfStream()) {
if (!(flags & kLoop)) { marked_for_removal = true;
marked_for_removal = true; break;
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 (!it->get()->streaming_in_progress.load(
std::memory_order_acquire)) {
it->get()->streaming_in_progress.store(true, it->get()->streaming_in_progress.store(true,
std::memory_order_relaxed); std::memory_order_relaxed);
// Swap buffers and start streaming in background. // Swap buffers and start streaming in background.
sound->SwapBuffers(); sound->SwapBuffers();
src[0] = sound->GetBuffer(0); src[0] = sound->GetChannelData(0);
src[1] = sound->GetBuffer(1); src[1] = sound->GetChannelData(1);
if (!src[1]) if (!src[1])
src[1] = src[0]; // mono. src[1] = src[0]; // mono.
num_samples = sound->GetNumSamples(); num_samples = sound->samples_per_channel();
ThreadPool::Get().PostTask( ThreadPool::Get().PostTask(
HERE, 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();) { for (auto it = end_list_.begin(); it != end_list_.end();) {
if ((!it->get()->sound->is_streaming_sound() || if (!it->get()->streaming_in_progress.load(std::memory_order_relaxed)) {
!it->get()->streaming_in_progress.load(std::memory_order_relaxed))) {
main_thread_task_runner_->PostTask( main_thread_task_runner_->PostTask(
HERE, std::bind(&AudioMixer::EndCallback, this, *it)); HERE, std::bind(&AudioMixer::EndCallback, this, *it));
it = end_list_.erase(it); it = end_list_.erase(it);

View File

@ -17,7 +17,7 @@ class TaskRunner;
namespace eng { namespace eng {
class AudioSink; class AudioSink;
class Sound; class AudioBus;
// Mix and render audio with low overhead. A platform specific AudioSink // Mix and render audio with low overhead. A platform specific AudioSink
// implementation is expected to periodically call RenderAudio() in a background // 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 DestroyResource(uint64_t resource_id);
void Play(uint64_t resource_id, void Play(uint64_t resource_id,
std::shared_ptr<Sound> sound, std::shared_ptr<AudioBus> sound,
float amplitude, float amplitude,
bool reset_pos); bool reset_pos);
void Stop(uint64_t resource_id); void Stop(uint64_t resource_id);
@ -48,7 +48,7 @@ class AudioMixer : public AudioSinkDelegate {
void Suspend(); void Suspend();
void Resume(); void Resume();
int GetHardwareSampleRate(); size_t GetHardwareSampleRate();
private: private:
enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 }; enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 };
@ -62,7 +62,7 @@ class AudioMixer : public AudioSinkDelegate {
base::Closure restart_cb; base::Closure restart_cb;
// Initialized by main thread, used by audio thread. // Initialized by main thread, used by audio thread.
std::shared_ptr<Sound> sound; std::shared_ptr<AudioBus> sound;
size_t src_index = 0; size_t src_index = 0;
size_t accumulator = 0; size_t accumulator = 0;
float amplitude = 1.0f; float amplitude = 1.0f;

View File

@ -15,7 +15,7 @@ class AudioSink {
virtual void Suspend() = 0; virtual void Suspend() = 0;
virtual void Resume() = 0; virtual void Resume() = 0;
virtual int GetHardwareSampleRate() = 0; virtual size_t GetHardwareSampleRate() = 0;
private: private:
AudioSink(const AudioSink&) = delete; AudioSink(const AudioSink&) = delete;

View File

@ -152,7 +152,7 @@ void AudioSinkAlsa::Resume() {
suspend_audio_thread_.store(false, std::memory_order_relaxed); suspend_audio_thread_.store(false, std::memory_order_relaxed);
} }
int AudioSinkAlsa::GetHardwareSampleRate() { size_t AudioSinkAlsa::GetHardwareSampleRate() {
return sample_rate_; return sample_rate_;
} }

View File

@ -22,7 +22,7 @@ class AudioSinkAlsa final : public AudioSink {
void Suspend() final; void Suspend() final;
void Resume() final; void Resume() final;
int GetHardwareSampleRate() final; size_t GetHardwareSampleRate() final;
private: private:
// Handle for the PCM device. // Handle for the PCM device.
@ -33,7 +33,7 @@ class AudioSinkAlsa final : public AudioSink {
std::atomic<bool> suspend_audio_thread_{false}; std::atomic<bool> suspend_audio_thread_{false};
size_t num_channels_ = 0; size_t num_channels_ = 0;
int sample_rate_ = 0; size_t sample_rate_ = 0;
size_t period_size_ = 0; size_t period_size_ = 0;
AudioSinkDelegate* delegate_ = nullptr; AudioSinkDelegate* delegate_ = nullptr;

View File

@ -29,7 +29,7 @@ void AudioSinkOboe::Resume() {
stream_->start(); stream_->start();
} }
int AudioSinkOboe::GetHardwareSampleRate() { size_t AudioSinkOboe::GetHardwareSampleRate() {
return stream_->getSampleRate(); return stream_->getSampleRate();
} }

View File

@ -22,7 +22,7 @@ class AudioSinkOboe final : public AudioSink {
void Suspend() final; void Suspend() final;
void Resume() final; void Resume() final;
int GetHardwareSampleRate() final; size_t GetHardwareSampleRate() final;
private: private:
class StreamCallback final : public oboe::AudioStreamCallback { class StreamCallback final : public oboe::AudioStreamCallback {

View File

@ -74,7 +74,7 @@
// Note: we're glossing over how the sub-sample handling works with // Note: we're glossing over how the sub-sample handling works with
// |virtual_source_idx_|, etc. // |virtual_source_idx_|, etc.
#include "base/sinc_resampler.h" #include "engine/audio/sinc_resampler.h"
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
@ -94,7 +94,9 @@
#include <arm_neon.h> #include <arm_neon.h>
#endif #endif
namespace base { using namespace base;
namespace eng {
namespace { namespace {
@ -257,14 +259,14 @@ void SincResampler::InitializeKernel() {
for (int i = 0; i < kernel_size_; ++i) { for (int i = 0; i < kernel_size_; ++i) {
const int idx = i + offset_idx * kernel_size_; const int idx = i + offset_idx * kernel_size_;
const float pre_sinc = 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; kernel_pre_sinc_storage_[idx] = pre_sinc;
// Compute Blackman window, matching the offset of the sinc(). // Compute Blackman window, matching the offset of the sinc().
const float x = (i - subsample_offset) / kernel_size_; const float x = (i - subsample_offset) / kernel_size_;
const float window = const float window =
static_cast<float>(kA0 - kA1 * cos(2.0 * base::kPiDouble * x) + static_cast<float>(kA0 - kA1 * cos(2.0 * kPiDouble * x) +
kA2 * cos(4.0 * base::kPiDouble * x)); kA2 * cos(4.0 * kPiDouble * x));
kernel_window_storage_[idx] = window; kernel_window_storage_[idx] = window;
// Compute the sinc with offset, then window the sinc() function and store // 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 #endif
} // namespace base } // namespace eng

View File

@ -1,16 +1,18 @@
// Copyright 2012 The Chromium Authors // Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
//
// Modified for Kaliber engine.
#ifndef BASE_SINC_RESAMPLER_H #ifndef ENGINE_AUDIO__SINC_RESAMPLER_H
#define BASE_SINC_RESAMPLER_H #define ENGINE_AUDIO__SINC_RESAMPLER_H
#include <functional> #include <functional>
#include <memory> #include <memory>
#include "base/mem.h" #include "base/mem.h"
namespace base { namespace eng {
// SincResampler is a high-quality single-channel sample-rate converter. // SincResampler is a high-quality single-channel sample-rate converter.
class SincResampler { class SincResampler {
@ -163,12 +165,12 @@ class SincResampler {
// Contains kKernelOffsetCount kernels back-to-back, each of size // Contains kKernelOffsetCount kernels back-to-back, each of size
// `kernel_size_`. The kernel offsets are sub-sample shifts of a windowed sinc // `kernel_size_`. The kernel offsets are sub-sample shifts of a windowed sinc
// shifted from 0.0 to 1.0 sample. // shifted from 0.0 to 1.0 sample.
AlignedMemPtr<float[]> kernel_storage_; base::AlignedMemPtr<float[]> kernel_storage_;
AlignedMemPtr<float[]> kernel_pre_sinc_storage_; base::AlignedMemPtr<float[]> kernel_pre_sinc_storage_;
AlignedMemPtr<float[]> kernel_window_storage_; base::AlignedMemPtr<float[]> kernel_window_storage_;
// Data from the source is copied into this buffer for each processing pass. // Data from the source is copied into this buffer for each processing pass.
AlignedMemPtr<float[]> input_buffer_; base::AlignedMemPtr<float[]> input_buffer_;
// Stores the runtime selection of which Convolve function to use. // Stores the runtime selection of which Convolve function to use.
using ConvolveProc = using ConvolveProc =
@ -184,6 +186,6 @@ class SincResampler {
float* r4_; float* r4_;
}; };
} // namespace base } // namespace eng
#endif // BASE_SINC_RESAMPLER_H #endif // ENGINE_AUDIO__SINC_RESAMPLER_H

View File

@ -474,7 +474,7 @@ const std::string& Engine::GetSharedDataPath() const {
return platform_->GetSharedDataPath(); return platform_->GetSharedDataPath();
} }
int Engine::GetAudioHardwareSampleRate() { size_t Engine::GetAudioHardwareSampleRate() {
return audio_mixer_->GetHardwareSampleRate(); return audio_mixer_->GetHardwareSampleRate();
} }

View File

@ -137,7 +137,7 @@ class Engine : public PlatformObserver {
const std::string& GetSharedDataPath() const; const std::string& GetSharedDataPath() const;
int GetAudioHardwareSampleRate(); size_t GetAudioHardwareSampleRate();
bool IsMobile() const; bool IsMobile() const;

View File

@ -3,7 +3,6 @@
#include <array> #include <array>
#include "base/log.h" #include "base/log.h"
#include "base/sinc_resampler.h"
#define MINIMP3_ONLY_MP3 #define MINIMP3_ONLY_MP3
#define MINIMP3_ONLY_SIMD #define MINIMP3_ONLY_SIMD
#define MINIMP3_FLOAT_OUTPUT #define MINIMP3_FLOAT_OUTPUT
@ -15,53 +14,10 @@
using namespace base; using namespace base;
namespace {
constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME * 10;
template <typename T>
std::array<std::unique_ptr<T[]>, 2> Deinterleave(size_t num_channels,
size_t num_samples,
float* input_buffer) {
std::array<std::unique_ptr<T[]>, 2> channels;
if (num_channels == 1) {
// Single channel.
if constexpr (std::is_same<float, T>::value) {
channels[0] = std::make_unique<T[]>(num_samples);
memcpy(channels[0].get(), input_buffer, num_samples * sizeof(float));
} else {
channels[0] = std::make_unique<T[]>(num_samples);
for (int i = 0; i < num_samples; ++i)
channels[0].get()[i] = static_cast<T>(input_buffer[i]);
}
} else {
// Deinterleave into separate channels.
channels[0] = std::make_unique<T[]>(num_samples);
channels[1] = std::make_unique<T[]>(num_samples);
for (size_t i = 0, j = 0; i < num_samples * 2; i += 2) {
channels[0].get()[j] = static_cast<T>(input_buffer[i]);
channels[1].get()[j++] = static_cast<T>(input_buffer[i + 1]);
}
}
return channels;
}
std::unique_ptr<SincResampler> CreateResampler(int src_samle_rate,
int dst_sample_rate,
size_t num_samples) {
const double io_ratio = static_cast<double>(src_samle_rate) /
static_cast<double>(dst_sample_rate);
auto resampler = std::make_unique<SincResampler>(io_ratio, num_samples);
resampler->PrimeWithSilence();
return resampler;
}
} // namespace
namespace eng { namespace eng {
constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME;
Sound::Sound() = default; Sound::Sound() = default;
Sound::~Sound() { Sound::~Sound() {
@ -92,164 +48,98 @@ bool Sound::Load(const std::string& file_name, bool stream) {
return false; return false;
} }
is_streaming_sound_ = stream; LOG << (stream ? "Streaming " : "Loaded ") << file_name << ". "
LOG << (is_streaming_sound_ ? "Streaming " : "Loaded ") << file_name << ". "
<< mp3_dec_->samples << " samples, " << mp3_dec_->info.channels << mp3_dec_->samples << " samples, " << mp3_dec_->info.channels
<< " channels, " << mp3_dec_->info.hz << " hz, " << " channels, " << mp3_dec_->info.hz << " hz, "
<< "layer " << mp3_dec_->info.layer << ", " << "layer " << mp3_dec_->info.layer << ", "
<< "avg_bitrate_kbps " << mp3_dec_->info.bitrate_kbps; << "avg_bitrate_kbps " << mp3_dec_->info.bitrate_kbps;
num_channels_ = mp3_dec_->info.channels; SetAudioConfig(mp3_dec_->info.channels, mp3_dec_->info.hz);
sample_rate_ = mp3_dec_->info.hz;
num_samples_back_ = cur_sample_back_ = 0;
eof_ = false;
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 (stream) {
if (sample_rate_ != hw_sample_rate_) { // Fill up the buffer.
for (int i = 0; i < mp3_dec_->info.channels; ++i) { StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
resampler_[i] =
CreateResampler(sample_rate_, hw_sample_rate_,
(int)kMaxSamplesPerChunk / mp3_dec_->info.channels);
}
}
// Fill up buffers.
StreamInternal(kMaxSamplesPerChunk, false);
SwapBuffers(); SwapBuffers();
StreamInternal(kMaxSamplesPerChunk, false); StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
// No need to stream if sample fits into the buffer. // No need to stream if sample fits into the buffer.
if (eof_) if (eos_)
is_streaming_sound_ = false; stream = false;
} else { } 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. // Decode entire file.
StreamInternal(mp3_dec_->samples, false); StreamInternal(mp3_dec_->samples, false);
SwapBuffers(); SwapBuffers();
eof_ = true; eos_ = true;
} }
if (!is_streaming_sound_) { if (!stream) {
// We are done with decoding. // We are done with decoding.
encoded_data_.reset(); encoded_data_.reset();
for (int i = 0; i < mp3_dec_->info.channels; ++i)
resampler_[i].reset();
mp3dec_ex_close(mp3_dec_.get()); mp3dec_ex_close(mp3_dec_.get());
mp3_dec_.reset(); mp3_dec_.reset();
} }
read_pos_ = 0;
return true; return true;
} }
bool Sound::Stream(bool loop) { void Sound::Stream(bool loop) {
DCHECK(is_streaming_sound_); DCHECK(mp3_dec_);
StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, loop);
return StreamInternal(kMaxSamplesPerChunk, loop);
} }
void Sound::SwapBuffers() { void Sound::SwapBuffers() {
front_buffer_[0].swap(back_buffer_[0]); FromInterleaved(std::move(interleaved_data_), samples_per_channel_);
front_buffer_[1].swap(back_buffer_[1]); samples_per_channel_ = 0;
cur_sample_front_ = cur_sample_back_;
num_samples_front_ = num_samples_back_;
num_samples_back_ = 0;
} }
void Sound::ResetStream() { void Sound::ResetStream() {
if (is_streaming_sound_ && cur_sample_front_ != 0) { if (mp3_dec_ && read_pos_ != 0) {
// Seek to 0 and ivalidate decoded data. // Seek to 0 and stream.
mp3dec_ex_seek(mp3_dec_.get(), 0); mp3dec_ex_seek(mp3_dec_.get(), 0);
eof_ = false; eos_ = false;
num_samples_back_ = num_samples_front_ = 0; StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
cur_sample_front_ = cur_sample_back_ = 0; SwapBuffers();
StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
read_pos_ = 0;
} }
} }
float* Sound::GetBuffer(int channel) const { void Sound::StreamInternal(size_t num_samples, bool loop) {
return front_buffer_[channel].get();
}
bool Sound::StreamInternal(size_t num_samples, bool loop) {
auto buffer = std::make_unique<float[]>(num_samples); auto buffer = std::make_unique<float[]>(num_samples);
size_t samples_read_per_channel = 0; size_t samples_read_per_channel = 0;
cur_sample_back_ = mp3_dec_->cur_sample; read_pos_ = mp3_dec_->cur_sample;
for (;;) { for (;;) {
size_t samples_read = size_t samples_read =
mp3dec_ex_read(mp3_dec_.get(), buffer.get(), num_samples); mp3dec_ex_read(mp3_dec_.get(), buffer.get(), num_samples);
if (samples_read != num_samples && mp3_dec_->last_error) { if (samples_read != num_samples && mp3_dec_->last_error) {
LOG << "mp3 decode error: " << mp3_dec_->last_error; LOG << "mp3 decode error: " << mp3_dec_->last_error;
eof_ = true; break;
return false;
} }
samples_read_per_channel = samples_read / mp3_dec_->info.channels; if (samples_read == 0 && loop) {
if (!samples_read_per_channel && loop) {
mp3dec_ex_seek(mp3_dec_.get(), 0); mp3dec_ex_seek(mp3_dec_.get(), 0);
loop = false; loop = false;
continue; continue;
} }
samples_read_per_channel = samples_read / mp3_dec_->info.channels;
break; break;
} }
if (samples_read_per_channel) { if (samples_read_per_channel > 0) {
Preprocess(std::move(buffer), samples_read_per_channel); interleaved_data_ = std::move(buffer);
samples_per_channel_ = samples_read_per_channel;
} else { } else {
num_samples_back_ = 0; samples_per_channel_ = 0;
eof_ = true; eos_ = true;
}
return true;
}
void Sound::Preprocess(std::unique_ptr<float[]> input_buffer,
size_t samples_per_channel) {
auto channels = Deinterleave<float>(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<float[]>(max_samples_);
if (num_channels_ == 2)
back_buffer_[1] = std::make_unique<float[]>(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));
});
}
} }
} }

View File

@ -5,11 +5,9 @@
#include <memory> #include <memory>
#include <string> #include <string>
typedef struct mp3dec_ex mp3dec_ex_t; #include "engine/audio/audio_bus.h"
namespace base { typedef struct mp3dec_ex mp3dec_ex_t;
class SincResampler;
} // namespace base
namespace eng { namespace eng {
@ -17,61 +15,30 @@ namespace eng {
// files. Resamples the decoded audio to match the system sample rate if // files. Resamples the decoded audio to match the system sample rate if
// necessary. Non-streaming sounds Can be shared between multiple audio // necessary. Non-streaming sounds Can be shared between multiple audio
// resources and played simultaneously. // resources and played simultaneously.
class Sound { class Sound final : public AudioBus {
public: public:
Sound(); Sound();
~Sound(); ~Sound() final;
bool Load(const std::string& file_name, bool stream); bool Load(const std::string& file_name, bool stream);
bool Stream(bool loop); // AudioBus interface
void Stream(bool loop) final;
void SwapBuffers(); void SwapBuffers() final;
void ResetStream() final;
void ResetStream(); bool IsEndOfStream() const final { return eos_; }
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_; }
private: private:
// Buffer holding decoded audio. // Buffer holding decoded audio.
std::unique_ptr<float[]> back_buffer_[2]; std::unique_ptr<float[]> interleaved_data_;
std::unique_ptr<float[]> front_buffer_[2]; size_t samples_per_channel_ = 0;
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<char[]> encoded_data_; std::unique_ptr<char[]> encoded_data_;
std::unique_ptr<mp3dec_ex_t> mp3_dec_; std::unique_ptr<mp3dec_ex_t> mp3_dec_;
uint64_t read_pos_ = 0;
bool eos_ = false;
std::unique_ptr<base::SincResampler> resampler_[2]; void StreamInternal(size_t num_samples, bool loop);
bool eof_ = false;
bool is_streaming_sound_ = false;
bool StreamInternal(size_t num_samples, bool loop);
void Preprocess(std::unique_ptr<float[]> input_buffer,
size_t samples_per_channel);
}; };
} // namespace eng } // namespace eng

View File

@ -2,9 +2,9 @@
#include "base/interpolation.h" #include "base/interpolation.h"
#include "base/log.h" #include "base/log.h"
#include "engine/audio/audio_bus.h"
#include "engine/audio/audio_mixer.h" #include "engine/audio/audio_mixer.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/sound.h"
using namespace base; using namespace base;
@ -17,16 +17,10 @@ SoundPlayer::~SoundPlayer() {
Engine::Get().GetAudioMixer()->DestroyResource(resource_id_); Engine::Get().GetAudioMixer()->DestroyResource(resource_id_);
} }
void SoundPlayer::SetSound(std::shared_ptr<Sound> sound) { void SoundPlayer::SetSound(std::shared_ptr<AudioBus> sound) {
CHECK(!sound->is_streaming_sound()) << "Streaming sound cannot be shared.";
sound_ = sound; sound_ = sound;
} }
void SoundPlayer::SetSound(std::unique_ptr<Sound> sound) {
sound_ = std::move(sound);
}
void SoundPlayer::Play(bool loop, float fade_in_duration) { void SoundPlayer::Play(bool loop, float fade_in_duration) {
if (!sound_) if (!sound_)
return; return;

View File

@ -7,15 +7,14 @@
namespace eng { namespace eng {
class Sound; class AudioBus;
class SoundPlayer { class SoundPlayer {
public: public:
SoundPlayer(); SoundPlayer();
~SoundPlayer(); ~SoundPlayer();
void SetSound(std::shared_ptr<Sound> sound); void SetSound(std::shared_ptr<AudioBus> sound);
void SetSound(std::unique_ptr<Sound> sound);
void Play(bool loop, float fade_in_duration = 0); void Play(bool loop, float fade_in_duration = 0);
@ -37,7 +36,7 @@ class SoundPlayer {
private: private:
uint64_t resource_id_ = 0; uint64_t resource_id_ = 0;
std::shared_ptr<Sound> sound_; std::shared_ptr<AudioBus> sound_;
float max_amplitude_ = 1.0f; float max_amplitude_ = 1.0f;