mirror of
https://github.com/izzy2lost/PSX2.git
synced 2026-07-05 15:18:36 -07:00
fixed crash caused by oboe settings
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user