mirror of https://github.com/auygun/kaliber.git
Refactoring audio code
- Add AudioBus - Move resampling to AudioBus - Move SincResampler to engine/audio
This commit is contained in:
parent
29ce485ca3
commit
7b637a95d9
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -29,7 +29,7 @@ void AudioSinkOboe::Resume() {
|
|||
stream_->start();
|
||||
}
|
||||
|
||||
int AudioSinkOboe::GetHardwareSampleRate() {
|
||||
size_t AudioSinkOboe::GetHardwareSampleRate() {
|
||||
return stream_->getSampleRate();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -474,7 +474,7 @@ const std::string& Engine::GetSharedDataPath() const {
|
|||
return platform_->GetSharedDataPath();
|
||||
}
|
||||
|
||||
int Engine::GetAudioHardwareSampleRate() {
|
||||
size_t Engine::GetAudioHardwareSampleRate() {
|
||||
return audio_mixer_->GetHardwareSampleRate();
|
||||
}
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ class Engine : public PlatformObserver {
|
|||
|
||||
const std::string& GetSharedDataPath() const;
|
||||
|
||||
int GetAudioHardwareSampleRate();
|
||||
size_t GetAudioHardwareSampleRate();
|
||||
|
||||
bool IsMobile() const;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue