mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 03:57:17 +01:00
setMMapEnabled(true) doesn't mean "allow it", it sets the process's MMAP policy to AUTO, overriding a device whose aaudio.mmap_policy disables MMAP. Since #22766 we did that on every audio start, and a Xiaomi Pad 5 Pro (Android 13) has been stuck on a blank screen at launch since then. The likely cause, not yet confirmed on the device: opening a stream on a MMAP path the vendor turned off, from the UI thread. Now the checkbox can only turn MMAP off, as on the Bluetooth TV. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
215 lines
7.6 KiB
C++
215 lines
7.6 KiB
C++
// Audio output and microphone input through Oboe, which uses AAudio where it's
|
|
// available (Android 8.1+) and falls back to OpenSL ES on older devices.
|
|
|
|
#include <mutex>
|
|
|
|
#include <oboe/Oboe.h>
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/System/System.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "android/jni/OboeContext.h"
|
|
|
|
// Owns the streams. Held by shared_ptr since after a disconnect, Oboe calls the error
|
|
// callback on a thread of its own that can outlive the OboeContext.
|
|
class OboeContext::Streams : public oboe::AudioStreamDataCallback, public oboe::AudioStreamErrorCallback, public std::enable_shared_from_this<OboeContext::Streams> {
|
|
public:
|
|
Streams(AndroidAudioCallback cb, int framesPerBuffer, int sampleRate)
|
|
: audioCallback_(cb), framesPerBuffer_(framesPerBuffer), sampleRate_(sampleRate) {
|
|
// The setting isn't offered before Android 8.1, where AAudio isn't stable.
|
|
if (g_Config.iAndroidAudioApi == (int)AndroidAudioApi::AAUDIO && oboe::AudioStreamBuilder::isAAudioRecommended()) {
|
|
audioApi_ = oboe::AudioApi::AAudio;
|
|
}
|
|
// Global in Oboe, and only read when an AAudio stream is opened. Never enable it beyond what
|
|
// the device's aaudio.mmap_policy allows: setMMapEnabled(true) forces the policy to AUTO, and
|
|
// a Xiaomi Pad 5 Pro (Android 13) got stuck on a blank screen at launch after we started doing that.
|
|
oboe::OboeExtensions::setMMapEnabled(g_Config.bAndroidAudioMMap && oboe::OboeExtensions::isMMapSupported());
|
|
}
|
|
|
|
bool StartOutput() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
return OpenOutput();
|
|
}
|
|
|
|
bool StartInput(int sampleRate) {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
CloseStream(input_);
|
|
// The PSP mic runs at 44100, 22050 or 11025 Hz. Oboe resamples from whatever the device has.
|
|
inputSampleRate_ = sampleRate > 0 ? sampleRate : 44100;
|
|
return OpenInput();
|
|
}
|
|
|
|
void StopInput() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
CloseStream(input_);
|
|
inputSampleRate_ = 0;
|
|
}
|
|
|
|
void Shutdown() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
shutdown_ = true;
|
|
CloseStream(output_);
|
|
CloseStream(input_);
|
|
}
|
|
|
|
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *stream, void *audioData, int32_t numFrames) override {
|
|
if (stream->getDirection() == oboe::Direction::Output) {
|
|
audioCallback_((short *)audioData, numFrames, stream->getSampleRate(), nullptr);
|
|
} else {
|
|
// This is the audio thread, so just buffer it. The CPU thread picks it up.
|
|
AndroidAudio_Recording_Push((const int16_t *)audioData, numFrames);
|
|
}
|
|
return oboe::DataCallbackResult::Continue;
|
|
}
|
|
|
|
// Oboe has already closed the stream by now. A disconnect (headphones unplugged, a Bluetooth
|
|
// device going away) means the route changed, so open a new stream on the new default device.
|
|
void onErrorAfterClose(oboe::AudioStream *stream, oboe::Result error) override {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
if (shutdown_) {
|
|
return;
|
|
}
|
|
const bool isOutput = stream == output_.get();
|
|
const bool isInput = stream == input_.get();
|
|
if (!isOutput && !isInput) {
|
|
return;
|
|
}
|
|
WARN_LOG(Log::Audio, "Oboe: %s stream closed with error: %s", isOutput ? "output" : "input", oboe::convertToText(error));
|
|
if (isOutput) {
|
|
output_.reset();
|
|
} else {
|
|
input_.reset();
|
|
}
|
|
if (error != oboe::Result::ErrorDisconnected) {
|
|
OboeContext::SetErrorString(std::string("Stream error: ") + oboe::convertToText(error));
|
|
return;
|
|
}
|
|
if (isOutput) {
|
|
OpenOutput();
|
|
} else if (inputSampleRate_) {
|
|
OpenInput();
|
|
}
|
|
}
|
|
|
|
private:
|
|
// The ones below are called with mutex_ held.
|
|
bool OpenOutput() {
|
|
oboe::AudioStreamBuilder builder;
|
|
builder.setDirection(oboe::Direction::Output)
|
|
->setAudioApi(audioApi_)
|
|
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
|
->setSharingMode(oboe::SharingMode::Shared)
|
|
->setUsage(oboe::Usage::Game)
|
|
->setFormat(oboe::AudioFormat::I16)
|
|
->setFormatConversionAllowed(true)
|
|
->setChannelCount(oboe::ChannelCount::Stereo)
|
|
->setChannelConversionAllowed(true)
|
|
->setSampleRate(sampleRate_)
|
|
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium)
|
|
->setFramesPerDataCallback(framesPerBuffer_)
|
|
->setDataCallback(this)
|
|
->setErrorCallback(shared_from_this());
|
|
oboe::Result result = builder.openStream(output_);
|
|
if (result != oboe::Result::OK) {
|
|
output_.reset();
|
|
return Fail("Failed to open output stream", result);
|
|
}
|
|
// Double buffering, the same latency as the old OpenSL path.
|
|
output_->setBufferSizeInFrames(output_->getFramesPerBurst() * 2);
|
|
result = output_->requestStart();
|
|
if (result != oboe::Result::OK) {
|
|
CloseStream(output_);
|
|
return Fail("Failed to start output stream", result);
|
|
}
|
|
char info[256];
|
|
snprintf(info, sizeof(info), "%s%s, %d Hz, burst %d, buffer %d, %s, device %d",
|
|
oboe::convertToText(output_->getAudioApi()),
|
|
// Only ask AAudio. An OpenSL ES stream has no AAudio stream underneath.
|
|
output_->getAudioApi() == oboe::AudioApi::AAudio && oboe::OboeExtensions::isMMapUsed(output_.get()) ? " (MMAP)" : "",
|
|
output_->getSampleRate(), output_->getFramesPerBurst(), output_->getBufferSizeInFrames(),
|
|
oboe::convertToText(output_->getPerformanceMode()), output_->getDeviceId());
|
|
INFO_LOG(Log::Audio, "Oboe: Output stream open: %s", info);
|
|
OboeContext::SetInfoString(info);
|
|
return true;
|
|
}
|
|
|
|
bool OpenInput() {
|
|
oboe::AudioStreamBuilder builder;
|
|
builder.setDirection(oboe::Direction::Input)
|
|
->setAudioApi(audioApi_)
|
|
->setSharingMode(oboe::SharingMode::Shared)
|
|
->setFormat(oboe::AudioFormat::I16)
|
|
->setFormatConversionAllowed(true)
|
|
->setChannelCount(oboe::ChannelCount::Mono)
|
|
->setChannelConversionAllowed(true)
|
|
->setSampleRate(inputSampleRate_)
|
|
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium)
|
|
->setDataCallback(this)
|
|
->setErrorCallback(shared_from_this());
|
|
oboe::Result result = builder.openStream(input_);
|
|
if (result != oboe::Result::OK) {
|
|
input_.reset();
|
|
return Fail("Failed to open input stream", result);
|
|
}
|
|
result = input_->requestStart();
|
|
if (result != oboe::Result::OK) {
|
|
CloseStream(input_);
|
|
return Fail("Failed to start input stream", result);
|
|
}
|
|
INFO_LOG(Log::Audio, "Oboe: Input stream open: %d Hz", input_->getSampleRate());
|
|
return true;
|
|
}
|
|
|
|
static void CloseStream(std::shared_ptr<oboe::AudioStream> &stream) {
|
|
if (stream) {
|
|
stream->stop();
|
|
stream->close();
|
|
stream.reset();
|
|
}
|
|
}
|
|
|
|
static bool Fail(const char *what, oboe::Result result) {
|
|
ERROR_LOG(Log::Audio, "Oboe: %s: %s", what, oboe::convertToText(result));
|
|
OboeContext::SetErrorString(std::string(what) + ": " + oboe::convertToText(result));
|
|
return false;
|
|
}
|
|
|
|
AndroidAudioCallback audioCallback_;
|
|
int framesPerBuffer_;
|
|
int sampleRate_;
|
|
oboe::AudioApi audioApi_ = oboe::AudioApi::OpenSLES;
|
|
|
|
std::mutex mutex_;
|
|
bool shutdown_ = false;
|
|
std::shared_ptr<oboe::AudioStream> output_;
|
|
std::shared_ptr<oboe::AudioStream> input_;
|
|
int inputSampleRate_ = 0;
|
|
};
|
|
|
|
OboeContext::OboeContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate)
|
|
: AudioContext(cb, _FramesPerBuffer, _SampleRate) {
|
|
// Only used by the OpenSL ES fallback, which can't query the device's native values itself.
|
|
oboe::DefaultStreamValues::SampleRate = sampleRate;
|
|
oboe::DefaultStreamValues::FramesPerBurst = framesPerBuffer;
|
|
streams_ = std::make_shared<Streams>(cb, framesPerBuffer, sampleRate);
|
|
}
|
|
|
|
bool OboeContext::Init() {
|
|
return streams_->StartOutput();
|
|
}
|
|
|
|
bool OboeContext::AudioRecord_Start(int sampleRate) {
|
|
return streams_->StartInput(sampleRate);
|
|
}
|
|
|
|
bool OboeContext::AudioRecord_Stop() {
|
|
streams_->StopInput();
|
|
return true;
|
|
}
|
|
|
|
OboeContext::~OboeContext() {
|
|
streams_->Shutdown();
|
|
INFO_LOG(Log::Audio, "Oboe: Shutdown - finished");
|
|
}
|