/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see .
*/
#ifdef USE_OBOE
#include "audiostream.h"
#include
#include
#include
#include
#include
#include "stdclass.h"
static RingBuffer ringBuffer;
static cResetEvent pushWait;
static std::shared_ptr stream;
static std::shared_ptr recordStream;
static void audio_init();
static void audio_term();
class AudioCallback : public oboe::AudioStreamDataCallback
{
public:
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) override
{
if (!ringBuffer.read((u8 *)audioData, numFrames * 4))
// underrun
memset(audioData, 0, numFrames * 4);
pushWait.Set();
return oboe::DataCallbackResult::Continue;
}
};
static AudioCallback audioCallback;
class AudioErrorCallback : public oboe::AudioStreamErrorCallback
{
public:
void onErrorAfterClose(oboe::AudioStream *stream, oboe::Result error) override {
WARN_LOG(AUDIO, "Audio device lost. Attempting to reopen the audio stream");
audio_term();
audio_init();
}
};
static AudioErrorCallback errorCallback;
static void audio_init()
{
// Actual capacity is size-1 to avoid overrun so add one buffer
ringBuffer.setCapacity((config::AudioBufferSize + SAMPLE_COUNT) * 4);
oboe::AudioStreamBuilder builder;
oboe::Result result = builder.setDirection(oboe::Direction::Output)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSharingMode(oboe::SharingMode::Exclusive)
->setFormat(oboe::AudioFormat::I16)
->setChannelCount(oboe::ChannelCount::Stereo)
->setSampleRate(44100)
->setFramesPerCallback(SAMPLE_COUNT)
->setDataCallback(&audioCallback)
->setErrorCallback(&errorCallback)
->setUsage(oboe::Usage::Game)
->openStream(stream);
if (result != oboe::Result::OK)
{
ERROR_LOG(AUDIO, "Oboe open stream failed: %s", oboe::convertToText(result));
return;
}
if (stream->getAudioApi() == oboe::AudioApi::AAudio && config::AudioBufferSize < 1764)
{
// Reduce internal buffer for low latency (< 40 ms)
int bufSize = stream->getBufferSizeInFrames();
int burst = stream->getFramesPerBurst();
if (bufSize - burst > SAMPLE_COUNT)
{
while (bufSize - burst > SAMPLE_COUNT)
bufSize -= burst;
stream->setBufferSizeInFrames(bufSize);
}
}
stream->requestStart();
NOTICE_LOG(AUDIO, "Oboe driver started. stream capacity: %d frames, size: %d frames, frames/callback: %d, frames/burst: %d",
stream->getBufferCapacityInFrames(), stream->getBufferSizeInFrames(),
stream->getFramesPerCallback(), stream->getFramesPerBurst());
}
static void audio_term()
{
NOTICE_LOG(AUDIO, "Oboe driver stopping");
if (stream != nullptr)
{
stream->stop();
stream->close();
stream.reset();
}
}
static u32 audio_push(const void* frame, u32 samples, bool wait) {
while (!ringBuffer.write((const u8 *)frame, samples * 4) && wait)
pushWait.Wait();
return 1;
}
static void term_record()
{
if (recordStream != nullptr)
{
recordStream->stop();
recordStream->close();
recordStream.reset();
}
NOTICE_LOG(AUDIO, "Oboe recorder stopped");
}
static bool init_record(u32 sampling_freq)
{
oboe::AudioStreamBuilder builder;
oboe::Result result = builder.setDirection(oboe::Direction::Input)
->setPerformanceMode(oboe::PerformanceMode::None)
->setSharingMode(oboe::SharingMode::Exclusive)
->setFormat(oboe::AudioFormat::I16)
->setChannelCount(oboe::ChannelCount::Mono)
->setSampleRate(sampling_freq)
->openStream(recordStream);
if (result != oboe::Result::OK)
{
ERROR_LOG(AUDIO, "Oboe open record stream failed: %s", oboe::convertToText(result));
return false;
}
recordStream->requestStart();
NOTICE_LOG(AUDIO, "Oboe recorder started. stream capacity: %d frames",
stream->getBufferCapacityInFrames());
return true;
}
static u32 record(void *data, u32 samples)
{
if (recordStream == nullptr)
return 0;
oboe::ResultWithValue result = recordStream->read(data, samples, 0);
if (result == oboe::Result::ErrorDisconnected)
{
WARN_LOG(AUDIO, "Recording device lost. Attempting to reopen the audio stream");
u32 sampleRate = recordStream->getSampleRate();
term_record();
init_record(sampleRate);
}
return std::max(0, result.value());
}
static audiobackend_t audiobackend_oboe = {
"Oboe", // Slug
"Automatic AAudio / OpenSL selection", // Name
&audio_init,
&audio_push,
&audio_term,
NULL,
&init_record,
&record,
&term_record
};
static bool oboebe = RegisterAudioBackend(&audiobackend_oboe);
#endif