From 0440104da61b4cd97eb21a1c748a4e71aade8e12 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Thu, 4 Dec 2025 12:42:22 -0500 Subject: [PATCH] fixed crash caused by oboe settings --- app/src/main/cpp/native-lib.cpp | 6 +-- .../main/cpp/pcsx2/Host/OboeAudioStream.cpp | 26 +++++++++--- app/src/main/cpp/pcsx2/SPU2/spu2.cpp | 33 ++++++++++++--- .../com/izzy2lost/psx2/CoversAdapter.java | 6 +-- .../java/com/izzy2lost/psx2/SafManager.java | 41 ++++++++++++++++--- 5 files changed, 89 insertions(+), 23 deletions(-) diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index 0bba203..6c49df3 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -204,10 +204,10 @@ Java_com_izzy2lost_psx2_NativeApp_initialize(JNIEnv *env, jclass clazz, si.SetBoolValue("InputSources", "SDL", true); si.SetBoolValue("InputSources", "XInput", false); - // Use Oboe audio backend for Android (low-latency audio) + // audio output by default on Android si.SetStringValue("SPU2/Output", "Backend", "Oboe"); - si.SetIntValue("SPU2/Output", "BufferMS", 100); // 100ms buffer for stability - si.SetIntValue("SPU2/Output", "OutputLatencyMS", 20); // 20ms output latency + si.SetIntValue("SPU2/Output", "BufferMS", 150); + si.SetIntValue("SPU2/Output", "OutputLatencyMS", 40); // none of the bindings are going to resolve to anything Pad::ClearPortBindings(si, 0); diff --git a/app/src/main/cpp/pcsx2/Host/OboeAudioStream.cpp b/app/src/main/cpp/pcsx2/Host/OboeAudioStream.cpp index 6441abe..d959af1 100644 --- a/app/src/main/cpp/pcsx2/Host/OboeAudioStream.cpp +++ b/app/src/main/cpp/pcsx2/Host/OboeAudioStream.cpp @@ -112,13 +112,15 @@ bool OboeAudioStream::Open() { oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Output); builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); - builder.setSharingMode(oboe::SharingMode::Shared); + builder.setSharingMode(oboe::SharingMode::Exclusive); builder.setFormat(oboe::AudioFormat::I16); builder.setSampleRate(m_sample_rate); - builder.setChannelCount(m_output_channels==2 ? oboe::ChannelCount::Stereo : oboe::ChannelCount::Mono); + builder.setChannelCount(m_output_channels >= 2 ? oboe::ChannelCount::Stereo : oboe::ChannelCount::Mono); builder.setDeviceId(oboe::kUnspecified); - builder.setBufferCapacityInFrames(2048 * 2); - builder.setFramesPerDataCallback(2048); + + const int32_t buffer_frames = static_cast(AudioStream::GetBufferSizeForMS(m_sample_rate, m_parameters.buffer_ms)); + builder.setBufferCapacityInFrames(buffer_frames); + builder.setFramesPerDataCallback(AudioStream::CHUNK_SIZE); builder.setDataCallback(this); builder.setErrorCallback(this); @@ -126,8 +128,20 @@ bool OboeAudioStream::Open() { oboe::Result result = builder.openStream(m_stream); if (result != oboe::Result::OK) { - Console.Error("(OboeMod) openStream() failed: %d", result); - return false; + Console.Error("(OboeMod) openStream() failed: %d (exclusive), retrying shared", result); + builder.setSharingMode(oboe::SharingMode::Shared); + result = builder.openStream(m_stream); + if (result != oboe::Result::OK) + { + Console.Error("(OboeMod) openStream() failed: %d (shared)", result); + return false; + } + } + + // Try to request our desired buffer size; ignore failures. + if (m_stream) + { + m_stream->setBufferSizeInFrames(buffer_frames); } return true; diff --git a/app/src/main/cpp/pcsx2/SPU2/spu2.cpp b/app/src/main/cpp/pcsx2/SPU2/spu2.cpp index 7a59c50..84b793a 100644 --- a/app/src/main/cpp/pcsx2/SPU2/spu2.cpp +++ b/app/src/main/cpp/pcsx2/SPU2/spu2.cpp @@ -103,15 +103,38 @@ void SPU2::CreateOutputStream() s_output_stream.reset(); Error error; - s_output_stream = AudioStream::CreateStream(EmuConfig.SPU2.Backend, sample_rate, EmuConfig.SPU2.StreamParameters, + const AudioBackend requested_backend = EmuConfig.SPU2.Backend; + s_output_stream = AudioStream::CreateStream(requested_backend, sample_rate, EmuConfig.SPU2.StreamParameters, EmuConfig.SPU2.DriverName.c_str(), EmuConfig.SPU2.DeviceName.c_str(), EmuConfig.SPU2.IsTimeStretchEnabled(), &error); if (!s_output_stream) { - Host::ReportErrorAsync("Error", - fmt::format("Failed to create or configure audio stream, falling back to null output. The error was:\n{}", - error.GetDescription())); +#ifdef __ANDROID__ + // If the requested backend fails on Android, try Oboe before falling back to null so we still get audio. + if (requested_backend != AudioBackend::Oboe) + { + Console.Warning("Audio backend {} failed ({}), retrying with Oboe.", + AudioStream::GetBackendName(requested_backend), error.GetDescription().c_str()); + Error oboe_error; + s_output_stream = AudioStream::CreateStream(AudioBackend::Oboe, sample_rate, EmuConfig.SPU2.StreamParameters, + EmuConfig.SPU2.DriverName.c_str(), EmuConfig.SPU2.DeviceName.c_str(), EmuConfig.SPU2.IsTimeStretchEnabled(), + &oboe_error); - s_output_stream = AudioStream::CreateNullStream(sample_rate, EmuConfig.SPU2.StreamParameters.buffer_ms); + if (!s_output_stream) + { + Console.Warning("Oboe backend also failed ({}), falling back to null output.", oboe_error.GetDescription().c_str()); + error = std::move(oboe_error); // present the most recent failure to the user + } + } +#endif + + if (!s_output_stream) + { + Host::ReportErrorAsync("Error", + fmt::format("Failed to create or configure audio stream, falling back to null output. The error was:\n{}", + error.GetDescription())); + + s_output_stream = AudioStream::CreateNullStream(sample_rate, EmuConfig.SPU2.StreamParameters.buffer_ms); + } } s_output_stream->SetOutputVolume(volume); diff --git a/app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java b/app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java index b0b9378..bac7bc2 100644 --- a/app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java +++ b/app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java @@ -147,10 +147,10 @@ public class CoversAdapter extends RecyclerView.Adapter { private Object getPlaceholder() { // Try SAF resources/no-cover.png first - android.net.Uri dataRoot = SafManager.getDataRootUri(context); - if (dataRoot != null) { + androidx.documentfile.provider.DocumentFile root = SafManager.getDataRoot(context); + if (root != null && root.canRead()) { androidx.documentfile.provider.DocumentFile f = SafManager.getChild(context, new String[]{"resources"}, "no-cover.png"); - if (f != null && f.exists()) return f.getUri(); + if (f != null && f.exists() && f.length() > 0) return f.getUri(); } // Then try app external files path File resDir = context.getExternalFilesDir("resources"); diff --git a/app/src/main/java/com/izzy2lost/psx2/SafManager.java b/app/src/main/java/com/izzy2lost/psx2/SafManager.java index 7cd6299..7a2f555 100644 --- a/app/src/main/java/com/izzy2lost/psx2/SafManager.java +++ b/app/src/main/java/com/izzy2lost/psx2/SafManager.java @@ -4,8 +4,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.UriPermission; import android.net.Uri; import android.provider.DocumentsContract; +import android.util.Log; import androidx.documentfile.provider.DocumentFile; @@ -20,6 +22,7 @@ import java.io.OutputStream; public final class SafManager { private static final String PREFS = "app_prefs"; private static final String KEY_DATA_ROOT = "data_root_tree_uri"; + private static final String TAG = "SafManager"; private SafManager() {} @@ -34,9 +37,20 @@ public final class SafManager { prefs.edit().putString(KEY_DATA_ROOT, treeUri != null ? treeUri.toString() : null).apply(); } + private static boolean hasPersistedPermission(Context ctx, Uri uri) { + if (uri == null) return false; + for (UriPermission perm : ctx.getContentResolver().getPersistedUriPermissions()) { + if (uri.equals(perm.getUri()) && perm.isReadPermission()) { + return true; + } + } + return false; + } + public static DocumentFile getDataRoot(Context ctx) { Uri u = getDataRootUri(ctx); if (u == null) return null; + if (!hasPersistedPermission(ctx, u)) return null; return DocumentFile.fromTreeUri(ctx, u); } @@ -46,10 +60,21 @@ public final class SafManager { DocumentFile cur = root; for (String seg : segments) { if (seg == null || seg.isEmpty()) continue; - DocumentFile next = cur.findFile(seg); - if (next == null) next = cur.createDirectory(seg); - if (next == null) return null; - cur = next; + try { + DocumentFile next = cur.findFile(seg); + if (next == null) { + if (!cur.canWrite()) { + // Cannot create without permission; fail gracefully. + return null; + } + next = cur.createDirectory(seg); + } + if (next == null) return null; + cur = next; + } catch (SecurityException | IllegalStateException e) { + Log.w(TAG, "Unable to access SAF directory segment '" + seg + "'", e); + return null; + } } return cur; } @@ -57,8 +82,12 @@ public final class SafManager { public static DocumentFile getChild(Context ctx, String[] dirSegments, String filename) { DocumentFile dir = getOrCreateDir(ctx, dirSegments); if (dir == null) return null; - DocumentFile f = dir.findFile(filename); - return f; + try { + return dir.findFile(filename); + } catch (Exception e) { + Log.w(TAG, "Unable to access SAF file '" + filename + "'", e); + return null; + } } public static DocumentFile createChild(Context ctx, String[] dirSegments, String filename, String mime) {