kaliber/src/engine/audio/audio_mixer.cc

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