mirror of https://github.com/auygun/kaliber.git
305 lines
8.7 KiB
C++
305 lines
8.7 KiB
C++
#include "engine/audio/audio_mixer.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include "base/log.h"
|
|
#include "base/task_runner.h"
|
|
#include "base/thread_pool.h"
|
|
#include "engine/audio/audio_bus.h"
|
|
|
|
#if defined(__ANDROID__)
|
|
#include "engine/audio/audio_sink_oboe.h"
|
|
#elif defined(__linux__)
|
|
#include "engine/audio/audio_sink_alsa.h"
|
|
#endif
|
|
|
|
using namespace base;
|
|
|
|
namespace eng {
|
|
|
|
AudioMixer::AudioMixer()
|
|
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()),
|
|
#if defined(__ANDROID__)
|
|
audio_sink_{std::make_unique<AudioSinkOboe>(this)} {
|
|
#elif defined(__linux__)
|
|
audio_sink_{std::make_unique<AudioSinkAlsa>(this)} {
|
|
#endif
|
|
bool res = audio_sink_->Initialize();
|
|
CHECK(res) << "Failed to initialize audio sink.";
|
|
}
|
|
|
|
AudioMixer::~AudioMixer() = default;
|
|
|
|
uint64_t AudioMixer::CreateResource() {
|
|
uint64_t resource_id = ++last_resource_id_;
|
|
resources_[resource_id] = std::make_shared<Resource>();
|
|
return resource_id;
|
|
}
|
|
|
|
void AudioMixer::DestroyResource(uint64_t resource_id) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
if (it->second->active) {
|
|
it->second->restart_cb = nullptr;
|
|
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
|
|
}
|
|
resources_.erase(it);
|
|
}
|
|
|
|
void AudioMixer::Play(uint64_t resource_id,
|
|
std::shared_ptr<AudioBus> audio_bus,
|
|
float amplitude,
|
|
bool reset_pos) {
|
|
if (!audio_enabled_)
|
|
return;
|
|
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
if (it->second->active) {
|
|
if (reset_pos)
|
|
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
|
|
|
|
if (it->second->flags.load(std::memory_order_relaxed) & kStopped)
|
|
it->second->restart_cb = [&, resource_id, audio_bus, amplitude,
|
|
reset_pos]() -> void {
|
|
Play(resource_id, audio_bus, amplitude, reset_pos);
|
|
};
|
|
|
|
return;
|
|
}
|
|
|
|
if (reset_pos) {
|
|
it->second->src_index = 0;
|
|
it->second->accumulator = 0;
|
|
audio_bus->ResetStream();
|
|
} else if (it->second->src_index >= audio_bus->samples_per_channel()) {
|
|
return;
|
|
}
|
|
|
|
it->second->active = true;
|
|
it->second->flags.fetch_and(~kStopped, std::memory_order_relaxed);
|
|
it->second->audio_bus = audio_bus;
|
|
if (amplitude >= 0)
|
|
it->second->amplitude = amplitude;
|
|
|
|
std::lock_guard<std::mutex> scoped_lock(lock_);
|
|
play_list_[0].push_back(it->second);
|
|
}
|
|
|
|
void AudioMixer::Stop(uint64_t resource_id) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
if (it->second->active) {
|
|
it->second->restart_cb = nullptr;
|
|
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
|
|
void AudioMixer::SetLoop(uint64_t resource_id, bool loop) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
if (loop)
|
|
it->second->flags.fetch_or(kLoop, std::memory_order_relaxed);
|
|
else
|
|
it->second->flags.fetch_and(~kLoop, std::memory_order_relaxed);
|
|
}
|
|
|
|
void AudioMixer::SetSimulateStereo(uint64_t resource_id, bool simulate) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
if (simulate)
|
|
it->second->flags.fetch_or(kSimulateStereo, std::memory_order_relaxed);
|
|
else
|
|
it->second->flags.fetch_and(~kSimulateStereo, std::memory_order_relaxed);
|
|
}
|
|
|
|
void AudioMixer::SetResampleStep(uint64_t resource_id, size_t step) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
it->second->step.store(step + 100, std::memory_order_relaxed);
|
|
}
|
|
|
|
void AudioMixer::SetMaxAmplitude(uint64_t resource_id, float max_amplitude) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
it->second->max_amplitude.store(max_amplitude, std::memory_order_relaxed);
|
|
}
|
|
|
|
void AudioMixer::SetAmplitudeInc(uint64_t resource_id, float amplitude_inc) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
it->second->amplitude_inc.store(amplitude_inc, std::memory_order_relaxed);
|
|
}
|
|
|
|
void AudioMixer::SetEndCallback(uint64_t resource_id, base::Closure cb) {
|
|
auto it = resources_.find(resource_id);
|
|
if (it == resources_.end())
|
|
return;
|
|
|
|
it->second->end_cb = std::move(cb);
|
|
}
|
|
|
|
void AudioMixer::Suspend() {
|
|
audio_sink_->Suspend();
|
|
}
|
|
|
|
void AudioMixer::Resume() {
|
|
audio_sink_->Resume();
|
|
}
|
|
|
|
size_t AudioMixer::GetHardwareSampleRate() {
|
|
return audio_sink_->GetHardwareSampleRate();
|
|
}
|
|
|
|
void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
|
|
{
|
|
std::unique_lock<std::mutex> scoped_lock(lock_, std::try_to_lock);
|
|
if (scoped_lock)
|
|
play_list_[1].splice(play_list_[1].end(), play_list_[0]);
|
|
}
|
|
|
|
memset(output_buffer, 0, sizeof(float) * num_frames * kChannelCount);
|
|
|
|
for (auto it = play_list_[1].begin(); it != play_list_[1].end();) {
|
|
auto audio_bus = it->get()->audio_bus.get();
|
|
unsigned flags = it->get()->flags.load(std::memory_order_relaxed);
|
|
bool marked_for_removal = false;
|
|
|
|
if (flags & kStopped) {
|
|
marked_for_removal = true;
|
|
} else {
|
|
const float* src[2] = {audio_bus->GetChannelData(0),
|
|
audio_bus->GetChannelData(1)};
|
|
if (!src[1])
|
|
src[1] = src[0]; // mono.
|
|
|
|
size_t num_samples = audio_bus->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;
|
|
float amplitude = it->get()->amplitude;
|
|
float amplitude_inc =
|
|
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) ? audio_bus->sample_rate() / 10 : 0;
|
|
|
|
DCHECK(num_samples > 0);
|
|
|
|
for (size_t i = 0; i < num_frames * kChannelCount;) {
|
|
if (src_index < num_samples) {
|
|
// Mix the 1st channel.
|
|
output_buffer[i++] += src[0][src_index] * amplitude;
|
|
|
|
// Mix the 2nd channel. Offset the source index for stereo simulation.
|
|
size_t ind = channel_offset + src_index;
|
|
if (ind < num_samples)
|
|
output_buffer[i++] += src[1][ind] * amplitude;
|
|
else if (flags & kLoop)
|
|
output_buffer[i++] += src[1][ind % num_samples] * amplitude;
|
|
else
|
|
i++;
|
|
|
|
// Apply amplitude modification.
|
|
amplitude += amplitude_inc;
|
|
if (amplitude <= 0) {
|
|
marked_for_removal = true;
|
|
break;
|
|
} else if (amplitude > max_amplitude) {
|
|
amplitude = max_amplitude;
|
|
}
|
|
|
|
// Advance source index. Apply basic resampling for variations.
|
|
accumulator += step;
|
|
src_index += accumulator / 100;
|
|
accumulator %= 100;
|
|
} else {
|
|
if (audio_bus->EndOfStream()) {
|
|
src_index %= num_samples;
|
|
marked_for_removal = !(flags & kLoop);
|
|
break;
|
|
}
|
|
|
|
if (!it->get()->streaming_in_progress.load(
|
|
std::memory_order_acquire)) {
|
|
src_index %= num_samples;
|
|
it->get()->streaming_in_progress.store(true,
|
|
std::memory_order_relaxed);
|
|
|
|
// Swap buffers and start streaming in background.
|
|
audio_bus->SwapBuffers();
|
|
src[0] = audio_bus->GetChannelData(0);
|
|
src[1] = audio_bus->GetChannelData(1);
|
|
if (!src[1])
|
|
src[1] = src[0]; // mono.
|
|
num_samples = audio_bus->samples_per_channel();
|
|
|
|
ThreadPool::Get().PostTask(
|
|
HERE,
|
|
std::bind(&AudioMixer::DoStream, this, *it, flags & kLoop),
|
|
true);
|
|
} else {
|
|
DLOG << "Mixer buffer underrun!";
|
|
}
|
|
}
|
|
}
|
|
|
|
it->get()->src_index = src_index;
|
|
it->get()->accumulator = accumulator;
|
|
it->get()->amplitude = amplitude;
|
|
}
|
|
|
|
if (marked_for_removal) {
|
|
end_list_.push_back(*it);
|
|
it = play_list_[1].erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
for (auto it = end_list_.begin(); it != end_list_.end();) {
|
|
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);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AudioMixer::DoStream(std::shared_ptr<Resource> resource, bool loop) {
|
|
resource->audio_bus->Stream(loop);
|
|
resource->streaming_in_progress.store(false, std::memory_order_release);
|
|
}
|
|
|
|
void AudioMixer::EndCallback(std::shared_ptr<Resource> resource) {
|
|
resource->active = false;
|
|
|
|
if (resource->end_cb)
|
|
resource->end_cb();
|
|
if (resource->restart_cb) {
|
|
resource->restart_cb();
|
|
resource->restart_cb = nullptr;
|
|
}
|
|
}
|
|
|
|
} // namespace eng
|