added menu on back select/start combo. audio choices in config

This commit is contained in:
izzy2lost
2026-02-17 19:29:18 -05:00
parent fce5a8d19f
commit 55262dde6d
11 changed files with 517 additions and 87 deletions
+2 -2
View File
@@ -30,8 +30,8 @@ android {
minSdk = 26
targetSdk = 36
versionCode = 5
versionName = "1.0.4"
versionCode = 6
versionName = "1.0.5"
ndk {
abiFilters += listOf("arm64-v8a")
+251 -67
View File
@@ -10,6 +10,7 @@
#include <android/asset_manager_jni.h>
#include <jni.h>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
@@ -179,6 +180,37 @@ static int GetTcgTbSizeFromEnv() {
return static_cast<int>(parsed);
}
static std::string ToLowerAscii(std::string value) {
for (char& c : value) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
return value;
}
static std::string ResolveAndroidAudioDriverHint() {
constexpr const char* kDefaultAudioDriverHint = "openslES,aaudio,android";
const char* value = SDL_getenv("XEMU_ANDROID_AUDIO_DRIVER");
if (!value || value[0] == '\0') {
return kDefaultAudioDriverHint;
}
std::string raw(value);
std::string normalized = ToLowerAscii(raw);
if (normalized == "auto" || normalized == "default") {
return kDefaultAudioDriverHint;
}
if (normalized == "opensl" || normalized == "opensles") {
return "openslES,aaudio,android";
}
if (normalized == "aaudio") {
return "aaudio,openslES,android";
}
if (normalized == "android" || normalized == "audiotrack") {
return "android,openslES,aaudio";
}
return raw;
}
static JNIEnv* GetEnv() {
return static_cast<JNIEnv*>(SDL_AndroidGetJNIEnv());
}
@@ -229,83 +261,231 @@ static bool ShouldEnableInlineAioWorkaround(const std::string& crash_flag_path)
}
static std::string GetPrefString(JNIEnv* env, jobject activity, const char* key) {
jclass activityClass = env->GetObjectClass(activity);
jmethodID getPrefs = env->GetMethodID(activityClass, "getSharedPreferences",
"(Ljava/lang/String;I)Landroid/content/SharedPreferences;");
if (!getPrefs) return {};
jstring prefsName = env->NewStringUTF(kPrefsName);
jobject prefs = env->CallObjectMethod(activity, getPrefs, prefsName, 0);
env->DeleteLocalRef(prefsName);
if (HasException(env, "getSharedPreferences") || !prefs) return {};
if (!env || !activity || !key || key[0] == '\0') {
return {};
}
jclass prefsClass = env->GetObjectClass(prefs);
jmethodID getString = env->GetMethodID(
std::string out;
jclass activityClass = nullptr;
jobject prefs = nullptr;
jclass prefsClass = nullptr;
jstring value = nullptr;
jmethodID getPrefs = nullptr;
jmethodID getString = nullptr;
activityClass = env->GetObjectClass(activity);
if (!activityClass) {
return {};
}
getPrefs = env->GetMethodID(activityClass, "getSharedPreferences",
"(Ljava/lang/String;I)Landroid/content/SharedPreferences;");
if (!getPrefs) {
goto cleanup;
}
{
jstring prefsName = env->NewStringUTF(kPrefsName);
if (!prefsName) {
goto cleanup;
}
prefs = env->CallObjectMethod(activity, getPrefs, prefsName, 0);
env->DeleteLocalRef(prefsName);
}
if (HasException(env, "getSharedPreferences") || !prefs) {
goto cleanup;
}
prefsClass = env->GetObjectClass(prefs);
if (!prefsClass) {
goto cleanup;
}
getString = env->GetMethodID(
prefsClass, "getString", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
if (!getString) return {};
if (!getString) {
goto cleanup;
}
jstring jkey = env->NewStringUTF(key);
jstring jdefault = nullptr;
jstring value = static_cast<jstring>(env->CallObjectMethod(prefs, getString, jkey, jdefault));
env->DeleteLocalRef(jkey);
if (HasException(env, "SharedPreferences.getString")) return {};
{
jstring jkey = env->NewStringUTF(key);
if (!jkey) {
goto cleanup;
}
jstring jdefault = nullptr;
value = static_cast<jstring>(env->CallObjectMethod(prefs, getString, jkey, jdefault));
env->DeleteLocalRef(jkey);
}
if (HasException(env, "SharedPreferences.getString")) {
goto cleanup;
}
std::string out = JStringToString(env, value);
out = JStringToString(env, value);
cleanup:
if (value) env->DeleteLocalRef(value);
if (prefsClass) env->DeleteLocalRef(prefsClass);
if (prefs) env->DeleteLocalRef(prefs);
if (activityClass) env->DeleteLocalRef(activityClass);
return out;
}
static bool CopyUriToPath(JNIEnv* env, jobject activity, const std::string& uriString, const std::string& path) {
if (uriString.empty() || path.empty()) return false;
if (!env || !activity || uriString.empty() || path.empty()) return false;
jclass activityClass = env->GetObjectClass(activity);
jmethodID getContentResolver = env->GetMethodID(activityClass, "getContentResolver",
"()Landroid/content/ContentResolver;");
if (!getContentResolver) return false;
jobject resolver = env->CallObjectMethod(activity, getContentResolver);
if (HasException(env, "getContentResolver") || !resolver) return false;
bool success = false;
jclass activityClass = nullptr;
jobject resolver = nullptr;
jclass uriClass = nullptr;
jobject uri = nullptr;
jclass resolverClass = nullptr;
jobject inputStream = nullptr;
jclass fosClass = nullptr;
jobject outputStream = nullptr;
jclass inputClass = nullptr;
jclass outputClass = nullptr;
jbyteArray buffer = nullptr;
jmethodID readMethod = nullptr;
jmethodID writeMethod = nullptr;
jmethodID closeInput = nullptr;
jmethodID closeOutput = nullptr;
jclass uriClass = env->FindClass("android/net/Uri");
jmethodID parse = env->GetStaticMethodID(uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
jstring juri = env->NewStringUTF(uriString.c_str());
jobject uri = env->CallStaticObjectMethod(uriClass, parse, juri);
env->DeleteLocalRef(juri);
if (HasException(env, "Uri.parse") || !uri) return false;
jclass resolverClass = env->GetObjectClass(resolver);
jmethodID openInputStream = env->GetMethodID(
resolverClass, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;");
jobject inputStream = env->CallObjectMethod(resolver, openInputStream, uri);
if (HasException(env, "openInputStream") || !inputStream) return false;
jclass fosClass = env->FindClass("java/io/FileOutputStream");
jmethodID fosCtor = env->GetMethodID(fosClass, "<init>", "(Ljava/lang/String;)V");
jstring jpath = env->NewStringUTF(path.c_str());
jobject outputStream = env->NewObject(fosClass, fosCtor, jpath);
env->DeleteLocalRef(jpath);
if (HasException(env, "FileOutputStream.<init>") || !outputStream) return false;
jclass inputClass = env->GetObjectClass(inputStream);
jclass outputClass = env->GetObjectClass(outputStream);
jmethodID readMethod = env->GetMethodID(inputClass, "read", "([B)I");
jmethodID closeInput = env->GetMethodID(inputClass, "close", "()V");
jmethodID writeMethod = env->GetMethodID(outputClass, "write", "([BII)V");
jmethodID closeOutput = env->GetMethodID(outputClass, "close", "()V");
if (!readMethod || !writeMethod) return false;
const int kBufferSize = 64 * 1024;
jbyteArray buffer = env->NewByteArray(kBufferSize);
while (true) {
jint read = env->CallIntMethod(inputStream, readMethod, buffer);
if (HasException(env, "InputStream.read")) break;
if (read <= 0) break;
env->CallVoidMethod(outputStream, writeMethod, buffer, 0, read);
if (HasException(env, "OutputStream.write")) break;
activityClass = env->GetObjectClass(activity);
if (!activityClass) {
goto cleanup;
}
env->DeleteLocalRef(buffer);
env->CallVoidMethod(inputStream, closeInput);
env->CallVoidMethod(outputStream, closeOutput);
HasException(env, "close streams");
return true;
{
jmethodID getContentResolver = env->GetMethodID(activityClass, "getContentResolver",
"()Landroid/content/ContentResolver;");
if (!getContentResolver) {
goto cleanup;
}
resolver = env->CallObjectMethod(activity, getContentResolver);
if (HasException(env, "getContentResolver") || !resolver) {
goto cleanup;
}
}
uriClass = env->FindClass("android/net/Uri");
if (!uriClass) {
goto cleanup;
}
{
jmethodID parse = env->GetStaticMethodID(uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
if (!parse) {
goto cleanup;
}
jstring juri = env->NewStringUTF(uriString.c_str());
if (!juri) {
goto cleanup;
}
uri = env->CallStaticObjectMethod(uriClass, parse, juri);
env->DeleteLocalRef(juri);
if (HasException(env, "Uri.parse") || !uri) {
goto cleanup;
}
}
resolverClass = env->GetObjectClass(resolver);
if (!resolverClass) {
goto cleanup;
}
{
jmethodID openInputStream = env->GetMethodID(
resolverClass, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;");
if (!openInputStream) {
goto cleanup;
}
inputStream = env->CallObjectMethod(resolver, openInputStream, uri);
if (HasException(env, "openInputStream") || !inputStream) {
goto cleanup;
}
}
fosClass = env->FindClass("java/io/FileOutputStream");
if (!fosClass) {
goto cleanup;
}
{
jmethodID fosCtor = env->GetMethodID(fosClass, "<init>", "(Ljava/lang/String;)V");
if (!fosCtor) {
goto cleanup;
}
jstring jpath = env->NewStringUTF(path.c_str());
if (!jpath) {
goto cleanup;
}
outputStream = env->NewObject(fosClass, fosCtor, jpath);
env->DeleteLocalRef(jpath);
if (HasException(env, "FileOutputStream.<init>") || !outputStream) {
goto cleanup;
}
}
inputClass = env->GetObjectClass(inputStream);
outputClass = env->GetObjectClass(outputStream);
if (!inputClass || !outputClass) {
goto cleanup;
}
readMethod = env->GetMethodID(inputClass, "read", "([B)I");
closeInput = env->GetMethodID(inputClass, "close", "()V");
writeMethod = env->GetMethodID(outputClass, "write", "([BII)V");
closeOutput = env->GetMethodID(outputClass, "close", "()V");
if (!readMethod || !writeMethod || !closeInput || !closeOutput) {
goto cleanup;
}
{
const int kBufferSize = 64 * 1024;
buffer = env->NewByteArray(kBufferSize);
if (!buffer) {
goto cleanup;
}
while (true) {
jint read = env->CallIntMethod(inputStream, readMethod, buffer);
if (HasException(env, "InputStream.read")) {
goto cleanup;
}
if (read < 0) {
break;
}
if (read == 0) {
continue;
}
env->CallVoidMethod(outputStream, writeMethod, buffer, 0, read);
if (HasException(env, "OutputStream.write")) {
goto cleanup;
}
}
success = true;
}
cleanup:
if (buffer) env->DeleteLocalRef(buffer);
if (inputStream && closeInput) {
env->CallVoidMethod(inputStream, closeInput);
if (HasException(env, "InputStream.close")) {
success = false;
}
}
if (outputStream && closeOutput) {
env->CallVoidMethod(outputStream, closeOutput);
if (HasException(env, "OutputStream.close")) {
success = false;
}
}
if (outputClass) env->DeleteLocalRef(outputClass);
if (inputClass) env->DeleteLocalRef(inputClass);
if (outputStream) env->DeleteLocalRef(outputStream);
if (fosClass) env->DeleteLocalRef(fosClass);
if (inputStream) env->DeleteLocalRef(inputStream);
if (resolverClass) env->DeleteLocalRef(resolverClass);
if (uri) env->DeleteLocalRef(uri);
if (uriClass) env->DeleteLocalRef(uriClass);
if (resolver) env->DeleteLocalRef(resolver);
if (activityClass) env->DeleteLocalRef(activityClass);
return success;
}
struct SetupFiles {
@@ -388,6 +568,9 @@ static bool WriteConfigToml(const std::string& config_path,
if (!android->contains("tcg_tb_size")) {
android->insert_or_assign("tcg_tb_size", 128);
}
if (!android->contains("audio_driver")) {
android->insert_or_assign("audio_driver", "auto");
}
files->insert_or_assign("bootrom_path", mcpx);
files->insert_or_assign("flashrom_path", flash);
@@ -549,9 +732,10 @@ extern "C" int SDL_main(int argc, char* argv[]) {
(void)argv;
LogInfo("SDL_main: start");
// Prefer AAudio on Android, but keep Android AudioTrack as fallback.
SDL_SetHintWithPriority(SDL_HINT_AUDIODRIVER, "aaudio,android",
std::string audio_driver_hint = ResolveAndroidAudioDriverHint();
SDL_SetHintWithPriority(SDL_HINT_AUDIODRIVER, audio_driver_hint.c_str(),
SDL_HINT_OVERRIDE);
LogInfoFmt("SDL_HINT_AUDIODRIVER=%s", audio_driver_hint.c_str());
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
SDL_DisableScreenSaver();
@@ -9,6 +9,7 @@
#include <toml++/toml.h>
#include <android/log.h>
#include <cctype>
#include <sstream>
#include <string>
@@ -142,6 +143,14 @@ static bool parse_filtering(const std::string &value, CONFIG_DISPLAY_FILTERING *
return false;
}
static std::string to_lower_ascii(std::string value)
{
for (char &c : value) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
return value;
}
const char *xemu_settings_get_error_message(void)
{
return error_msg.empty() ? NULL : error_msg.c_str();
@@ -358,6 +367,17 @@ bool xemu_settings_load(void)
snprintf(fifo_str, sizeof(fifo_str), "%d", fifo_frames);
setenv("XEMU_ANDROID_AUDIO_FIFO_FRAMES", fifo_str, 1);
}
if (auto audio_driver = android_cfg["audio_driver"].value<std::string>()) {
std::string driver = *audio_driver;
std::string normalized = to_lower_ascii(driver);
if (normalized == "audiotrack") {
setenv("XEMU_ANDROID_AUDIO_DRIVER", "android", 1);
} else if (normalized == "opensl") {
setenv("XEMU_ANDROID_AUDIO_DRIVER", "opensles", 1);
} else if (!driver.empty()) {
setenv("XEMU_ANDROID_AUDIO_DRIVER", driver.c_str(), 1);
}
}
// System file paths
if (auto bootrom = sys_files["bootrom_path"].value<std::string>()) {
@@ -35,6 +35,10 @@ import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
class GameLibraryActivity : AppCompatActivity() {
companion object {
const val EXTRA_RESTART_LAST_GAME = "com.izzy2lost.x1box.extra.RESTART_LAST_GAME"
}
private data class GameEntry(
val title: String,
val uri: Uri,
@@ -95,6 +99,10 @@ class GameLibraryActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game_library)
if (tryRestartLastGameFromIntent()) {
return
}
folderText = findViewById(R.id.library_folder_text)
loadingSpinner = findViewById(R.id.library_loading)
loadingText = findViewById(R.id.library_loading_text)
@@ -160,6 +168,49 @@ class GameLibraryActivity : AppCompatActivity() {
loadGames()
}
private fun tryRestartLastGameFromIntent(): Boolean {
if (!intent.getBooleanExtra(EXTRA_RESTART_LAST_GAME, false)) {
return false
}
val internalDvdIso = resolveInternalDvdIsoFile()
if (internalDvdIso == null || !internalDvdIso.isFile) {
Toast.makeText(this, getString(R.string.library_restart_failed), Toast.LENGTH_SHORT).show()
return false
}
prefs.edit()
.putString("dvdPath", internalDvdIso.absolutePath)
.remove("dvdUri")
.putBoolean("skip_game_picker", false)
.apply()
launchMainActivityForRestart()
return true
}
private fun resolveInternalDvdIsoFile(): File? {
val external = getExternalFilesDir(null)
if (external != null) {
val externalIso = File(external, "x1box/dvd.iso")
if (externalIso.isFile) {
return externalIso
}
}
val internalIso = File(filesDir, "x1box/dvd.iso")
if (internalIso.isFile) {
return internalIso
}
return null
}
private fun launchMainActivityForRestart() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
private fun loadGames() {
val folderUri = gamesFolderUri
if (!isFolderReady(folderUri)) {
@@ -1,14 +1,18 @@
package com.izzy2lost.x1box
import android.content.Context
import android.content.Intent
import android.hardware.input.InputManager
import android.os.Build
import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.widget.FrameLayout
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.libsdl.app.SDLActivity
class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
@@ -17,6 +21,10 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
private var isControllerVisible = false
private var inputManager: InputManager? = null
private var hasPhysicalController = false
private var inGameMenuDialog: AlertDialog? = null
private var startButtonDown = false
private var selectButtonDown = false
private var comboTriggered = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -32,6 +40,34 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
}
}
override fun onBackPressed() {
val currentDialog = inGameMenuDialog
if (currentDialog?.isShowing == true) {
currentDialog.dismiss()
return
}
showInGameMenu()
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == KeyEvent.KEYCODE_BACK && !isGamepadKeyEvent(event)) {
if (event.action == KeyEvent.ACTION_UP && event.repeatCount == 0) {
val currentDialog = inGameMenuDialog
if (currentDialog?.isShowing == true) {
currentDialog.dismiss()
} else {
showInGameMenu()
}
}
return true
}
if (handleGamepadMenuCombo(event)) {
return true
}
return super.dispatchKeyEvent(event)
}
private fun hideSystemUI() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11 (API 30) and above
@@ -164,6 +200,9 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
}
override fun onDestroy() {
inGameMenuDialog?.dismiss()
inGameMenuDialog = null
// Unregister virtual controller
try {
org.libsdl.app.SDLControllerManager.nativeRemoveJoystick(-2)
@@ -195,6 +234,98 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
checkForPhysicalControllers()
}
private fun handleGamepadMenuCombo(event: KeyEvent): Boolean {
if (!isGamepadKeyEvent(event)) {
return false
}
val isStartKey = event.keyCode == KeyEvent.KEYCODE_BUTTON_START
val isSelectKey = event.keyCode == KeyEvent.KEYCODE_BUTTON_SELECT ||
event.keyCode == KeyEvent.KEYCODE_BACK
if (!isStartKey && !isSelectKey) {
return false
}
when (event.action) {
KeyEvent.ACTION_DOWN -> {
if (isStartKey) {
startButtonDown = true
}
if (isSelectKey) {
selectButtonDown = true
}
if (!comboTriggered && event.repeatCount == 0 &&
startButtonDown && selectButtonDown) {
comboTriggered = true
showInGameMenu()
return true
}
}
KeyEvent.ACTION_UP -> {
if (isStartKey) {
startButtonDown = false
}
if (isSelectKey) {
selectButtonDown = false
}
if (!startButtonDown || !selectButtonDown) {
comboTriggered = false
}
}
}
return comboTriggered
}
private fun isGamepadKeyEvent(event: KeyEvent): Boolean {
val source = event.source
return ((source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
((source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)
}
private fun showInGameMenu() {
val options = arrayOf(
getString(R.string.in_game_menu_resume),
if (isControllerVisible) {
getString(R.string.in_game_menu_hide_touch_controls)
} else {
getString(R.string.in_game_menu_show_touch_controls)
},
getString(R.string.in_game_menu_exit_to_library),
getString(R.string.in_game_menu_quit_app),
)
val dialog = MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog)
.setTitle(getString(R.string.in_game_menu_title))
.setItems(options) { _, which ->
when (which) {
0 -> {
// Resume
}
1 -> toggleOnScreenController()
2 -> exitToGameLibrary()
3 -> finishAffinity()
}
}
.setOnDismissListener {
inGameMenuDialog = null
hideSystemUI()
}
.create()
inGameMenuDialog = dialog
dialog.show()
}
private fun exitToGameLibrary() {
val intent = Intent(this, GameLibraryActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
startActivity(intent)
finish()
}
override fun getLibraries(): Array<String> = arrayOf(
"SDL2",
"xemu",
@@ -54,6 +54,7 @@
<string name="library_convert_copy_input_failed">Failed to copy ISO into converter workspace.</string>
<string name="library_convert_copy_output_failed">Failed to copy converted XISO back to games folder.</string>
<string name="library_convert_resolve_failed">Could not resolve destination folder for selected game.</string>
<string name="library_restart_failed">Unable to restart. The selected game is no longer accessible.</string>
<string name="library_game_size">Size: %1$s</string>
<string name="library_game_path">Path: %1$s</string>
<string name="library_open_game">Open</string>
@@ -71,4 +72,12 @@
<string name="library_about_url_fork" translatable="false">https://github.com/xemu-project/xemu</string>
<string name="library_about_url_license" translatable="false">https://github.com/izzy2lost/xemu/blob/master/LICENSE</string>
<string name="library_about_url_disclaimer" translatable="false">https://github.com/izzy2lost/xemu/blob/master/COPYING</string>
<string name="in_game_menu_title">Game Menu</string>
<string name="in_game_menu_resume">Resume</string>
<string name="in_game_menu_restart_game">Restart Game</string>
<string name="in_game_menu_show_touch_controls">Show Touch Controls</string>
<string name="in_game_menu_hide_touch_controls">Hide Touch Controls</string>
<string name="in_game_menu_exit_to_library">Exit to Game Library</string>
<string name="in_game_menu_quit_app">Quit App</string>
</resources>
@@ -17,4 +17,13 @@
<item name="colorOutline">@color/xemu_outline</item>
<item name="colorOutlineVariant">@color/xemu_outline_variant</item>
</style>
<style name="ThemeOverlay.Xemu.RoundedDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Xemu.RoundedDialog</item>
</style>
<style name="ShapeAppearanceOverlay.Xemu.RoundedDialog" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">24dp</item>
</style>
</resources>
+24 -16
View File
@@ -135,12 +135,13 @@ static void throttle(MCPXAPUState *d)
}
if (queued_bytes > d->monitor.queued_bytes_low) {
int64_t now_us = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
if (d->next_frame_time_us == 0 ||
start_us - d->next_frame_time_us > EP_FRAME_US) {
d->next_frame_time_us = start_us;
now_us - d->next_frame_time_us > EP_FRAME_US) {
d->next_frame_time_us = now_us;
}
while (!qatomic_read(&d->exiting)) {
int64_t now_us = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
now_us = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
int64_t remaining_ms = (d->next_frame_time_us - now_us) / 1000;
if (remaining_ms > 0) {
int sleep_ms = remaining_ms > INT_MAX ? INT_MAX : (int)remaining_ms;
@@ -211,12 +212,14 @@ static void se_frame(MCPXAPUState *d)
}
}
qemu_spin_lock(&d->monitor.fifo_lock);
int num_bytes_free = (int)fifo8_num_free(&d->monitor.fifo);
assert(num_bytes_free >= sizeof(d->monitor.frame_buf));
fifo8_push_all(&d->monitor.fifo, (uint8_t *)d->monitor.frame_buf,
sizeof(d->monitor.frame_buf));
qemu_spin_unlock(&d->monitor.fifo_lock);
if (d->monitor.fifo_capacity_bytes > 0) {
qemu_spin_lock(&d->monitor.fifo_lock);
int num_bytes_free = (int)fifo8_num_free(&d->monitor.fifo);
assert(num_bytes_free >= sizeof(d->monitor.frame_buf));
fifo8_push_all(&d->monitor.fifo, (uint8_t *)d->monitor.frame_buf,
sizeof(d->monitor.frame_buf));
qemu_spin_unlock(&d->monitor.fifo_lock);
}
memset(d->monitor.frame_buf, 0, sizeof(d->monitor.frame_buf));
}
@@ -310,6 +313,10 @@ static void monitor_sink_cb(void *opaque, uint8_t *stream, int free_b)
static void monitor_init(MCPXAPUState *d)
{
qemu_spin_init(&d->monitor.fifo_lock);
d->monitor.fifo_capacity_bytes = 0;
d->monitor.device_buffer_bytes = 0;
d->monitor.queued_bytes_low = 0;
d->monitor.queued_bytes_high = 0;
int fifo_frames = 3;
int audio_samples = 512;
@@ -323,7 +330,6 @@ static void monitor_init(MCPXAPUState *d)
#endif
int fifo_capacity_bytes = fifo_frames * sizeof(d->monitor.frame_buf);
fifo8_create(&d->monitor.fifo, fifo_capacity_bytes);
d->monitor.fifo_capacity_bytes = fifo_capacity_bytes;
struct SDL_AudioSpec sdl_audio_spec = {
.freq = 48000,
@@ -335,8 +341,9 @@ static void monitor_init(MCPXAPUState *d)
};
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
fprintf(stderr, "Failed to initialize SDL audio subsystem: %s\n", SDL_GetError());
exit(1);
fprintf(stderr, "WARNING: Failed to initialize SDL audio subsystem: %s\n",
SDL_GetError());
return;
}
SDL_AudioDeviceID sdl_audio_dev;
@@ -344,9 +351,9 @@ static void monitor_init(MCPXAPUState *d)
sdl_audio_dev = SDL_OpenAudioDevice(NULL, 0, &sdl_audio_spec,
&obtained_audio_spec, 0);
if (sdl_audio_dev == 0) {
fprintf(stderr, "SDL_OpenAudioDevice failed: %s\n", SDL_GetError());
assert(!"SDL_OpenAudioDevice failed");
exit(1);
fprintf(stderr, "WARNING: SDL_OpenAudioDevice failed: %s\n",
SDL_GetError());
return;
}
int bytes_per_sample = SDL_AUDIO_BITSIZE(obtained_audio_spec.format) / 8;
@@ -366,7 +373,8 @@ static void monitor_init(MCPXAPUState *d)
int frame_bytes = sizeof(d->monitor.frame_buf);
int drain_bytes = MAX(device_buffer_bytes, frame_bytes);
int max_high = MAX(d->monitor.fifo_capacity_bytes - frame_bytes, frame_bytes);
int max_high = MAX(fifo_capacity_bytes - frame_bytes, frame_bytes);
d->monitor.fifo_capacity_bytes = fifo_capacity_bytes;
d->monitor.device_buffer_bytes = device_buffer_bytes;
d->monitor.queued_bytes_high = MIN(3 * drain_bytes, max_high);
d->monitor.queued_bytes_low = MIN(drain_bytes, d->monitor.queued_bytes_high);
+7
View File
@@ -48,6 +48,13 @@ static inline void hrtf_filter_init(HrtfFilter *f)
memset(f, 0, sizeof(*f));
}
static inline void hrtf_filter_clear_history(HrtfFilter *f)
{
f->buf_pos = 0;
memset(f->ch[0].buf, 0, sizeof(f->ch[0].buf));
memset(f->ch[1].buf, 0, sizeof(f->ch[1].buf));
}
static inline void
hrtf_filter_set_target_params(HrtfFilter *f, float hrir_coeff[2][HRTF_NUM_TAPS],
float itd)
+1
View File
@@ -55,6 +55,7 @@ static void voice_reset_filters(MCPXAPUState *d, uint16_t v)
{
assert(v < MCPX_HW_MAX_VOICES);
memset(&d->vp.filters[v].svf, 0, sizeof(d->vp.filters[v].svf));
hrtf_filter_clear_history(&d->vp.filters[v].hrtf);
if (d->vp.filters[v].resampler) {
src_reset(d->vp.filters[v].resampler);
}
+12 -2
View File
@@ -949,9 +949,19 @@ static void sdl2_display_very_early_init(DisplayOptions *o)
#endif
#ifdef __ANDROID__
SDL_setenv("SDL_AUDIODRIVER", "aaudio", 0);
const char *audio_env = SDL_getenv("SDL_AUDIODRIVER");
const char *audio_hint = SDL_GetHint(SDL_HINT_AUDIODRIVER);
if ((!audio_env || !audio_env[0]) &&
(!audio_hint || !audio_hint[0])) {
SDL_SetHintWithPriority(SDL_HINT_AUDIODRIVER,
"openslES,aaudio,android",
SDL_HINT_DEFAULT);
audio_hint = SDL_GetHint(SDL_HINT_AUDIODRIVER);
}
__android_log_print(ANDROID_LOG_INFO, "xemu-android",
"SDL_AUDIODRIVER=%s", SDL_getenv("SDL_AUDIODRIVER"));
"SDL audio env=%s hint=%s",
(audio_env && audio_env[0]) ? audio_env : "(unset)",
(audio_hint && audio_hint[0]) ? audio_hint : "(unset)");
#endif
if (SDL_Init(SDL_INIT_VIDEO)) {