fixed crash caused by oboe settings

This commit is contained in:
izzy2lost
2025-12-04 12:42:22 -05:00
parent 14076f6748
commit 0440104da6
5 changed files with 89 additions and 23 deletions
+3 -3
View File
@@ -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);
@@ -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<int32_t>(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;
+28 -5
View File
@@ -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);
@@ -147,10 +147,10 @@ public class CoversAdapter extends RecyclerView.Adapter<CoversAdapter.VH> {
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");
@@ -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) {