mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-15 07:20:31 +02:00
Oboe uses AAudio on Android 8.1+ and falls back to OpenSL ES on older devices. Output and microphone input both go through it. The mic now gets resampled by Oboe from the device's rate to whatever the game asks for. New: when a stream is disconnected (headphones unplugged, Bluetooth device going away), it's reopened on the new default device instead of going silent. Oboe 1.11.0 is added as a submodule, built through ext/oboe-build for CMake and as a module in Android.mk. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
147 lines
3.9 KiB
C++
147 lines
3.9 KiB
C++
#include "Common/Log.h"
|
|
|
|
#include "android/jni/AndroidAudio.h"
|
|
#include "android/jni/OboeContext.h"
|
|
|
|
std::string g_error;
|
|
std::mutex g_errorMutex;
|
|
|
|
AudioContext::AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate)
|
|
: audioCallback(cb), framesPerBuffer(_FramesPerBuffer), sampleRate(_SampleRate) {
|
|
if (framesPerBuffer == 0)
|
|
framesPerBuffer = 256;
|
|
if (framesPerBuffer < 32)
|
|
framesPerBuffer = 32;
|
|
if (framesPerBuffer > 4096)
|
|
framesPerBuffer = 4096;
|
|
|
|
sampleRate = _SampleRate;
|
|
g_error = "";
|
|
}
|
|
|
|
void AudioContext::SetErrorString(const std::string &error) {
|
|
std::unique_lock<std::mutex> lock(g_errorMutex);
|
|
g_error = error;
|
|
}
|
|
|
|
struct AndroidAudioState {
|
|
AudioContext *ctx = nullptr;
|
|
AndroidAudioCallback callback = nullptr;
|
|
// output
|
|
int frames_per_buffer = 0;
|
|
int sample_rate = 0;
|
|
// input
|
|
int input_enable = 0;
|
|
int input_sample_rate = 0;
|
|
};
|
|
|
|
AndroidAudioState *AndroidAudio_Init(AndroidAudioCallback callback, int optimalFramesPerBuffer, int optimalSampleRate) {
|
|
AndroidAudioState *state = new AndroidAudioState();
|
|
state->callback = callback;
|
|
state->frames_per_buffer = optimalFramesPerBuffer ? optimalFramesPerBuffer : 256;
|
|
state->sample_rate = optimalSampleRate ? optimalSampleRate : 44100;
|
|
return state;
|
|
}
|
|
|
|
bool AndroidAudio_Recording_SetSampleRate(AndroidAudioState *state, int sampleRate) {
|
|
if (!state) {
|
|
ERROR_LOG(Log::Audio, "AndroidAudioState not initialized, cannot set recording sample rate");
|
|
return false;
|
|
}
|
|
state->input_sample_rate = sampleRate;
|
|
INFO_LOG(Log::Audio, "AndroidAudio_Recording_SetSampleRate=%d", sampleRate);
|
|
return true;
|
|
}
|
|
|
|
bool AndroidAudio_Recording_Start(AndroidAudioState *state) {
|
|
if (!state) {
|
|
ERROR_LOG(Log::Audio, "AndroidAudioState not initialized, cannot start recording!");
|
|
return false;
|
|
}
|
|
state->input_enable = 1;
|
|
if (!state->ctx) {
|
|
ERROR_LOG(Log::Audio, "Audio context not initialized, cannot start recording!");
|
|
return false;
|
|
}
|
|
state->ctx->AudioRecord_Start(state->input_sample_rate);
|
|
INFO_LOG(Log::Audio, "AndroidAudio_Recording_Start");
|
|
return true;
|
|
}
|
|
|
|
bool AndroidAudio_Recording_Stop(AndroidAudioState *state) {
|
|
if (!state) {
|
|
ERROR_LOG(Log::Audio, "AndroidAudioState not initialized, cannot stop recording!");
|
|
return false;
|
|
}
|
|
if (!state->ctx) {
|
|
ERROR_LOG(Log::Audio, "Audio context not initialized, cannot stop recording!");
|
|
return false;
|
|
}
|
|
state->input_enable = 0;
|
|
state->input_sample_rate = 0;
|
|
state->ctx->AudioRecord_Stop();
|
|
INFO_LOG(Log::Audio, "AndroidAudio_Recording_Stop");
|
|
return true;
|
|
}
|
|
|
|
bool AndroidAudio_Recording_State(AndroidAudioState *state) {
|
|
if (!state) {
|
|
return false;
|
|
}
|
|
return state->input_enable;
|
|
}
|
|
|
|
bool AndroidAudio_Resume(AndroidAudioState *state) {
|
|
if (!state) {
|
|
ERROR_LOG(Log::Audio, "Audio was shutdown, cannot resume!");
|
|
return false;
|
|
}
|
|
if (!state->ctx) {
|
|
state->ctx = new OboeContext(state->callback, state->frames_per_buffer, state->sample_rate);
|
|
bool init_retval = state->ctx->Init();
|
|
if (!init_retval) {
|
|
delete state->ctx;
|
|
state->ctx = nullptr;
|
|
} else if (state->input_enable) {
|
|
state->ctx->AudioRecord_Start(state->input_sample_rate);
|
|
}
|
|
return init_retval;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AndroidAudio_Pause(AndroidAudioState *state) {
|
|
if (!state) {
|
|
ERROR_LOG(Log::Audio, "Audio was shutdown, cannot pause!");
|
|
return false;
|
|
}
|
|
if (state->ctx) {
|
|
delete state->ctx;
|
|
state->ctx = nullptr;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AndroidAudio_Shutdown(AndroidAudioState *state) {
|
|
if (!state) {
|
|
ERROR_LOG(Log::Audio, "Audio already shutdown!");
|
|
return false;
|
|
}
|
|
if (state->ctx) {
|
|
ERROR_LOG(Log::Audio, "Should not shut down when playing! Something is wrong!");
|
|
return false;
|
|
}
|
|
delete state;
|
|
INFO_LOG(Log::Audio, "Android audio completely unloaded.");
|
|
return true;
|
|
}
|
|
|
|
const std::string AndroidAudio_GetErrorString(AndroidAudioState *state) {
|
|
if (!state) {
|
|
return "No state";
|
|
}
|
|
std::unique_lock<std::mutex> lock(g_errorMutex);
|
|
return g_error;
|
|
}
|