// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL #include "audio-output.h" #include "circular-buf.hpp" #include #include #include #define BUFFER_CHUNK_SIZE 1024 #define BUFFER_CHUNKS_COUNT 32 using AudioBuffer = CircularBuffer; class AudioOutput; class AudioOutputCallback: public oboe::AudioStreamCallback { private: AudioOutput *audio_output; public: AudioOutputCallback(AudioOutput *audio_output) : audio_output(audio_output) {} oboe::DataCallbackResult onAudioReady(oboe::AudioStream *stream, void *audioData, int32_t numFrames) override; void onErrorBeforeClose(oboe::AudioStream *stream, oboe::Result error) override; void onErrorAfterClose(oboe::AudioStream *stream, oboe::Result error) override; }; struct AudioOutput { ChiakiLog *log; oboe::ManagedStream stream; AudioOutputCallback stream_callback; AudioBuffer buf; AudioOutput() : stream_callback(this) {} }; extern "C" void *android_chiaki_audio_output_new(ChiakiLog *log) { auto r = new AudioOutput(); r->log = log; return r; } extern "C" void android_chiaki_audio_output_free(void *audio_output) { if(!audio_output) return; auto ao = reinterpret_cast(audio_output); ao->stream = nullptr; delete ao; } extern "C" void android_chiaki_audio_output_settings(uint32_t channels, uint32_t rate, void *audio_output) { auto ao = reinterpret_cast(audio_output); oboe::AudioStreamBuilder builder; builder.setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setSharingMode(oboe::SharingMode::Exclusive) ->setFormat(oboe::AudioFormat::I16) ->setChannelCount(channels) ->setSampleRate(rate) ->setCallback(&ao->stream_callback); auto result = builder.openManagedStream(ao->stream); if(result == oboe::Result::OK) CHIAKI_LOGI(ao->log, "Audio Output opened Oboe stream"); else CHIAKI_LOGE(ao->log, "Audio Output failed to open Oboe stream: %s", oboe::convertToText(result)); result = ao->stream->start(); if(result == oboe::Result::OK) CHIAKI_LOGI(ao->log, "Audio Output started Oboe stream"); else CHIAKI_LOGE(ao->log, "Audio Output failed to start Oboe stream: %s", oboe::convertToText(result)); } extern "C" void android_chiaki_audio_output_frame(int16_t *buf, size_t samples_count, void *audio_output) { auto ao = reinterpret_cast(audio_output); size_t buf_size = samples_count * sizeof(int16_t); size_t pushed = ao->buf.Push(reinterpret_cast(buf), buf_size); if(pushed < buf_size) CHIAKI_LOGW(ao->log, "Audio Output Buffer Overflow!"); } oboe::DataCallbackResult AudioOutputCallback::onAudioReady(oboe::AudioStream *stream, void *audio_data, int32_t num_frames) { if(stream->getFormat() != oboe::AudioFormat::I16) { CHIAKI_LOGE(audio_output->log, "Oboe stream has invalid format in callback"); return oboe::DataCallbackResult::Stop; } int32_t bytes_per_frame = stream->getBytesPerFrame(); size_t buf_size_requested = static_cast(bytes_per_frame * num_frames); auto buf = reinterpret_cast(audio_data); size_t buf_size_delivered = audio_output->buf.Pop(buf, buf_size_requested); //CHIAKI_LOGW(audio_output->log, "Delivered %llu", (unsigned long long)buf_size_delivered); if(buf_size_delivered < buf_size_requested) { CHIAKI_LOGV(audio_output->log, "Audio Output Buffer Underflow!"); memset(buf + buf_size_delivered, 0, buf_size_requested - buf_size_delivered); } return oboe::DataCallbackResult::Continue; } void AudioOutputCallback::onErrorBeforeClose(oboe::AudioStream *stream, oboe::Result error) { CHIAKI_LOGE(audio_output->log, "Oboe reported error before close: %s", oboe::convertToText(error)); } void AudioOutputCallback::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result error) { CHIAKI_LOGE(audio_output->log, "Oboe reported error after close: %s", oboe::convertToText(error)); }