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
../../../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

View File

@ -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 \

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/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> sound,
std::shared_ptr<AudioBus> 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,16 +198,12 @@ 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;
@ -230,36 +229,28 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
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;
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);
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);

View File

@ -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> sound,
std::shared_ptr<AudioBus> 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> sound;
std::shared_ptr<AudioBus> sound;
size_t src_index = 0;
size_t accumulator = 0;
float amplitude = 1.0f;

View File

@ -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;

View File

@ -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_;
}

View File

@ -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<bool> 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;

View File

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

View File

@ -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 {

View File

@ -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 <cmath>
#include <cstring>
@ -94,7 +94,9 @@
#include <arm_neon.h>
#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<float>(kA0 - kA1 * cos(2.0 * base::kPiDouble * x) +
kA2 * cos(4.0 * base::kPiDouble * x));
static_cast<float>(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

View File

@ -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 <functional>
#include <memory>
#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<float[]> kernel_storage_;
AlignedMemPtr<float[]> kernel_pre_sinc_storage_;
AlignedMemPtr<float[]> kernel_window_storage_;
base::AlignedMemPtr<float[]> kernel_storage_;
base::AlignedMemPtr<float[]> kernel_pre_sinc_storage_;
base::AlignedMemPtr<float[]> kernel_window_storage_;
// 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.
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

View File

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

View File

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

View File

@ -3,7 +3,6 @@
#include <array>
#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 <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 {
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<float[]>(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<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));
});
}
samples_per_channel_ = 0;
eos_ = true;
}
}

View File

@ -5,11 +5,9 @@
#include <memory>
#include <string>
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<float[]> back_buffer_[2];
std::unique_ptr<float[]> 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<float[]> interleaved_data_;
size_t samples_per_channel_ = 0;
std::unique_ptr<char[]> encoded_data_;
std::unique_ptr<mp3dec_ex_t> mp3_dec_;
uint64_t read_pos_ = 0;
bool eos_ = false;
std::unique_ptr<base::SincResampler> resampler_[2];
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);
void StreamInternal(size_t num_samples, bool loop);
};
} // namespace eng

View File

@ -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> sound) {
CHECK(!sound->is_streaming_sound()) << "Streaming sound cannot be shared.";
void SoundPlayer::SetSound(std::shared_ptr<AudioBus> sound) {
sound_ = sound;
}
void SoundPlayer::SetSound(std::unique_ptr<Sound> sound) {
sound_ = std::move(sound);
}
void SoundPlayer::Play(bool loop, float fade_in_duration) {
if (!sound_)
return;

View File

@ -7,15 +7,14 @@
namespace eng {
class Sound;
class AudioBus;
class SoundPlayer {
public:
SoundPlayer();
~SoundPlayer();
void SetSound(std::shared_ptr<Sound> sound);
void SetSound(std::unique_ptr<Sound> sound);
void SetSound(std::shared_ptr<AudioBus> 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> sound_;
std::shared_ptr<AudioBus> sound_;
float max_amplitude_ = 1.0f;