From 90cb284837300f7a41a8df90b60b01010f02cbb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 15 Jun 2021 00:23:45 +0200 Subject: [PATCH] Remove asserts from OpenSL init. Expose error inside audio settings --- UI/GameSettingsScreen.cpp | 14 +++++ android/jni/AndroidAudio.cpp | 10 +++ android/jni/AndroidAudio.h | 19 +++++- android/jni/OpenSLContext.cpp | 112 +++++++++++++++++++--------------- android/jni/OpenSLContext.h | 8 ++- android/jni/app-android.cpp | 2 +- 6 files changed, 111 insertions(+), 54 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index f7e3f029c6..9051606286 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -80,6 +80,14 @@ #include "Windows/W32Util/ShellUtil.h" #endif +#if PPSSPP_PLATFORM(ANDROID) + +#include "android/jni/AndroidAudio.h" + +extern AndroidAudioState *g_audioState; + +#endif + GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID, bool editThenRestore) : UIDialogScreenWithGameBackground(gamePath), gameID_(gameID), enableReports_(false), editThenRestore_(editThenRestore) { lastVertical_ = UseVerticalLayout(); @@ -640,6 +648,12 @@ void GameSettingsScreen::CreateViews() { #if defined(__ANDROID__) CheckBox *extraAudio = audioSettings->Add(new CheckBox(&g_Config.bExtraAudioBuffering, a->T("AudioBufferingForBluetooth", "Bluetooth-friendly buffer (slower)"))); extraAudio->SetEnabledPtr(&g_Config.bEnableSound); + + // Show OpenSL debug info + const std::string audioErrorStr = AndroidAudio_GetErrorString(g_audioState); + if (!audioErrorStr.empty()) { + audioSettings->Add(new InfoItem(a->T("Audio Error"), audioErrorStr)); + } #endif // Control diff --git a/android/jni/AndroidAudio.cpp b/android/jni/AndroidAudio.cpp index 33297fd734..68926850dd 100644 --- a/android/jni/AndroidAudio.cpp +++ b/android/jni/AndroidAudio.cpp @@ -132,3 +132,13 @@ bool AndroidAudio_Shutdown(AndroidAudioState *state) { INFO_LOG(AUDIO, "OpenSLWrap completely unloaded."); return true; } + +const std::string AndroidAudio_GetErrorString(AndroidAudioState *state) { + if (!state) { + return "No state"; + } + if (!state->ctx) { + return "No context"; + } + return state->ctx->GetErrorString(); +} diff --git a/android/jni/AndroidAudio.h b/android/jni/AndroidAudio.h index 3a8fe5e33c..30c79ea2d7 100644 --- a/android/jni/AndroidAudio.h +++ b/android/jni/AndroidAudio.h @@ -1,6 +1,7 @@ #pragma once #include +#include typedef int (*AndroidAudioCallback)(short *buffer, int num_samples); @@ -8,19 +9,32 @@ class AudioContext { public: AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate); virtual bool Init() { return false; } - virtual int AudioRecord_Start(int sampleRate) { return false; }; - virtual int AudioRecord_Stop() { return false; }; + virtual bool AudioRecord_Start(int sampleRate) { return false; }; + virtual bool AudioRecord_Stop() { return false; }; + const std::string &GetErrorString() { + std::unique_lock lock(errorMutex_); + return error_; + } + virtual ~AudioContext() {} protected: + void SetErrorString(const std::string &error) { + std::unique_lock lock(errorMutex_); + error_ = error; + } AndroidAudioCallback audioCallback; int framesPerBuffer; int sampleRate; + std::string error_ = ""; + std::mutex errorMutex_; }; struct AndroidAudioState; +// TODO: Get rid of this unnecessary wrapper layer from the old .so days + // It's okay for optimalFramesPerBuffer and optimalSampleRate to be 0. Defaults will be used. AndroidAudioState *AndroidAudio_Init(AndroidAudioCallback cb, int optimalFramesPerBuffer, int optimalSampleRate); bool AndroidAudio_Recording_SetSampleRate(AndroidAudioState *state, int sampleRate); @@ -30,3 +44,4 @@ bool AndroidAudio_Recording_State(AndroidAudioState *state); bool AndroidAudio_Pause(AndroidAudioState *state); bool AndroidAudio_Resume(AndroidAudioState *state); bool AndroidAudio_Shutdown(AndroidAudioState *state); +const std::string AndroidAudio_GetErrorString(AndroidAudioState *state); diff --git a/android/jni/OpenSLContext.cpp b/android/jni/OpenSLContext.cpp index 47841567e0..ce568536d7 100644 --- a/android/jni/OpenSLContext.cpp +++ b/android/jni/OpenSLContext.cpp @@ -10,6 +10,7 @@ #include #include "Common/Log.h" +#include "Common/StringUtils.h" #include "OpenSLContext.h" #include "Core/HLE/sceUsbMic.h" @@ -19,18 +20,14 @@ void OpenSLContext::bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *c SLuint32 recordsState; result = (*ctx->recorderRecord)->GetRecordState(ctx->recorderRecord, &recordsState); - if(result != SL_RESULT_SUCCESS) { - ERROR_LOG(AUDIO, "GetRecordState error: %d", result); + if (!CheckResultStatic(result, "GetRecordState error: %d")) return; - } Microphone::addAudioData((uint8_t*) ctx->recordBuffer[ctx->activeRecordBuffer], ctx->recordBufferSize); if (recordsState == SL_RECORDSTATE_RECORDING) { result = (*ctx->recorderBufferQueue)->Enqueue(ctx->recorderBufferQueue, ctx->recordBuffer[ctx->activeRecordBuffer], ctx->recordBufferSize); - if (result != SL_RESULT_SUCCESS) { - ERROR_LOG(AUDIO, "Enqueue error: %d", result); - } + CheckResultStatic(result, "Enqueue error"); } ctx->activeRecordBuffer += 1; // Switch buffer @@ -61,7 +58,7 @@ void OpenSLContext::BqPlayerCallback(SLAndroidSimpleBufferQueueItf bq) { memset(buffer[curBuffer] + renderedFrames * 2, 0, byteCount); } SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeInBytes); - + CheckResult(result, StringFromFormat("Failed to enqueue: %d %d", renderedFrames, sizeInBytes).c_str()); // Comment from sample code: // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, // which for this code example would indicate a programming error @@ -82,27 +79,30 @@ bool OpenSLContext::Init() { SLresult result; // create engine result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); - if (result != SL_RESULT_SUCCESS) { - ERROR_LOG(AUDIO, "OpenSL: Failed to create the engine: %d", (int)result); + if (!CheckResult(result, "slCreateEngine")) { engineObject = nullptr; return false; } + result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "engine->Realize")) + return false; result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "engine->GetInterface(ENGINE)")) + return false; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0); - if (result != SL_RESULT_SUCCESS) { - ERROR_LOG(AUDIO, "OpenSL: Failed to create output mix: %d", (int)result); + if (!CheckResult(result, "engine->CreateOutputMix(ENGINE)")) { (*engineObject)->Destroy(engineObject); engineEngine = nullptr; engineObject = nullptr; return false; } + result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "outputMix->Realize")) + return false; // The constants, such as SL_SAMPLINGRATE_44_1, are just 44100000. SLuint32 sr = (SLuint32)sampleRate * 1000; @@ -142,18 +142,23 @@ bool OpenSLContext::Init() { } result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "player->Realize")) + return false; // TODO: Release stuff! result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); - _assert_(SL_RESULT_SUCCESS == result); - result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, - &bqPlayerBufferQueue); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "player->GetInterface(PLAY)")) + return false; // TODO: Release stuff! + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); + if (!CheckResult(result, "player->GetInterface(BUFFER_QUEUE)")) + return false; // TODO: Release stuff! result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, &bqPlayerCallbackWrap, this); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "playerbq->RegisterCallback()")) + return false; // TODO: Release stuff! result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "playerbq->GetInterface()")) + return false; // TODO: Release stuff! result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); - _assert_(SL_RESULT_SUCCESS == result); + if (!CheckResult(result, "playerbq->SetPlayState(PLAYING)")) + return false; // TODO: Release stuff! // Allocate and enqueue N empty buffers. for (int i = 0; i < NUM_BUFFERS; i++) { @@ -172,9 +177,14 @@ bool OpenSLContext::Init() { return true; } -int OpenSLContext::AudioRecord_Start(int sampleRate) { +bool OpenSLContext::AudioRecord_Start(int sampleRate) { SLresult result; + if (!engineEngine) { + SetErrorString("AudioRecord_Start: No engine"); + return false; + } + // configure audio source SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; SLDataSource audioSrc = {&loc_dev, NULL}; @@ -197,38 +207,29 @@ int OpenSLContext::AudioRecord_Start(int sampleRate) { const SLboolean req[1] = {SL_BOOLEAN_TRUE}; result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, sizeof(id)/sizeof(id[0]), id, req); - if (SL_RESULT_SUCCESS != result) { - ERROR_LOG(AUDIO, "CreateAudioRecorder failed: %d", result); + if (!CheckResult(result, "CreateAudioRecorder failed")) return false; - } // realize the audio recorder result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); - if (SL_RESULT_SUCCESS != result) { - ERROR_LOG(AUDIO, "Realize failed: %d", result); + if (!CheckResult(result, "recorderObject->Realize failed")) return false; - } + // get the record interface result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); - if (SL_RESULT_SUCCESS != result) { - ERROR_LOG(AUDIO, "GetInterface(record) failed: %d", result); + if (!CheckResult(result, "GetInterface(recorderObject) failed")) return false; - } // get the buffer queue interface result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, (void*) &recorderBufferQueue); - if (SL_RESULT_SUCCESS != result) { - ERROR_LOG(AUDIO, "GetInterface(queue interface) failed: %d", result); + if (!CheckResult(result, "GetInterface(queue interface) failed")) return false; - } // register callback on the buffer queue result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, &bqRecorderCallback, this); - if (SL_RESULT_SUCCESS != result) { - ERROR_LOG(AUDIO, "RegisterCallback failed: %d", result); + if (!CheckResult(result, "RegisterCallback failed")) return false; - } recordBufferSize = (44100 * 20 / 1000 * 2); for (int i = 0; i < NUM_BUFFERS; i++) { @@ -236,22 +237,18 @@ int OpenSLContext::AudioRecord_Start(int sampleRate) { } for (int i = 0; i < NUM_BUFFERS; i++) { result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer[i], recordBufferSize); - if (SL_RESULT_SUCCESS != result) { - ERROR_LOG(AUDIO, "Enqueue failed: %d", result); + if (!CheckResult(result, "Enqueue failed")) return false; - } } result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); - _assert_(SL_RESULT_SUCCESS == result); - - return true; + return CheckResult(result, "SetRecordState(recording) failed"); } -int OpenSLContext::AudioRecord_Stop() { +bool OpenSLContext::AudioRecord_Stop() { if (recorderRecord != nullptr) { SLresult result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); - _assert_(SL_RESULT_SUCCESS == result); + CheckResult(result, "SetRecordState(stopped) failed"); } if (recorderObject != nullptr) { (*recorderObject)->Destroy(recorderObject); @@ -274,9 +271,7 @@ OpenSLContext::~OpenSLContext() { INFO_LOG(AUDIO, "OpenSL: Shutdown - stopping playback"); SLresult result; result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); - if (SL_RESULT_SUCCESS != result) { - ERROR_LOG(AUDIO, "SetPlayState failed"); - } + CheckResult(result, "SetPlayState(stopped) failed"); } INFO_LOG(AUDIO, "OpenSL: Shutdown - deleting player object"); @@ -311,3 +306,22 @@ OpenSLContext::~OpenSLContext() { } INFO_LOG(AUDIO, "OpenSL: Shutdown - finished"); } + +bool OpenSLContext::CheckResult(SLresult result, const char *str) { + if (result != SL_RESULT_SUCCESS) { + ERROR_LOG(AUDIO, "OpenSL failure (%s): %d", str, result); + SetErrorString(str); + return false; + } else { + return true; + } +} + +bool OpenSLContext::CheckResultStatic(SLresult result, const char *str) { + if (result != SL_RESULT_SUCCESS) { + ERROR_LOG(AUDIO, "OpenSL failure (%s): %d", str, result); + return false; + } else { + return true; + } +} diff --git a/android/jni/OpenSLContext.h b/android/jni/OpenSLContext.h index e95afa8502..81a4c9bc60 100644 --- a/android/jni/OpenSLContext.h +++ b/android/jni/OpenSLContext.h @@ -10,11 +10,15 @@ public: OpenSLContext(AndroidAudioCallback cb, int framesPerBuffer, int sampleRate); bool Init() override; - int AudioRecord_Start(int sampleRate) override; - int AudioRecord_Stop() override; + bool AudioRecord_Start(int sampleRate) override; + bool AudioRecord_Stop() override; + ~OpenSLContext(); private: + bool CheckResult(SLresult result, const char *str); + static bool CheckResultStatic(SLresult result, const char *str); + // Should be no reason to need more than two buffers, but make it clear in the code. enum { NUM_BUFFERS = 2, diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index afb5a658d2..cdc19ebae8 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -113,7 +113,7 @@ static std::atomic emuThreadState((int)EmuThreadState::DISABLED); void UpdateRunLoopAndroid(JNIEnv *env); -static AndroidAudioState *g_audioState; +AndroidAudioState *g_audioState; struct FrameCommand { FrameCommand() {}