From 9a0fba522f8c68dd7834f75856963bcc096c4772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 11:38:58 +0800 Subject: [PATCH 01/18] Fix #1698: Add ALSA stub for libjsound.so compatibility on Android libjsound.so in JDK 25+ Android builds now links against libasound.so.2 (ALSA), which is not available on Android. This causes javax.sound operations to fail with UnsatisfiedLinkError. Changes: - Add minimal ALSA stub library (alsa_stub.c) that provides all the symbols libjsound.so needs, built with SONAME libasound.so.2 - Register ALSA stub in CMakeLists.txt so it gets compiled for all Android architectures and bundled in the APK - Update setUpJavaRuntime() in FCLauncher.java to load the ALSA stub before loading JDK libraries, ensuring libjsound.so can resolve its ALSA dependency - Skip libjsound.so in locateLibs() since it will be loaded lazily by the JVM when javax.sound is used, at which point the ALSA stub is already available --- .../com/tungsten/fclauncher/FCLauncher.java | 16 +- FCLauncher/src/main/jni/CMakeLists.txt | 36 +- FCLauncher/src/main/jni/alsa_stub/alsa_stub.c | 842 ++++++++++++++++++ 3 files changed, 877 insertions(+), 17 deletions(-) create mode 100644 FCLauncher/src/main/jni/alsa_stub/alsa_stub.c diff --git a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java index 4b47d10cf8..04229095e1 100644 --- a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java +++ b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java @@ -342,6 +342,15 @@ private static void addCustomEnv(FCLConfig config, HashMap envMa private static void setUpJavaRuntime(FCLConfig config, FCLBridge bridge) throws IOException { printTaskTitle(bridge, "DLOPEN"); + // Load ALSA stub for Java Sound (libjsound.so) compatibility + // libjsound.so in Android JDK 25+ depends on libasound.so.2 which is not available on Android + try { + File alsaStub = new File(config.getContext().getApplicationInfo().nativeLibraryDir, "libalsa_stub.so"); + if (alsaStub.exists()) { + bridge.dlopen(alsaStub.getAbsolutePath()); + } + } catch (Exception ignored) { + } String javaLibDir = config.getJavaPath() + getJavaLibDir(config.getJavaPath()); String jliLibDir = new File(javaLibDir + "/jli/libjli.so").exists() ? javaLibDir + "/jli" : javaLibDir; if (isJDK8(config.getJavaPath())) @@ -368,7 +377,10 @@ public static ArrayList locateLibs(File path) { File[] list = path.listFiles(); if (list != null) { for (File f : list) { - if (f.isFile() && f.getName().endsWith(".so")) { + // Skip libjsound.so - it depends on libasound.so.2 (ALSA) which is not + // available on Android. The ALSA stub (libalsa_stub.so) provides the + // necessary symbols and is loaded separately in setUpJavaRuntime. + if (f.isFile() && f.getName().endsWith(".so") && !f.getName().equals("libjsound.so")) { returnValue.add(f); } else if (f.isDirectory()) { returnValue.addAll(locateLibs(f)); @@ -570,3 +582,5 @@ public static boolean isJDK8(String javaPath) { } } + + diff --git a/FCLauncher/src/main/jni/CMakeLists.txt b/FCLauncher/src/main/jni/CMakeLists.txt index d6591aa9d0..03d5442889 100644 --- a/FCLauncher/src/main/jni/CMakeLists.txt +++ b/FCLauncher/src/main/jni/CMakeLists.txt @@ -2,24 +2,19 @@ cmake_minimum_required(VERSION 3.18.1) project("fcl") -# 设置C标准 set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 11) -# 查找ByteHook库 find_package(bytehook REQUIRED CONFIG) -# 设置全局包含目录 include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) -# 检查架构 if(${ANDROID_ABI} STREQUAL "arm64-v8a") add_definitions(-DADRENO_POSSIBLE) endif() -# 模块:fcl add_library( fcl SHARED @@ -44,7 +39,6 @@ target_link_libraries( android ) -# 模块:driver_helper add_library( driver_helper SHARED @@ -66,7 +60,6 @@ target_link_libraries( android ) -# 仅在arm64-v8a架构下链接EGL和GLESv2 if(${ANDROID_ABI} STREQUAL "arm64-v8a") target_link_libraries( driver_helper @@ -76,7 +69,6 @@ if(${ANDROID_ABI} STREQUAL "arm64-v8a") ) endif() -# 模块:linkerhook add_library( linkerhook SHARED @@ -90,22 +82,18 @@ target_link_options( -z global ) -# 模块:awt_headless - 占位库 add_library( awt_headless SHARED - # 空库,仅作为占位 - ${CMAKE_CURRENT_SOURCE_DIR}/awt_headless/placeholder.c + awt_headless/placeholder.c ) -# 为占位库添加必要的链接选项 target_link_libraries( awt_headless PRIVATE log ) -# 模块:awt_xawt add_library( awt_xawt SHARED @@ -124,7 +112,6 @@ target_link_libraries( awt_headless ) -# 模块:pojavexec_awt add_library( pojavexec_awt SHARED @@ -137,7 +124,6 @@ target_link_libraries( fcl ) -# 模块:pojavexec add_library( pojavexec SHARED @@ -175,7 +161,6 @@ target_link_libraries( android ) -# 仅在arm64-v8a架构下链接EGL和GLESv2 if(${ANDROID_ABI} STREQUAL "arm64-v8a") target_link_libraries( pojavexec @@ -184,3 +169,22 @@ if(${ANDROID_ABI} STREQUAL "arm64-v8a") GLESv2 ) endif() + +add_library( + alsa_stub + SHARED + alsa_stub/alsa_stub.c +) + +target_link_options( + alsa_stub + PRIVATE + -Wl,-soname,libasound.so.2 +) + +target_link_libraries( + alsa_stub + PRIVATE + log + dl +) \ No newline at end of file diff --git a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c new file mode 100644 index 0000000000..dc62989e42 --- /dev/null +++ b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c @@ -0,0 +1,842 @@ +#include +#include +#include +#include + +#define FALSE 0 +#define TRUE 1 + +typedef unsigned char u_char; +typedef unsigned int UINT32; +typedef int snd_pcm_stream_t; +typedef int snd_pcm_access_t; +typedef int snd_pcm_format_t; +typedef int snd_pcm_state_t; +typedef int snd_mixer_selem_channel_id_t; +typedef long snd_pcm_sframes_t; +typedef unsigned int snd_pcm_uframes_t; + +typedef struct _snd_pcm snd_pcm_t; +typedef struct _snd_ctl snd_ctl_t; +typedef struct _snd_mixer snd_mixer_t; +typedef struct _snd_mixer_elem snd_mixer_elem_t; +typedef struct _snd_mixer_selem_id snd_mixer_selem_id_t; +typedef struct _snd_hctl snd_hctl_t; +typedef struct _snd_seq snd_seq_t; +typedef struct _snd_rawmidi snd_rawmidi_t; + +typedef struct { + unsigned int device; + unsigned int subdevice; + int stream; + int card; + unsigned int subdevices_count; + unsigned int subdevices_avail; + char id[64]; + char name[256]; + char subname[256]; +} snd_pcm_info_t; + +typedef struct { + char id[64]; + char name[256]; + char driver[64]; +} snd_ctl_card_info_t; + +typedef struct { + unsigned int flags; +} snd_pcm_hw_params_t; + +typedef struct { + unsigned int flags; +} snd_pcm_sw_params_t; + +static const char* alsa_error_str(int err) { + switch (err) { + case 0: return "Success"; + case -1: return "Operation not permitted"; + case -2: return "No such file or directory"; + case -5: return "Input/output error"; + case -16: return "Device or resource busy"; + case -19: return "No such device"; + case -22: return "Invalid argument"; + case -27: return "File too large"; + case -77: return "Function not implemented"; + case -84: return "Invalid value"; + case -99: return "Address family not supported"; + case -114: return "Operation already in progress"; + case -115: return "Operation in progress"; + case -118: return "No data available"; + default: return "Unknown ALSA error"; + } +} + +int snd_strerror(int err) { + printf("ALSA stub: snd_strerror(%d) called\n", err); + return -1; +} + +const char* snd_strerror_pointer(int err) { + return alsa_error_str(err); +} + +typedef void (*snd_lib_error_handler_t)(const char *file, int line, const char *function, int err, const char *fmt, ...); + +static void default_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { + printf("ALSA lib %s:%d:(%s) error %d: %s\n", file, line, function, err, snd_strerror_pointer(err)); +} + +snd_lib_error_handler_t snd_lib_error_set_handler(snd_lib_error_handler_t handler) { + printf("ALSA stub: snd_lib_error_set_handler(%p)\n", (void*)handler); + return NULL; +} + +int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) { + printf("ALSA stub: snd_pcm_open(%s, stream=%d, mode=%d)\n", name, stream, mode); + *pcm = NULL; + return -19; +} + +int snd_pcm_close(snd_pcm_t *pcm) { + printf("ALSA stub: snd_pcm_close(%p)\n", (void*)pcm); + return 0; +} + +int snd_pcm_info(snd_pcm_t *pcm, snd_pcm_info_t *info) { + printf("ALSA stub: snd_pcm_info(%p)\n", (void*)pcm); + return -19; +} + +int snd_pcm_info_malloc(snd_pcm_info_t **ptr) { + printf("ALSA stub: snd_pcm_info_malloc\n"); + *ptr = (snd_pcm_info_t*)calloc(1, sizeof(snd_pcm_info_t)); + return *ptr ? 0 : -12; +} + +void snd_pcm_info_free(snd_pcm_info_t *obj) { + printf("ALSA stub: snd_pcm_info_free\n"); + free(obj); +} + +void snd_pcm_info_set_device(snd_pcm_info_t *obj, unsigned int val) { + obj->device = val; +} + +void snd_pcm_info_set_subdevice(snd_pcm_info_t *obj, unsigned int val) { + obj->subdevice = val; +} + +void snd_pcm_info_set_stream(snd_pcm_info_t *obj, int val) { + obj->stream = val; +} + +int snd_pcm_info_get_card(const snd_pcm_info_t *obj) { + return obj->card; +} + +unsigned int snd_pcm_info_get_subdevices_count(const snd_pcm_info_t *obj) { + return 0; +} + +unsigned int snd_pcm_info_get_subdevices_avail(const snd_pcm_info_t *obj) { + return 0; +} + +const char* snd_pcm_info_get_id(const snd_pcm_info_t *obj) { + return "default"; +} + +const char* snd_pcm_info_get_name(const snd_pcm_info_t *obj) { + return "ALSA stub default device"; +} + +const char* snd_pcm_info_get_subdevice_name(const snd_pcm_info_t *obj) { + return ""; +} + +int snd_pcm_info_get_stream(const snd_pcm_info_t *obj) { + return obj->stream; +} + +int snd_ctl_open(snd_ctl_t **handle, const char *name, int mode) { + printf("ALSA stub: snd_ctl_open(%s)\n", name); + *handle = NULL; + return -19; +} + +int snd_ctl_close(snd_ctl_t *handle) { + printf("ALSA stub: snd_ctl_close(%p)\n", (void*)handle); + return 0; +} + +int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr) { + printf("ALSA stub: snd_ctl_card_info_malloc\n"); + *ptr = (snd_ctl_card_info_t*)calloc(1, sizeof(snd_ctl_card_info_t)); + return *ptr ? 0 : -12; +} + +void snd_ctl_card_info_free(snd_ctl_card_info_t *obj) { + printf("ALSA stub: snd_ctl_card_info_free\n"); + free(obj); +} + +int snd_ctl_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info) { + printf("ALSA stub: snd_ctl_card_info(%p)\n", (void*)handle); + return -19; +} + +const char* snd_ctl_card_info_get_id(const snd_ctl_card_info_t *obj) { + return "stub"; +} + +const char* snd_ctl_card_info_get_name(const snd_ctl_card_info_t *obj) { + return "ALSA Stub Card"; +} + +int snd_ctl_pcm_info(snd_ctl_t *handle, snd_pcm_info_t *info) { + printf("ALSA stub: snd_ctl_pcm_info(%p)\n", (void*)handle); + return -2; +} + +int snd_ctl_pcm_next_device(snd_ctl_t *handle, int *device) { + printf("ALSA stub: snd_ctl_pcm_next_device\n"); + *device = -1; + return 0; +} + +int snd_ctl_rawmidi_next_device(snd_ctl_t *handle, int *device) { + *device = -1; + return 0; +} + +int snd_card_next(int *card) { + printf("ALSA stub: snd_card_next\n"); + *card = -1; + return 0; +} + +int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t **ptr) { + printf("ALSA stub: snd_pcm_hw_params_malloc\n"); + *ptr = (snd_pcm_hw_params_t*)calloc(1, sizeof(snd_pcm_hw_params_t)); + return *ptr ? 0 : -12; +} + +void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj) { + printf("ALSA stub: snd_pcm_hw_params_free\n"); + free(obj); +} + +int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { + printf("ALSA stub: snd_pcm_hw_params_any\n"); + return -19; +} + +int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access) { + return -19; +} + +int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val) { + return -19; +} + +int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { + return -19; +} + +int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params_set_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { + return -19; +} + +int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { + return -19; +} + +int snd_pcm_hw_params_set_buffer_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params_set_period_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { + return -19; +} + +int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { + return -19; +} + +int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *access) { + return -19; +} + +int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params, snd_pcm_format_t *format) { + return -19; +} + +int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *val) { + return -19; +} + +int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { + return -19; +} + +int snd_pcm_hw_params_get_sbits(const snd_pcm_hw_params_t *params) { + return -19; +} + +int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { + return -19; +} + +int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params) { + return 0; +} + +int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr) { + printf("ALSA stub: snd_pcm_sw_params_malloc\n"); + *ptr = (snd_pcm_sw_params_t*)calloc(1, sizeof(snd_pcm_sw_params_t)); + return *ptr ? 0 : -12; +} + +void snd_pcm_sw_params_free(snd_pcm_sw_params_t *obj) { + printf("ALSA stub: snd_pcm_sw_params_free\n"); + free(obj); +} + +int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { + return -19; +} + +int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { + return -19; +} + +int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { + return -19; +} + +int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { + return -19; +} + +int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { + return -19; +} + +int snd_pcm_sw_params_set_silence_size(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { + return -19; +} + +int snd_pcm_sw_params_get_silence_threshold(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val) { + return -19; +} + +int snd_pcm_sw_params_get_silence_size(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val) { + return -19; +} + +int snd_pcm_sw_params_get_boundary(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val) { + return -19; +} + +int snd_pcm_sw_params_set_period_event(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, int val) { + return -19; +} + +int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { + return -19; +} + +int snd_pcm_prepare(snd_pcm_t *pcm) { + return -19; +} + +snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) { + return -19; +} + +snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) { + return -19; +} + +int snd_pcm_drain(snd_pcm_t *pcm) { + return -19; +} + +int snd_pcm_drop(snd_pcm_t *pcm) { + return -19; +} + +int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent) { + return -19; +} + +snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm) { + return 0; +} + +snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm) { + return -19; +} + +snd_pcm_sframes_t snd_pcm_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) { + return -19; +} + +int snd_pcm_resume(snd_pcm_t *pcm) { + return -19; +} + +int snd_pcm_start(snd_pcm_t *pcm) { + return -19; +} + +int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) { + return -19; +} + +snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm) { + return -19; +} + +int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay) { + return -19; +} + +int snd_pcm_wait(snd_pcm_t *pcm, int timeout) { + return -19; +} + +snd_pcm_sframes_t snd_pcm_mmap_begin(snd_pcm_t *pcm, const void **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames) { + return -19; +} + +snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames) { + return -19; +} + +int snd_pcm_format_physical_width(snd_pcm_format_t format) { + return 16; +} + +int snd_pcm_format_width(snd_pcm_format_t format) { + return 16; +} + +int snd_pcm_format_signed(snd_pcm_format_t format) { + return 1; +} + +int snd_pcm_format_big_endian(snd_pcm_format_t format) { + return 0; +} + +int snd_pcm_format_linear(snd_pcm_format_t format) { + return 1; +} + +size_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) { + return samples * 2; +} + +snd_pcm_format_t snd_pcm_build_linear_format(int width, int pwidth, int unsignd, int big_endian) { + return 0; +} + +int snd_pcm_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2) { + return -19; +} + +int snd_pcm_unlink(snd_pcm_t *pcm) { + return -19; +} + +int snd_mixer_open(snd_mixer_t **mixer, int mode) { + printf("ALSA stub: snd_mixer_open\n"); + *mixer = NULL; + return -19; +} + +int snd_mixer_close(snd_mixer_t *mixer) { + return 0; +} + +int snd_mixer_attach(snd_mixer_t *mixer, const char *name) { + return -19; +} + +int snd_mixer_detach(snd_mixer_t *mixer, const char *name) { + return 0; +} + +int snd_mixer_selem_register(snd_mixer_t *mixer, void *options, void *classp) { + return -19; +} + +int snd_mixer_load(snd_mixer_t *mixer) { + return -19; +} + +void snd_mixer_set_callback(snd_mixer_t *obj, void *val) {} + +void snd_mixer_elem_set_callback(snd_mixer_elem_t *obj, void *val) {} + +snd_mixer_elem_t* snd_mixer_first_elem(snd_mixer_t *mixer) { + return NULL; +} + +snd_mixer_elem_t* snd_mixer_last_elem(snd_mixer_t *mixer) { + return NULL; +} + +snd_mixer_elem_t* snd_mixer_elem_next(snd_mixer_elem_t *elem) { + return NULL; +} + +snd_mixer_elem_t* snd_mixer_elem_prev(snd_mixer_elem_t *elem) { + return NULL; +} + +int snd_mixer_handle_events(snd_mixer_t *mixer) { + return -19; +} + +int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr) { + printf("ALSA stub: snd_mixer_selem_id_malloc\n"); + *ptr = (snd_mixer_selem_id_t*)calloc(1, sizeof(snd_mixer_selem_id_t)); + return *ptr ? 0 : -12; +} + +void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj) { + free(obj); +} + +void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t *obj, const char *val) {} + +const char* snd_mixer_selem_id_get_name(snd_mixer_selem_id_t *obj) { + return ""; +} + +void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t *obj, unsigned int val) {} + +unsigned int snd_mixer_selem_id_get_index(snd_mixer_selem_id_t *obj) { + return 0; +} + +const char* snd_mixer_selem_get_name(snd_mixer_elem_t *elem) { + return ""; +} + +unsigned int snd_mixer_selem_get_index(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_is_active(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t *elem, int channel, int *value) { + return -19; +} + +int snd_mixer_selem_get_capture_switch(snd_mixer_elem_t *elem, int channel, int *value) { + return -19; +} + +int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { + return -19; +} + +int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { + return -19; +} + +int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *elem, long value) { + return -19; +} + +int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t *elem, long value) { + return -19; +} + +int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *elem, long *min, long *max) { + return -19; +} + +int snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t *elem, long *min, long *max) { + return -19; +} + +int snd_mixer_selem_set_playback_volume_range(snd_mixer_elem_t *elem, long min, long max) { + return -19; +} + +int snd_mixer_selem_set_capture_volume_range(snd_mixer_elem_t *elem, long min, long max) { + return -19; +} + +int snd_mixer_selem_get_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { + return -19; +} + +int snd_mixer_selem_get_capture_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { + return -19; +} + +int snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *elem, long value, int dir) { + return -19; +} + +int snd_mixer_selem_set_capture_dB_all(snd_mixer_elem_t *elem, long value, int dir) { + return -19; +} + +int snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *elem, long *min, long *max) { + return -19; +} + +int snd_mixer_selem_get_capture_dB_range(snd_mixer_elem_t *elem, long *min, long *max) { + return -19; +} + +int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *elem) { + return 1; +} + +int snd_mixer_selem_has_capture_volume_joined(snd_mixer_elem_t *elem) { + return 1; +} + +int snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *elem) { + return 1; +} + +int snd_mixer_selem_has_capture_switch_joined(snd_mixer_elem_t *elem) { + return 1; +} + +int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t *elem, int value) { + return -19; +} + +int snd_mixer_selem_set_capture_switch_all(snd_mixer_elem_t *elem, int value) { + return -19; +} + +int snd_mixer_selem_get_playback_switch_switch(snd_mixer_elem_t *elem, int channel, int *value) { + return -19; +} + +int snd_mixer_selem_has_capture_switch_switch(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_get_capture_switch_switch(snd_mixer_elem_t *elem, int channel, int *value) { + return -19; +} + +int snd_mixer_selem_get_type(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_get_count(snd_mixer_elem_t *elem) { + return 0; +} + +int snd_mixer_selem_channel_id(snd_mixer_selem_channel_id_t channel) { + return (int)channel; +} + +int snd_seq_open(snd_seq_t **seq, const char *name, int streams, int mode) { + printf("ALSA stub: snd_seq_open(%s)\n", name); + *seq = NULL; + return -19; +} + +int snd_seq_close(snd_seq_t *seq) { + return 0; +} + +int snd_seq_set_client_name(snd_seq_t *seq, const char *name) { + return -19; +} + +int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type) { + return -19; +} + +int snd_seq_connect_from(snd_seq_t *seq, int my_port, int src_client, int src_port) { + return -19; +} + +int snd_seq_connect_to(snd_seq_t *seq, int my_port, int dst_client, int dst_port) { + return -19; +} + +int snd_seq_alloc_named_queue(snd_seq_t *seq, const char *name) { + return -19; +} + +int snd_seq_alloc_queue(snd_seq_t *seq) { + return -19; +} + +int snd_seq_free_queue(snd_seq_t *seq, int q) { + return 0; +} + +int snd_seq_set_tempo(snd_seq_t *seq, int q, int tempo) { + return -19; +} + +int snd_seq_drain_output(snd_seq_t *seq) { + return -19; +} + +int snd_seq_sync_output_queue(snd_seq_t *seq) { + return -19; +} + +int snd_seq_event_output(snd_seq_t *seq, void *ev) { + return -19; +} + +int snd_seq_event_input(snd_seq_t *seq, void **ev) { + return -19; +} + +int snd_seq_free_event(void *ev) { + return 0; +} + +int snd_seq_event_input_pending(snd_seq_t *seq, int fetch) { + return 0; +} + +int snd_rawmidi_open(snd_rawmidi_t **in, snd_rawmidi_t **out, const char *name, int mode) { + printf("ALSA stub: snd_rawmidi_open(%s)\n", name); + *in = NULL; + *out = NULL; + return -19; +} + +int snd_rawmidi_close(snd_rawmidi_t *sub) { + return 0; +} + +int snd_rawmidi_read(snd_rawmidi_t *in, void *buf, size_t size) { + return -19; +} + +int snd_rawmidi_write(snd_rawmidi_t *out, const void *buf, size_t size) { + return -19; +} + +int snd_rawmidi_drain(snd_rawmidi_t *out) { + return 0; +} + +int snd_rawmidi_nonblock(snd_rawmidi_t *rm, int nonblock) { + return -19; +} + +int snd_rawmidi_params(snd_rawmidi_t *rm, void *params) { + return -19; +} + +unsigned int snd_rawmidi_count(snd_rawmidi_t *rm) { + return 0; +} + +int snd_rawmidi_params_malloc(void **ptr) { + *ptr = calloc(1, 64); + return *ptr ? 0 : -12; +} + +void snd_rawmidi_params_free(void *obj) { + free(obj); +} + +int snd_rawmidi_params_set_buffer_size(snd_rawmidi_t *rm, void *params, unsigned long val) { + return -19; +} + +int snd_rawmidi_params_set_avail_min(snd_rawmidi_t *rm, void *params, unsigned long val) { + return -19; +} + +int snd_rawmidi_status_malloc(void **ptr) { + *ptr = calloc(1, 64); + return *ptr ? 0 : -12; +} + +void snd_rawmidi_status_free(void *obj) { + free(obj); +} + +int snd_rawmidi_status(snd_rawmidi_t *rm, void *status) { + return -19; +} + +unsigned int snd_rawmidi_status_get_avail(void *status) { + return 0; +} + +int snd_rawmidi_nonblock_old(snd_rawmidi_t *rm, int nonblock) { + return -19; +} + +int snd_output_stdio_attach(void **output, FILE *fp, int close) { + return 0; +} + +int snd_output_close(void *output) { + return 0; +} + +int snd_config_update(void) { + return 0; +} + +int snd_config_update_free(void) { + return 0; +} \ No newline at end of file From 6b37f0db83bca84c9ca99ef495f365e435456925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 13:25:32 +0800 Subject: [PATCH 02/18] Restore original Chinese comments in CMakeLists.txt --- FCLauncher/src/main/jni/CMakeLists.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/FCLauncher/src/main/jni/CMakeLists.txt b/FCLauncher/src/main/jni/CMakeLists.txt index 03d5442889..b496b33bd8 100644 --- a/FCLauncher/src/main/jni/CMakeLists.txt +++ b/FCLauncher/src/main/jni/CMakeLists.txt @@ -2,19 +2,24 @@ cmake_minimum_required(VERSION 3.18.1) project("fcl") +# 设置C标准 set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 11) +# 查找ByteHook库 find_package(bytehook REQUIRED CONFIG) +# 设置全局包含目录 include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) +# 检查架构 if(${ANDROID_ABI} STREQUAL "arm64-v8a") add_definitions(-DADRENO_POSSIBLE) endif() +# 模块:fcl add_library( fcl SHARED @@ -39,6 +44,7 @@ target_link_libraries( android ) +# 模块:driver_helper add_library( driver_helper SHARED @@ -60,6 +66,7 @@ target_link_libraries( android ) +# 仅在arm64-v8a架构下链接EGL和GLESv2 if(${ANDROID_ABI} STREQUAL "arm64-v8a") target_link_libraries( driver_helper @@ -69,6 +76,7 @@ if(${ANDROID_ABI} STREQUAL "arm64-v8a") ) endif() +# 模块:linkerhook add_library( linkerhook SHARED @@ -82,18 +90,22 @@ target_link_options( -z global ) +# 模块:awt_headless - 占位库 add_library( awt_headless SHARED - awt_headless/placeholder.c + # 空库,仅作为占位 + ${CMAKE_CURRENT_SOURCE_DIR}/awt_headless/placeholder.c ) +# 为占位库添加必要的链接选项 target_link_libraries( awt_headless PRIVATE log ) +# 模块:awt_xawt add_library( awt_xawt SHARED @@ -112,6 +124,7 @@ target_link_libraries( awt_headless ) +# 模块:pojavexec_awt add_library( pojavexec_awt SHARED @@ -124,6 +137,7 @@ target_link_libraries( fcl ) +# 模块:pojavexec add_library( pojavexec SHARED @@ -161,6 +175,7 @@ target_link_libraries( android ) +# 仅在arm64-v8a架构下链接EGL和GLESv2 if(${ANDROID_ABI} STREQUAL "arm64-v8a") target_link_libraries( pojavexec From 27b5eeb7909b3720c24676a7cb2d423394c829a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 15:18:27 +0800 Subject: [PATCH 03/18] Fix incomplete type error for snd_mixer_selem_id_t --- FCLauncher/src/main/jni/alsa_stub/alsa_stub.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c index dc62989e42..bdace50710 100644 --- a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c +++ b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c @@ -20,7 +20,10 @@ typedef struct _snd_pcm snd_pcm_t; typedef struct _snd_ctl snd_ctl_t; typedef struct _snd_mixer snd_mixer_t; typedef struct _snd_mixer_elem snd_mixer_elem_t; -typedef struct _snd_mixer_selem_id snd_mixer_selem_id_t; +typedef struct _snd_mixer_selem_id { + char name[64]; + unsigned int index; +} snd_mixer_selem_id_t; typedef struct _snd_hctl snd_hctl_t; typedef struct _snd_seq snd_seq_t; typedef struct _snd_rawmidi snd_rawmidi_t; From dc807fa653db381a961f69cd77a40eb493f570ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 17:16:18 +0800 Subject: [PATCH 04/18] Implement real ALSA PCM playback via OpenSL ES --- FCLauncher/src/main/jni/alsa_stub/alsa_stub.c | 1154 ++++++----------- 1 file changed, 416 insertions(+), 738 deletions(-) diff --git a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c index bdace50710..139f95d8e5 100644 --- a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c +++ b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c @@ -2,12 +2,14 @@ #include #include #include +#include +#include +#include #define FALSE 0 #define TRUE 1 typedef unsigned char u_char; -typedef unsigned int UINT32; typedef int snd_pcm_stream_t; typedef int snd_pcm_access_t; typedef int snd_pcm_format_t; @@ -20,39 +22,77 @@ typedef struct _snd_pcm snd_pcm_t; typedef struct _snd_ctl snd_ctl_t; typedef struct _snd_mixer snd_mixer_t; typedef struct _snd_mixer_elem snd_mixer_elem_t; -typedef struct _snd_mixer_selem_id { - char name[64]; - unsigned int index; -} snd_mixer_selem_id_t; +typedef struct _snd_mixer_selem_id { char name[64]; unsigned int index; } snd_mixer_selem_id_t; typedef struct _snd_hctl snd_hctl_t; typedef struct _snd_seq snd_seq_t; typedef struct _snd_rawmidi snd_rawmidi_t; +#define SND_PCM_STREAM_PLAYBACK 0 +#define SND_PCM_STREAM_CAPTURE 1 +#define SND_PCM_ACCESS_RW_INTERLEAVED 3 +#define SND_PCM_FORMAT_U8 1 +#define SND_PCM_FORMAT_S16_LE 2 +#define SND_PCM_FORMAT_S32_LE 10 +#define SND_PCM_STATE_OPEN 0 +#define SND_PCM_STATE_SETUP 1 +#define SND_PCM_STATE_PREPARED 2 +#define SND_PCM_STATE_RUNNING 3 +#define SND_PCM_STATE_XRUN 4 +#define SND_PCM_STATE_DRAINING 5 +#define SND_PCM_STATE_PAUSED 6 +#define SND_PCM_STATE_DISCONNECTED 8 + typedef struct { - unsigned int device; - unsigned int subdevice; - int stream; - int card; - unsigned int subdevices_count; - unsigned int subdevices_avail; - char id[64]; - char name[256]; - char subname[256]; + unsigned int device; unsigned int subdevice; int stream; int card; + unsigned int subdevices_count; unsigned int subdevices_avail; + char id[64]; char name[256]; char subname[256]; } snd_pcm_info_t; -typedef struct { - char id[64]; - char name[256]; - char driver[64]; -} snd_ctl_card_info_t; +typedef struct { char id[64]; char name[256]; char driver[64]; } snd_ctl_card_info_t; typedef struct { - unsigned int flags; + unsigned int rate; unsigned int channels; unsigned int buffer_frames; + unsigned int period_frames; unsigned int periods; + snd_pcm_format_t format; snd_pcm_access_t access; + int bits; int sbits; int signed_fmt; int big_endian; int linear; } snd_pcm_hw_params_t; +typedef struct { unsigned int flags; } snd_pcm_sw_params_t; + +struct _snd_pcm { + int stream; int state; char name[256]; + snd_pcm_hw_params_t hw; + SLObjectItf engineObj; SLEngineItf engine; + SLObjectItf outputMixObj; SLObjectItf playerObj; + SLPlayItf player; SLAndroidSimpleBufferQueueItf bufferQueue; + short *buffer; int buffer_size; int period_frames; + int frame_size; int write_pos; int play_pos; int buffered_frames; + pthread_mutex_t mutex; int engine_initialized; +}; + +static int g_engine_initialized = 0; +static SLObjectItf g_engineObj = NULL; +static SLEngineItf g_engine = NULL; +static SLObjectItf g_outputMixObj = NULL; +static pthread_mutex_t g_engine_mutex = PTHREAD_MUTEX_INITIALIZER; + typedef struct { - unsigned int flags; -} snd_pcm_sw_params_t; + snd_pcm_format_t alsa_fmt; int bits; int sbits; + int signed_fmt; int big_endian; int linear; SLuint32 sl_format; +} format_entry_t; + +static format_entry_t format_table[] = { + { SND_PCM_FORMAT_U8, 8, 8, 0, 0, 1, SL_DATAFORMAT_PCM_SUB_BITS_8 }, + { SND_PCM_FORMAT_S16_LE, 16, 16, 1, 0, 1, SL_DATAFORMAT_PCM_SUB_BITS_16 }, + { SND_PCM_FORMAT_S32_LE, 32, 32, 1, 0, 1, SL_DATAFORMAT_PCM_SUB_BITS_32 }, + { -1, 0, 0, 0, 0, 0, 0 }, +}; + +static format_entry_t* find_format(snd_pcm_format_t fmt) { + for (int i = 0; format_table[i].alsa_fmt >= 0; i++) + if (format_table[i].alsa_fmt == fmt) return &format_table[i]; + return NULL; +} static const char* alsa_error_str(int err) { switch (err) { @@ -60,786 +100,424 @@ static const char* alsa_error_str(int err) { case -1: return "Operation not permitted"; case -2: return "No such file or directory"; case -5: return "Input/output error"; + case -12: return "Cannot allocate memory"; case -16: return "Device or resource busy"; case -19: return "No such device"; case -22: return "Invalid argument"; - case -27: return "File too large"; case -77: return "Function not implemented"; case -84: return "Invalid value"; - case -99: return "Address family not supported"; - case -114: return "Operation already in progress"; - case -115: return "Operation in progress"; - case -118: return "No data available"; default: return "Unknown ALSA error"; } } -int snd_strerror(int err) { - printf("ALSA stub: snd_strerror(%d) called\n", err); - return -1; -} - -const char* snd_strerror_pointer(int err) { - return alsa_error_str(err); -} - -typedef void (*snd_lib_error_handler_t)(const char *file, int line, const char *function, int err, const char *fmt, ...); - -static void default_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { - printf("ALSA lib %s:%d:(%s) error %d: %s\n", file, line, function, err, snd_strerror_pointer(err)); -} - -snd_lib_error_handler_t snd_lib_error_set_handler(snd_lib_error_handler_t handler) { - printf("ALSA stub: snd_lib_error_set_handler(%p)\n", (void*)handler); - return NULL; -} +static int init_engine(void) { + pthread_mutex_lock(&g_engine_mutex); + if (g_engine_initialized) { pthread_mutex_unlock(&g_engine_mutex); return 0; } + SLresult r; + r = slCreateEngine(&g_engineObj, 0, NULL, 0, NULL, NULL); + if (r != SL_RESULT_SUCCESS) { pthread_mutex_unlock(&g_engine_mutex); return -5; } + r = (*g_engineObj)->Realize(g_engineObj, SL_BOOLEAN_FALSE); + if (r != SL_RESULT_SUCCESS) { (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } + r = (*g_engineObj)->GetInterface(g_engineObj, SL_IID_ENGINE, &g_engine); + if (r != SL_RESULT_SUCCESS) { (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } + const SLInterfaceID ids[] = { SL_IID_VOLUME }; + const SLboolean req[] = { SL_BOOLEAN_FALSE }; + r = (*g_engine)->CreateOutputMix(g_engine, &g_outputMixObj, 1, ids, req); + if (r != SL_RESULT_SUCCESS) { (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; g_engine = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } + r = (*g_outputMixObj)->Realize(g_outputMixObj, SL_BOOLEAN_FALSE); + if (r != SL_RESULT_SUCCESS) { (*g_outputMixObj)->Destroy(g_outputMixObj); g_outputMixObj = NULL; (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; g_engine = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } + g_engine_initialized = 1; + fprintf(stderr, "ALSA stub: OpenSL ES engine initialized\n"); + pthread_mutex_unlock(&g_engine_mutex); + return 0; +} + +static void buffer_queue_callback(SLAndroidSimpleBufferQueueItf bq, void *context) { + (void)bq; (void)context; +} + +static int pcm_setup_player(snd_pcm_t *pcm) { + if (pcm->playerObj) { (*pcm->playerObj)->Destroy(pcm->playerObj); pcm->playerObj = NULL; pcm->player = NULL; pcm->bufferQueue = NULL; } + format_entry_t *fe = find_format(pcm->hw.format); + if (!fe) return -22; + pcm->frame_size = (pcm->hw.channels * fe->bits) / 8; + if (pcm->frame_size == 0) pcm->frame_size = 2; + pcm->buffer_size = pcm->hw.buffer_frames; + pcm->period_frames = pcm->hw.period_frames; + SLDataFormat_PCM fmt; + fmt.formatType = SL_DATAFORMAT_PCM; + fmt.numChannels = pcm->hw.channels; + fmt.samplesPerSec = pcm->hw.rate * 1000; + fmt.bitsPerSample = fe->bits; + fmt.containerSize = fe->bits; + fmt.channelMask = (pcm->hw.channels == 1) ? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + fmt.endianness = fe->big_endian ? SL_BYTEORDER_BIGENDIAN : SL_BYTEORDER_LITTLEENDIAN; + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 }; + SLDataSource audioSrc = { &loc_bufq, &fmt }; + SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, g_outputMixObj }; + SLDataSink audioSink = { &loc_outmix, NULL }; + SLInterfaceID ids[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; + SLboolean req[] = { SL_BOOLEAN_TRUE }; + SLresult r = (*g_engine)->CreateAudioPlayer(g_engine, &pcm->playerObj, &audioSrc, &audioSink, 1, ids, req); + if (r != SL_RESULT_SUCCESS) return -5; + r = (*pcm->playerObj)->Realize(pcm->playerObj, SL_BOOLEAN_FALSE); + if (r != SL_RESULT_SUCCESS) { (*pcm->playerObj)->Destroy(pcm->playerObj); pcm->playerObj = NULL; return -5; } + r = (*pcm->playerObj)->GetInterface(pcm->playerObj, SL_IID_PLAY, &pcm->player); + if (r != SL_RESULT_SUCCESS) return -5; + r = (*pcm->playerObj)->GetInterface(pcm->playerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pcm->bufferQueue); + if (r != SL_RESULT_SUCCESS) return -5; + r = (*pcm->bufferQueue)->RegisterCallback(pcm->bufferQueue, buffer_queue_callback, pcm); + if (r != SL_RESULT_SUCCESS) return -5; + return 0; +} + +int snd_strerror(int err) { return -1; } +const char* snd_strerror_pointer(int err) { return alsa_error_str(err); } +typedef void (*snd_lib_error_handler_t)(const char*,int,const char*,int,const char*,...); +snd_lib_error_handler_t snd_lib_error_set_handler(snd_lib_error_handler_t h) { return NULL; } int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) { - printf("ALSA stub: snd_pcm_open(%s, stream=%d, mode=%d)\n", name, stream, mode); - *pcm = NULL; - return -19; + if (!pcm) return -22; + if (!g_engine_initialized) { int ret = init_engine(); if (ret) { *pcm = NULL; return ret; } } + snd_pcm_t *p = (snd_pcm_t*)calloc(1, sizeof(snd_pcm_t)); + if (!p) return -12; + pthread_mutex_init(&p->mutex, NULL); + p->stream = stream; p->state = SND_PCM_STATE_OPEN; + strncpy(p->name, name, sizeof(p->name) - 1); + p->hw.rate = 44100; p->hw.channels = 2; + p->hw.format = SND_PCM_FORMAT_S16_LE; + p->hw.access = SND_PCM_ACCESS_RW_INTERLEAVED; + p->hw.buffer_frames = 4096; p->hw.period_frames = 1024; + p->hw.periods = 4; p->hw.bits = 16; p->hw.sbits = 16; + p->hw.signed_fmt = 1; p->hw.linear = 1; + p->engine_initialized = 1; + *pcm = p; + fprintf(stderr, "ALSA stub: snd_pcm_open(%s, stream=%d) = %p\n", name, stream, (void*)p); + return 0; } int snd_pcm_close(snd_pcm_t *pcm) { - printf("ALSA stub: snd_pcm_close(%p)\n", (void*)pcm); + if (!pcm) return 0; + if (pcm->playerObj) { + if (pcm->player) (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); + (*pcm->playerObj)->Destroy(pcm->playerObj); + } + if (pcm->buffer) free(pcm->buffer); + pthread_mutex_destroy(&pcm->mutex); + free(pcm); return 0; } int snd_pcm_info(snd_pcm_t *pcm, snd_pcm_info_t *info) { - printf("ALSA stub: snd_pcm_info(%p)\n", (void*)pcm); - return -19; -} - -int snd_pcm_info_malloc(snd_pcm_info_t **ptr) { - printf("ALSA stub: snd_pcm_info_malloc\n"); - *ptr = (snd_pcm_info_t*)calloc(1, sizeof(snd_pcm_info_t)); - return *ptr ? 0 : -12; -} - -void snd_pcm_info_free(snd_pcm_info_t *obj) { - printf("ALSA stub: snd_pcm_info_free\n"); - free(obj); -} - -void snd_pcm_info_set_device(snd_pcm_info_t *obj, unsigned int val) { - obj->device = val; -} - -void snd_pcm_info_set_subdevice(snd_pcm_info_t *obj, unsigned int val) { - obj->subdevice = val; -} - -void snd_pcm_info_set_stream(snd_pcm_info_t *obj, int val) { - obj->stream = val; -} - -int snd_pcm_info_get_card(const snd_pcm_info_t *obj) { - return obj->card; -} - -unsigned int snd_pcm_info_get_subdevices_count(const snd_pcm_info_t *obj) { - return 0; -} - -unsigned int snd_pcm_info_get_subdevices_avail(const snd_pcm_info_t *obj) { - return 0; -} - -const char* snd_pcm_info_get_id(const snd_pcm_info_t *obj) { - return "default"; -} - -const char* snd_pcm_info_get_name(const snd_pcm_info_t *obj) { - return "ALSA stub default device"; -} - -const char* snd_pcm_info_get_subdevice_name(const snd_pcm_info_t *obj) { - return ""; -} - -int snd_pcm_info_get_stream(const snd_pcm_info_t *obj) { - return obj->stream; -} - -int snd_ctl_open(snd_ctl_t **handle, const char *name, int mode) { - printf("ALSA stub: snd_ctl_open(%s)\n", name); - *handle = NULL; - return -19; -} - -int snd_ctl_close(snd_ctl_t *handle) { - printf("ALSA stub: snd_ctl_close(%p)\n", (void*)handle); - return 0; -} - -int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr) { - printf("ALSA stub: snd_ctl_card_info_malloc\n"); - *ptr = (snd_ctl_card_info_t*)calloc(1, sizeof(snd_ctl_card_info_t)); - return *ptr ? 0 : -12; -} - -void snd_ctl_card_info_free(snd_ctl_card_info_t *obj) { - printf("ALSA stub: snd_ctl_card_info_free\n"); - free(obj); -} - -int snd_ctl_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info) { - printf("ALSA stub: snd_ctl_card_info(%p)\n", (void*)handle); - return -19; -} - -const char* snd_ctl_card_info_get_id(const snd_ctl_card_info_t *obj) { - return "stub"; -} - -const char* snd_ctl_card_info_get_name(const snd_ctl_card_info_t *obj) { - return "ALSA Stub Card"; -} - -int snd_ctl_pcm_info(snd_ctl_t *handle, snd_pcm_info_t *info) { - printf("ALSA stub: snd_ctl_pcm_info(%p)\n", (void*)handle); - return -2; -} - -int snd_ctl_pcm_next_device(snd_ctl_t *handle, int *device) { - printf("ALSA stub: snd_ctl_pcm_next_device\n"); - *device = -1; - return 0; -} - -int snd_ctl_rawmidi_next_device(snd_ctl_t *handle, int *device) { - *device = -1; - return 0; -} - -int snd_card_next(int *card) { - printf("ALSA stub: snd_card_next\n"); - *card = -1; - return 0; -} - -int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t **ptr) { - printf("ALSA stub: snd_pcm_hw_params_malloc\n"); - *ptr = (snd_pcm_hw_params_t*)calloc(1, sizeof(snd_pcm_hw_params_t)); - return *ptr ? 0 : -12; -} - -void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj) { - printf("ALSA stub: snd_pcm_hw_params_free\n"); - free(obj); -} + if (!pcm || !info) return -22; + info->card = 0; info->device = 0; info->subdevice = 0; + info->stream = pcm->stream; info->subdevices_count = 1; info->subdevices_avail = 1; + strcpy(info->id, "default"); + snprintf(info->name, sizeof(info->name), "Android Audio (%s)", pcm->name); + strcpy(info->subname, ""); + return 0; +} + +int snd_pcm_info_malloc(snd_pcm_info_t **ptr) { *ptr = (snd_pcm_info_t*)calloc(1, sizeof(snd_pcm_info_t)); return *ptr ? 0 : -12; } +void snd_pcm_info_free(snd_pcm_info_t *obj) { free(obj); } +void snd_pcm_info_set_device(snd_pcm_info_t *obj, unsigned int val) { obj->device = val; } +void snd_pcm_info_set_subdevice(snd_pcm_info_t *obj, unsigned int val) { obj->subdevice = val; } +void snd_pcm_info_set_stream(snd_pcm_info_t *obj, int val) { obj->stream = val; } +int snd_pcm_info_get_card(const snd_pcm_info_t *obj) { return obj->card; } +unsigned int snd_pcm_info_get_subdevices_count(const snd_pcm_info_t *obj) { return obj->subdevices_count; } +unsigned int snd_pcm_info_get_subdevices_avail(const snd_pcm_info_t *obj) { return obj->subdevices_avail; } +const char* snd_pcm_info_get_id(const snd_pcm_info_t *obj) { return obj->id; } +const char* snd_pcm_info_get_name(const snd_pcm_info_t *obj) { return obj->name; } +const char* snd_pcm_info_get_subdevice_name(const snd_pcm_info_t *obj) { return obj->subname; } +int snd_pcm_info_get_stream(const snd_pcm_info_t *obj) { return obj->stream; } + +int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t **ptr) { *ptr = (snd_pcm_hw_params_t*)calloc(1, sizeof(snd_pcm_hw_params_t)); return *ptr ? 0 : -12; } +void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj) { free(obj); } int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { - printf("ALSA stub: snd_pcm_hw_params_any\n"); - return -19; + if (!params) return -22; memcpy(params, &pcm->hw, sizeof(snd_pcm_hw_params_t)); return 0; } int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access) { - return -19; + if (!params) return -22; params->access = access; return 0; } int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val) { - return -19; + if (!params) return -22; + format_entry_t *fe = find_format(val); + if (!fe) return -22; + params->format = val; params->bits = fe->bits; params->sbits = fe->sbits; + params->signed_fmt = fe->signed_fmt; params->big_endian = fe->big_endian; params->linear = fe->linear; + return 0; } int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { - return -19; + if (!params) return -22; if (val < 1 || val > 2) return -22; params->channels = val; return 0; } int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - return -19; + if (!params || !val) return -22; + unsigned int rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000 }; + unsigned int best = rates[0]; int best_diff = abs((int)*val - (int)rates[0]); + for (int i = 1; i < 8; i++) { int d = abs((int)*val - (int)rates[i]); if (d < best_diff) { best_diff = d; best = rates[i]; } } + params->rate = best; *val = best; if (dir) *dir = 0; + return 0; } -int snd_pcm_hw_params_set_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { - return -19; -} +int snd_pcm_hw_params_set_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { return 0; } int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - return -19; + if (!params || !val) return -22; + if (*val < 2) *val = 2; if (*val > 8) *val = 8; + params->periods = *val; if (dir) *dir = 0; + return 0; } int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { - return -19; + if (!params || !val) return -22; params->buffer_frames = *val; return 0; } int snd_pcm_hw_params_set_buffer_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - return -19; + if (!params || !val) return -22; + params->buffer_frames = (*val * params->rate) / 1000000; if (dir) *dir = 0; + return 0; } int snd_pcm_hw_params_set_period_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - return -19; + if (!params || !val) return -22; + params->period_frames = (*val * params->rate) / 1000000; + if (params->period_frames < 64) params->period_frames = 64; + if (params->period_frames > params->buffer_frames) params->period_frames = params->buffer_frames / 4; + if (dir) *dir = 0; + return 0; } int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { - return -19; -} - -int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { - return -19; -} - -int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *access) { - return -19; -} - -int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params, snd_pcm_format_t *format) { - return -19; -} - -int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *val) { - return -19; -} - -int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - return -19; -} - -int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir) { - return -19; -} - -int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { - return -19; -} - -int snd_pcm_hw_params_get_sbits(const snd_pcm_hw_params_t *params) { - return -19; -} - -int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - return -19; -} - -int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - return -19; -} - -int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params) { + if (!pcm || !params) return -22; + memcpy(&pcm->hw, params, sizeof(snd_pcm_hw_params_t)); + pcm->state = SND_PCM_STATE_SETUP; + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { int ret = pcm_setup_player(pcm); if (ret) return ret; } + int fs = (pcm->hw.channels * pcm->hw.bits) / 8; if (fs == 0) fs = 2; + pcm->frame_size = fs; pcm->buffer_size = pcm->hw.buffer_frames; + if (pcm->buffer) free(pcm->buffer); + pcm->buffer = (short*)calloc(pcm->buffer_size, fs); + if (!pcm->buffer) return -12; + fprintf(stderr, "ALSA stub: hw_params: %dHz %dch fmt=%d buf=%d period=%d\n", + pcm->hw.rate, pcm->hw.channels, pcm->hw.format, pcm->hw.buffer_frames, pcm->hw.period_frames); return 0; } -int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr) { - printf("ALSA stub: snd_pcm_sw_params_malloc\n"); - *ptr = (snd_pcm_sw_params_t*)calloc(1, sizeof(snd_pcm_sw_params_t)); - return *ptr ? 0 : -12; -} - -void snd_pcm_sw_params_free(snd_pcm_sw_params_t *obj) { - printf("ALSA stub: snd_pcm_sw_params_free\n"); - free(obj); -} - -int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { - return -19; -} - -int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { - return -19; -} - -int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { - return -19; -} - -int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { - return -19; -} - -int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { - return -19; -} - -int snd_pcm_sw_params_set_silence_size(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) { - return -19; -} - -int snd_pcm_sw_params_get_silence_threshold(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val) { - return -19; -} - -int snd_pcm_sw_params_get_silence_size(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val) { - return -19; -} - -int snd_pcm_sw_params_get_boundary(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val) { - return -19; -} - -int snd_pcm_sw_params_set_period_event(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, int val) { - return -19; -} - -int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { - return -19; -} +int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { + if (!pcm || !params) return -22; memcpy(params, &pcm->hw, sizeof(snd_pcm_hw_params_t)); return 0; +} + +int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *a) { if (!params || !a) return -22; *a = params->access; return 0; } +int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params, snd_pcm_format_t *f) { if (!params || !f) return -22; *f = params->format; return 0; } +int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *v) { if (!params || !v) return -22; *v = params->channels; return 0; } +int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (!params || !v) return -22; *v = params->rate; if (d) *d = 0; return 0; } +int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *v, int *d) { if (!params || !v) return -22; *v = params->period_frames; if (d) *d = 0; return 0; } +int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *v) { if (!params || !v) return -22; *v = params->buffer_frames; return 0; } +int snd_pcm_hw_params_get_sbits(const snd_pcm_hw_params_t *params) { return params ? params->sbits : 0; } +int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (!params || !v) return -22; *v = (params->buffer_frames * 1000000) / params->rate; if (d) *d = 0; return 0; } +int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (!params || !v) return -22; *v = (params->period_frames * 1000000) / params->rate; if (d) *d = 0; return 0; } +int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params) { (void)params; return 0; } + +int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr) { *ptr = (snd_pcm_sw_params_t*)calloc(1, sizeof(snd_pcm_sw_params_t)); return *ptr ? 0 : -12; } +void snd_pcm_sw_params_free(snd_pcm_sw_params_t *obj) { free(obj); } +int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { return 0; } +int snd_pcm_sw_params_set_avail_min(snd_pcm_t *p, snd_pcm_sw_params_t *pp, snd_pcm_uframes_t v) { return 0; } +int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *p, snd_pcm_sw_params_t *pp, snd_pcm_uframes_t v) { return 0; } +int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *p, snd_pcm_sw_params_t *pp, snd_pcm_uframes_t v) { return 0; } +int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *p, snd_pcm_sw_params_t *pp, snd_pcm_uframes_t v) { return 0; } +int snd_pcm_sw_params_set_silence_size(snd_pcm_t *p, snd_pcm_sw_params_t *pp, snd_pcm_uframes_t v) { return 0; } +int snd_pcm_sw_params_get_silence_threshold(const snd_pcm_sw_params_t *p, snd_pcm_uframes_t *v) { return 0; } +int snd_pcm_sw_params_get_silence_size(const snd_pcm_sw_params_t *p, snd_pcm_uframes_t *v) { return 0; } +int snd_pcm_sw_params_get_boundary(const snd_pcm_sw_params_t *p, snd_pcm_uframes_t *v) { return 0; } +int snd_pcm_sw_params_set_period_event(snd_pcm_t *p, snd_pcm_sw_params_t *pp, int v) { return 0; } +int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { return 0; } int snd_pcm_prepare(snd_pcm_t *pcm) { - return -19; + if (!pcm) return -22; + pthread_mutex_lock(&pcm->mutex); + pcm->write_pos = 0; pcm->play_pos = 0; pcm->buffered_frames = 0; + if (pcm->player && pcm->stream == SND_PCM_STREAM_PLAYBACK) { + (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); + (*pcm->bufferQueue)->Clear(pcm->bufferQueue); + } + pcm->state = SND_PCM_STATE_PREPARED; + pthread_mutex_unlock(&pcm->mutex); + return 0; } snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) { - return -19; + if (!pcm || !buffer) return -22; + pthread_mutex_lock(&pcm->mutex); + if (pcm->state != SND_PCM_STATE_PREPARED && pcm->state != SND_PCM_STATE_RUNNING) { pthread_mutex_unlock(&pcm->mutex); return -22; } + if (pcm->stream != SND_PCM_STREAM_PLAYBACK) { pthread_mutex_unlock(&pcm->mutex); return -22; } + if (pcm->state == SND_PCM_STATE_PREPARED && pcm->player) { + SLresult r = (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_PLAYING); + if (r == SL_RESULT_SUCCESS) pcm->state = SND_PCM_STATE_RUNNING; + } + if (pcm->bufferQueue) { + SLresult r = (*pcm->bufferQueue)->Enqueue(pcm->bufferQueue, buffer, size * pcm->frame_size); + if (r != SL_RESULT_SUCCESS) { pthread_mutex_unlock(&pcm->mutex); return -5; } + pcm->buffered_frames += size; + } + pthread_mutex_unlock(&pcm->mutex); + return size; } -snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) { - return -19; -} +snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) { (void)buffer; (void)size; return -19; } int snd_pcm_drain(snd_pcm_t *pcm) { - return -19; -} - -int snd_pcm_drop(snd_pcm_t *pcm) { - return -19; -} - -int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent) { - return -19; -} - -snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm) { - return 0; -} - -snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm) { - return -19; -} - -snd_pcm_sframes_t snd_pcm_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) { - return -19; -} - -int snd_pcm_resume(snd_pcm_t *pcm) { - return -19; -} - -int snd_pcm_start(snd_pcm_t *pcm) { - return -19; -} - -int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) { - return -19; -} - -snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm) { - return -19; -} - -int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay) { - return -19; -} - -int snd_pcm_wait(snd_pcm_t *pcm, int timeout) { - return -19; -} - -snd_pcm_sframes_t snd_pcm_mmap_begin(snd_pcm_t *pcm, const void **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames) { - return -19; -} - -snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames) { - return -19; -} - -int snd_pcm_format_physical_width(snd_pcm_format_t format) { - return 16; -} - -int snd_pcm_format_width(snd_pcm_format_t format) { - return 16; -} - -int snd_pcm_format_signed(snd_pcm_format_t format) { - return 1; -} - -int snd_pcm_format_big_endian(snd_pcm_format_t format) { - return 0; -} - -int snd_pcm_format_linear(snd_pcm_format_t format) { - return 1; -} - -size_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) { - return samples * 2; -} - -snd_pcm_format_t snd_pcm_build_linear_format(int width, int pwidth, int unsignd, int big_endian) { - return 0; -} - -int snd_pcm_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2) { - return -19; -} - -int snd_pcm_unlink(snd_pcm_t *pcm) { - return -19; -} - -int snd_mixer_open(snd_mixer_t **mixer, int mode) { - printf("ALSA stub: snd_mixer_open\n"); - *mixer = NULL; - return -19; -} - -int snd_mixer_close(snd_mixer_t *mixer) { + if (!pcm) return -22; + if (pcm->player) (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); + pcm->state = SND_PCM_STATE_DRAINING; return 0; } -int snd_mixer_attach(snd_mixer_t *mixer, const char *name) { - return -19; -} - -int snd_mixer_detach(snd_mixer_t *mixer, const char *name) { - return 0; -} - -int snd_mixer_selem_register(snd_mixer_t *mixer, void *options, void *classp) { - return -19; -} - -int snd_mixer_load(snd_mixer_t *mixer) { - return -19; -} - -void snd_mixer_set_callback(snd_mixer_t *obj, void *val) {} - -void snd_mixer_elem_set_callback(snd_mixer_elem_t *obj, void *val) {} - -snd_mixer_elem_t* snd_mixer_first_elem(snd_mixer_t *mixer) { - return NULL; -} - -snd_mixer_elem_t* snd_mixer_last_elem(snd_mixer_t *mixer) { - return NULL; -} - -snd_mixer_elem_t* snd_mixer_elem_next(snd_mixer_elem_t *elem) { - return NULL; -} - -snd_mixer_elem_t* snd_mixer_elem_prev(snd_mixer_elem_t *elem) { - return NULL; -} - -int snd_mixer_handle_events(snd_mixer_t *mixer) { - return -19; -} +int snd_pcm_drop(snd_pcm_t *pcm) { + if (!pcm) return -22; + if (pcm->player) { (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); (*pcm->bufferQueue)->Clear(pcm->bufferQueue); } + pcm->buffered_frames = 0; pcm->state = SND_PCM_STATE_SETUP; + return 0; +} + +int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent) { (void)err; (void)silent; return snd_pcm_prepare(pcm); } +snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm) { return pcm ? pcm->state : SND_PCM_STATE_DISCONNECTED; } +snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm) { return pcm ? (snd_pcm_sframes_t)(pcm->hw.buffer_frames - pcm->buffered_frames) : -22; } +snd_pcm_sframes_t snd_pcm_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) { (void)frames; return 0; } +int snd_pcm_resume(snd_pcm_t *pcm) { if (!pcm) return -22; if (pcm->player) (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_PLAYING); pcm->state = SND_PCM_STATE_RUNNING; return 0; } +int snd_pcm_start(snd_pcm_t *pcm) { return snd_pcm_prepare(pcm); } +int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) { (void)nonblock; return 0; } +snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm) { return snd_pcm_avail_update(pcm); } +int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay) { if (!pcm || !delay) return -22; *delay = 0; return 0; } +int snd_pcm_wait(snd_pcm_t *pcm, int timeout) { (void)timeout; return 1; } +snd_pcm_sframes_t snd_pcm_mmap_begin(snd_pcm_t *pcm, const void **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames) { (void)areas; (void)offset; (void)frames; return -77; } +snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames) { (void)offset; (void)frames; return -77; } + +int snd_pcm_format_physical_width(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->bits : 16; } +int snd_pcm_format_width(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->sbits : 16; } +int snd_pcm_format_signed(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->signed_fmt : 1; } +int snd_pcm_format_big_endian(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->big_endian : 0; } +int snd_pcm_format_linear(snd_pcm_format_t format) { return 1; } +size_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) { format_entry_t *fe = find_format(format); return samples * ((fe ? fe->bits : 16) / 8); } +snd_pcm_format_t snd_pcm_build_linear_format(int w, int pw, int u, int be) { (void)w; (void)pw; (void)u; (void)be; return SND_PCM_FORMAT_S16_LE; } +int snd_pcm_link(snd_pcm_t *a, snd_pcm_t *b) { (void)a; (void)b; return 0; } +int snd_pcm_unlink(snd_pcm_t *p) { (void)p; return 0; } + +int snd_mixer_open(snd_mixer_t **m, int mode) { (void)mode; *m = NULL; return -19; } +int snd_mixer_close(snd_mixer_t *m) { (void)m; return 0; } +int snd_mixer_attach(snd_mixer_t *m, const char *n) { (void)m; (void)n; return -19; } +int snd_mixer_detach(snd_mixer_t *m, const char *n) { (void)m; (void)n; return 0; } +int snd_mixer_selem_register(snd_mixer_t *m, void *o, void *c) { (void)m; (void)o; (void)c; return -19; } +int snd_mixer_load(snd_mixer_t *m) { (void)m; return -19; } +void snd_mixer_set_callback(snd_mixer_t *o, void *v) { (void)o; (void)v; } +void snd_mixer_elem_set_callback(snd_mixer_elem_t *o, void *v) { (void)o; (void)v; } +snd_mixer_elem_t* snd_mixer_first_elem(snd_mixer_t *m) { (void)m; return NULL; } +snd_mixer_elem_t* snd_mixer_last_elem(snd_mixer_t *m) { (void)m; return NULL; } +snd_mixer_elem_t* snd_mixer_elem_next(snd_mixer_elem_t *e) { (void)e; return NULL; } +snd_mixer_elem_t* snd_mixer_elem_prev(snd_mixer_elem_t *e) { (void)e; return NULL; } +int snd_mixer_handle_events(snd_mixer_t *m) { (void)m; return 0; } int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr) { - printf("ALSA stub: snd_mixer_selem_id_malloc\n"); - *ptr = (snd_mixer_selem_id_t*)calloc(1, sizeof(snd_mixer_selem_id_t)); - return *ptr ? 0 : -12; -} - -void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj) { - free(obj); -} - -void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t *obj, const char *val) {} - -const char* snd_mixer_selem_id_get_name(snd_mixer_selem_id_t *obj) { - return ""; -} - -void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t *obj, unsigned int val) {} - -unsigned int snd_mixer_selem_id_get_index(snd_mixer_selem_id_t *obj) { - return 0; -} - -const char* snd_mixer_selem_get_name(snd_mixer_elem_t *elem) { - return ""; -} - -unsigned int snd_mixer_selem_get_index(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_is_active(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t *elem, int channel, int *value) { - return -19; -} - -int snd_mixer_selem_get_capture_switch(snd_mixer_elem_t *elem, int channel, int *value) { - return -19; -} - -int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { - return -19; -} - -int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { - return -19; -} - -int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *elem, long value) { - return -19; -} - -int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t *elem, long value) { - return -19; -} - -int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *elem, long *min, long *max) { - return -19; -} - -int snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t *elem, long *min, long *max) { - return -19; -} - -int snd_mixer_selem_set_playback_volume_range(snd_mixer_elem_t *elem, long min, long max) { - return -19; -} - -int snd_mixer_selem_set_capture_volume_range(snd_mixer_elem_t *elem, long min, long max) { - return -19; -} - -int snd_mixer_selem_get_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { - return -19; -} - -int snd_mixer_selem_get_capture_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) { - return -19; -} - -int snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *elem, long value, int dir) { - return -19; -} - -int snd_mixer_selem_set_capture_dB_all(snd_mixer_elem_t *elem, long value, int dir) { - return -19; -} - -int snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *elem, long *min, long *max) { - return -19; -} - -int snd_mixer_selem_get_capture_dB_range(snd_mixer_elem_t *elem, long *min, long *max) { - return -19; -} - -int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *elem) { - return 1; -} - -int snd_mixer_selem_has_capture_volume_joined(snd_mixer_elem_t *elem) { - return 1; -} - -int snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *elem) { - return 1; -} - -int snd_mixer_selem_has_capture_switch_joined(snd_mixer_elem_t *elem) { - return 1; -} - -int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t *elem, int value) { - return -19; -} - -int snd_mixer_selem_set_capture_switch_all(snd_mixer_elem_t *elem, int value) { - return -19; -} - -int snd_mixer_selem_get_playback_switch_switch(snd_mixer_elem_t *elem, int channel, int *value) { - return -19; -} - -int snd_mixer_selem_has_capture_switch_switch(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_get_capture_switch_switch(snd_mixer_elem_t *elem, int channel, int *value) { - return -19; -} - -int snd_mixer_selem_get_type(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_get_count(snd_mixer_elem_t *elem) { - return 0; -} - -int snd_mixer_selem_channel_id(snd_mixer_selem_channel_id_t channel) { - return (int)channel; -} - -int snd_seq_open(snd_seq_t **seq, const char *name, int streams, int mode) { - printf("ALSA stub: snd_seq_open(%s)\n", name); - *seq = NULL; - return -19; -} - -int snd_seq_close(snd_seq_t *seq) { - return 0; -} - -int snd_seq_set_client_name(snd_seq_t *seq, const char *name) { - return -19; -} - -int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type) { - return -19; -} - -int snd_seq_connect_from(snd_seq_t *seq, int my_port, int src_client, int src_port) { - return -19; -} - -int snd_seq_connect_to(snd_seq_t *seq, int my_port, int dst_client, int dst_port) { - return -19; -} - -int snd_seq_alloc_named_queue(snd_seq_t *seq, const char *name) { - return -19; -} - -int snd_seq_alloc_queue(snd_seq_t *seq) { - return -19; -} - -int snd_seq_free_queue(snd_seq_t *seq, int q) { - return 0; -} - -int snd_seq_set_tempo(snd_seq_t *seq, int q, int tempo) { - return -19; -} - -int snd_seq_drain_output(snd_seq_t *seq) { - return -19; -} - -int snd_seq_sync_output_queue(snd_seq_t *seq) { - return -19; -} - -int snd_seq_event_output(snd_seq_t *seq, void *ev) { - return -19; -} - -int snd_seq_event_input(snd_seq_t *seq, void **ev) { - return -19; -} - -int snd_seq_free_event(void *ev) { - return 0; -} - -int snd_seq_event_input_pending(snd_seq_t *seq, int fetch) { - return 0; -} - -int snd_rawmidi_open(snd_rawmidi_t **in, snd_rawmidi_t **out, const char *name, int mode) { - printf("ALSA stub: snd_rawmidi_open(%s)\n", name); - *in = NULL; - *out = NULL; - return -19; -} - -int snd_rawmidi_close(snd_rawmidi_t *sub) { - return 0; -} - -int snd_rawmidi_read(snd_rawmidi_t *in, void *buf, size_t size) { - return -19; -} - -int snd_rawmidi_write(snd_rawmidi_t *out, const void *buf, size_t size) { - return -19; -} - -int snd_rawmidi_drain(snd_rawmidi_t *out) { - return 0; -} - -int snd_rawmidi_nonblock(snd_rawmidi_t *rm, int nonblock) { - return -19; -} - -int snd_rawmidi_params(snd_rawmidi_t *rm, void *params) { - return -19; -} - -unsigned int snd_rawmidi_count(snd_rawmidi_t *rm) { - return 0; -} - -int snd_rawmidi_params_malloc(void **ptr) { - *ptr = calloc(1, 64); - return *ptr ? 0 : -12; -} - -void snd_rawmidi_params_free(void *obj) { - free(obj); -} - -int snd_rawmidi_params_set_buffer_size(snd_rawmidi_t *rm, void *params, unsigned long val) { - return -19; -} - -int snd_rawmidi_params_set_avail_min(snd_rawmidi_t *rm, void *params, unsigned long val) { - return -19; -} - -int snd_rawmidi_status_malloc(void **ptr) { - *ptr = calloc(1, 64); + *ptr = (snd_mixer_selem_id_t*)calloc(1, sizeof(snd_mixer_selem_id_t)); return *ptr ? 0 : -12; +} +void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj) { free(obj); } +void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t *obj, const char *val) { if (obj && val) strncpy(obj->name, val, sizeof(obj->name) - 1); } +const char* snd_mixer_selem_id_get_name(snd_mixer_selem_id_t *obj) { return obj ? obj->name : ""; } +void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t *obj, unsigned int val) { if (obj) obj->index = val; } +unsigned int snd_mixer_selem_id_get_index(snd_mixer_selem_id_t *obj) { return obj ? obj->index : 0; } +const char* snd_mixer_selem_get_name(snd_mixer_elem_t *e) { (void)e; return ""; } +unsigned int snd_mixer_selem_get_index(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_is_active(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t *e, int c, int *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_get_capture_switch(snd_mixer_elem_t *e, int c, int *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *e, snd_mixer_selem_channel_id_t c, long *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t *e, snd_mixer_selem_channel_id_t c, long *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *e, long v) { (void)e; (void)v; return -19; } +int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t *e, long v) { (void)e; (void)v; return -19; } +int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *e, long *mn, long *mx) { (void)e; (void)mn; (void)mx; return -19; } +int snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t *e, long *mn, long *mx) { (void)e; (void)mn; (void)mx; return -19; } +int snd_mixer_selem_set_playback_volume_range(snd_mixer_elem_t *e, long mn, long mx) { (void)e; (void)mn; (void)mx; return -19; } +int snd_mixer_selem_set_capture_volume_range(snd_mixer_elem_t *e, long mn, long mx) { (void)e; (void)mn; (void)mx; return -19; } +int snd_mixer_selem_get_playback_dB(snd_mixer_elem_t *e, snd_mixer_selem_channel_id_t c, long *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_get_capture_dB(snd_mixer_elem_t *e, snd_mixer_selem_channel_id_t c, long *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *e, long v, int d) { (void)e; (void)v; (void)d; return -19; } +int snd_mixer_selem_set_capture_dB_all(snd_mixer_elem_t *e, long v, int d) { (void)e; (void)v; (void)d; return -19; } +int snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *e, long *mn, long *mx) { (void)e; (void)mn; (void)mx; return -19; } +int snd_mixer_selem_get_capture_dB_range(snd_mixer_elem_t *e, long *mn, long *mx) { (void)e; (void)mn; (void)mx; return -19; } +int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *e) { (void)e; return 1; } +int snd_mixer_selem_has_capture_volume_joined(snd_mixer_elem_t *e) { (void)e; return 1; } +int snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *e) { (void)e; return 1; } +int snd_mixer_selem_has_capture_switch_joined(snd_mixer_elem_t *e) { (void)e; return 1; } +int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t *e, int v) { (void)e; (void)v; return -19; } +int snd_mixer_selem_set_capture_switch_all(snd_mixer_elem_t *e, int v) { (void)e; (void)v; return -19; } +int snd_mixer_selem_get_playback_switch_switch(snd_mixer_elem_t *e, int c, int *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_has_capture_switch_switch(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_get_capture_switch_switch(snd_mixer_elem_t *e, int c, int *v) { (void)e; (void)c; (void)v; return -19; } +int snd_mixer_selem_get_type(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_get_count(snd_mixer_elem_t *e) { (void)e; return 0; } +int snd_mixer_selem_channel_id(snd_mixer_selem_channel_id_t c) { return (int)c; } + +int snd_ctl_open(snd_ctl_t **h, const char *n, int m) { (void)n; (void)m; *h = NULL; return -19; } +int snd_ctl_close(snd_ctl_t *h) { (void)h; return 0; } +int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr) { + *ptr = (snd_ctl_card_info_t*)calloc(1, sizeof(snd_ctl_card_info_t)); + if (*ptr) { strcpy((*ptr)->id, "Android"); strcpy((*ptr)->name, "Android Audio"); strcpy((*ptr)->driver, "alsa_stub"); } return *ptr ? 0 : -12; } - -void snd_rawmidi_status_free(void *obj) { - free(obj); -} - -int snd_rawmidi_status(snd_rawmidi_t *rm, void *status) { - return -19; -} - -unsigned int snd_rawmidi_status_get_avail(void *status) { - return 0; -} - -int snd_rawmidi_nonblock_old(snd_rawmidi_t *rm, int nonblock) { - return -19; -} - -int snd_output_stdio_attach(void **output, FILE *fp, int close) { - return 0; -} - -int snd_output_close(void *output) { - return 0; -} - -int snd_config_update(void) { - return 0; -} - -int snd_config_update_free(void) { - return 0; -} \ No newline at end of file +void snd_ctl_card_info_free(snd_ctl_card_info_t *obj) { free(obj); } +int snd_ctl_card_info(snd_ctl_t *h, snd_ctl_card_info_t *i) { (void)h; (void)i; return -19; } +const char* snd_ctl_card_info_get_id(const snd_ctl_card_info_t *o) { return o ? o->id : ""; } +const char* snd_ctl_card_info_get_name(const snd_ctl_card_info_t *o) { return o ? o->name : ""; } +const char* snd_ctl_card_info_get_driver(const snd_ctl_card_info_t *o) { return o ? o->driver : ""; } +int snd_ctl_pcm_info(snd_ctl_t *h, snd_pcm_info_t *i) { (void)h; (void)i; return -2; } +int snd_ctl_pcm_next_device(snd_ctl_t *h, int *d) { (void)h; *d = -1; return 0; } +int snd_ctl_rawmidi_next_device(snd_ctl_t *h, int *d) { (void)h; *d = -1; return 0; } +int snd_card_next(int *card) { *card = -1; return 0; } + +int snd_seq_open(snd_seq_t **s, const char *n, int st, int m) { (void)n; (void)st; (void)m; *s = NULL; return -19; } +int snd_seq_close(snd_seq_t *s) { (void)s; return 0; } +int snd_seq_set_client_name(snd_seq_t *s, const char *n) { (void)s; (void)n; return -19; } +int snd_seq_create_simple_port(snd_seq_t *s, const char *n, unsigned int c, unsigned int t) { (void)s; (void)n; (void)c; (void)t; return -19; } +int snd_seq_connect_from(snd_seq_t *s, int mp, int sc, int sp) { (void)s; (void)mp; (void)sc; (void)sp; return -19; } +int snd_seq_connect_to(snd_seq_t *s, int mp, int dc, int dp) { (void)s; (void)mp; (void)dc; (void)dp; return -19; } +int snd_seq_alloc_named_queue(snd_seq_t *s, const char *n) { (void)s; (void)n; return -19; } +int snd_seq_alloc_queue(snd_seq_t *s) { (void)s; return -19; } +int snd_seq_free_queue(snd_seq_t *s, int q) { (void)s; (void)q; return 0; } +int snd_seq_set_tempo(snd_seq_t *s, int q, int t) { (void)s; (void)q; (void)t; return -19; } +int snd_seq_drain_output(snd_seq_t *s) { (void)s; return -19; } +int snd_seq_sync_output_queue(snd_seq_t *s) { (void)s; return -19; } +int snd_seq_event_output(snd_seq_t *s, void *e) { (void)s; (void)e; return -19; } +int snd_seq_event_input(snd_seq_t *s, void **e) { (void)s; (void)e; return -19; } +int snd_seq_free_event(void *e) { (void)e; return 0; } +int snd_seq_event_input_pending(snd_seq_t *s, int f) { (void)s; (void)f; return 0; } + +int snd_rawmidi_open(snd_rawmidi_t **in, snd_rawmidi_t **out, const char *n, int m) { + (void)n; (void)m; if (in) *in = NULL; if (out) *out = NULL; return -19; +} +int snd_rawmidi_close(snd_rawmidi_t *s) { (void)s; return 0; } +int snd_rawmidi_read(snd_rawmidi_t *in, void *b, size_t sz) { (void)in; (void)b; (void)sz; return -19; } +int snd_rawmidi_write(snd_rawmidi_t *out, const void *b, size_t sz) { (void)out; (void)b; (void)sz; return -19; } +int snd_rawmidi_drain(snd_rawmidi_t *out) { (void)out; return 0; } +int snd_rawmidi_nonblock(snd_rawmidi_t *rm, int nb) { (void)rm; (void)nb; return -19; } +int snd_rawmidi_params(snd_rawmidi_t *rm, void *p) { (void)rm; (void)p; return -19; } +unsigned int snd_rawmidi_count(snd_rawmidi_t *rm) { (void)rm; return 0; } +int snd_rawmidi_params_malloc(void **ptr) { *ptr = calloc(1, 64); return *ptr ? 0 : -12; } +void snd_rawmidi_params_free(void *obj) { free(obj); } +int snd_rawmidi_params_set_buffer_size(snd_rawmidi_t *rm, void *p, unsigned long v) { (void)rm; (void)p; (void)v; return -19; } +int snd_rawmidi_params_set_avail_min(snd_rawmidi_t *rm, void *p, unsigned long v) { (void)rm; (void)p; (void)v; return -19; } +int snd_rawmidi_status_malloc(void **ptr) { *ptr = calloc(1, 64); return *ptr ? 0 : -12; } +void snd_rawmidi_status_free(void *obj) { free(obj); } +int snd_rawmidi_status(snd_rawmidi_t *rm, void *st) { (void)rm; (void)st; return -19; } +unsigned int snd_rawmidi_status_get_avail(void *st) { (void)st; return 0; } +int snd_rawmidi_nonblock_old(snd_rawmidi_t *rm, int nb) { (void)rm; (void)nb; return -19; } +int snd_output_stdio_attach(void **o, FILE *fp, int c) { (void)o; (void)fp; (void)c; return 0; } +int snd_output_close(void *o) { (void)o; return 0; } +int snd_config_update(void) { return 0; } +int snd_config_update_free(void) { return 0; } \ No newline at end of file From 86a9067112d6e4c087edcbebdf8322d13b914234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 17:17:05 +0800 Subject: [PATCH 05/18] Link OpenSLES library for alsa_stub --- FCLauncher/src/main/jni/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/FCLauncher/src/main/jni/CMakeLists.txt b/FCLauncher/src/main/jni/CMakeLists.txt index b496b33bd8..be3c17a64f 100644 --- a/FCLauncher/src/main/jni/CMakeLists.txt +++ b/FCLauncher/src/main/jni/CMakeLists.txt @@ -202,4 +202,5 @@ target_link_libraries( PRIVATE log dl + OpenSLES ) \ No newline at end of file From 0c6e2e075c1d7c15c1ea55ca40b3cfd3485f20c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 17:25:50 +0800 Subject: [PATCH 06/18] Fix OpenSL ES include: use OpenSLES_Android.h --- FCLauncher/src/main/jni/alsa_stub/alsa_stub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c index 139f95d8e5..99911b7c53 100644 --- a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c +++ b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c @@ -4,7 +4,7 @@ #include #include #include -#include +#include #define FALSE 0 #define TRUE 1 From b89a42d518e2998e9bf5783e3358ddaa4a9c0356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 17:30:54 +0800 Subject: [PATCH 07/18] Fix: remove non-NDK SL_DATAFORMAT_PCM_SUB_BITS constants --- FCLauncher/src/main/jni/alsa_stub/alsa_stub.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c index 99911b7c53..15962d72ac 100644 --- a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c +++ b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c @@ -78,14 +78,14 @@ static pthread_mutex_t g_engine_mutex = PTHREAD_MUTEX_INITIALIZER; typedef struct { snd_pcm_format_t alsa_fmt; int bits; int sbits; - int signed_fmt; int big_endian; int linear; SLuint32 sl_format; + int signed_fmt; int big_endian; int linear; } format_entry_t; static format_entry_t format_table[] = { - { SND_PCM_FORMAT_U8, 8, 8, 0, 0, 1, SL_DATAFORMAT_PCM_SUB_BITS_8 }, - { SND_PCM_FORMAT_S16_LE, 16, 16, 1, 0, 1, SL_DATAFORMAT_PCM_SUB_BITS_16 }, - { SND_PCM_FORMAT_S32_LE, 32, 32, 1, 0, 1, SL_DATAFORMAT_PCM_SUB_BITS_32 }, - { -1, 0, 0, 0, 0, 0, 0 }, + { SND_PCM_FORMAT_U8, 8, 8, 0, 0, 1 }, + { SND_PCM_FORMAT_S16_LE, 16, 16, 1, 0, 1 }, + { SND_PCM_FORMAT_S32_LE, 32, 32, 1, 0, 1 }, + { -1, 0, 0, 0, 0, 0 }, }; static format_entry_t* find_format(snd_pcm_format_t fmt) { From 615af021252c5556451f37809741737d402b2ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 18:17:51 +0800 Subject: [PATCH 08/18] Add custom libjsound.so with OpenSL ES PCM playback --- FCLauncher/src/main/jni/jsound/jsound.c | 277 ++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 FCLauncher/src/main/jni/jsound/jsound.c diff --git a/FCLauncher/src/main/jni/jsound/jsound.c b/FCLauncher/src/main/jni/jsound/jsound.c new file mode 100644 index 0000000000..55f27f93a5 --- /dev/null +++ b/FCLauncher/src/main/jni/jsound/jsound.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DEVICES 8 +#define DEVICE_NAME "Android PCM Output" + +typedef struct { + SLObjectItf engineObj; + SLEngineItf engine; + SLObjectItf outputMixObj; + int initialized; + pthread_mutex_t mutex; +} AudioEngine; + +typedef struct { + int in_use; + int channels; + int sample_rate; + int bits_per_sample; + int frame_size; + int buffer_size; + int state; + SLObjectItf playerObj; + SLPlayItf player; + SLAndroidSimpleBufferQueueItf bufferQueue; + AudioEngine *engine; +} PcmDevice; + +static AudioEngine g_engine = {0}; + +static pthread_mutex_t g_device_mutex = PTHREAD_MUTEX_INITIALIZER; +static PcmDevice g_devices[MAX_DEVICES]; + +static int init_engine(AudioEngine *e) { + pthread_mutex_lock(&e->mutex); + if (e->initialized) { pthread_mutex_unlock(&e->mutex); return 0; } + SLresult r; + r = slCreateEngine(&e->engineObj, 0, NULL, 0, NULL, NULL); + if (r != SL_RESULT_SUCCESS) { pthread_mutex_unlock(&e->mutex); return -1; } + r = (*e->engineObj)->Realize(e->engineObj, SL_BOOLEAN_FALSE); + if (r != SL_RESULT_SUCCESS) { (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; pthread_mutex_unlock(&e->mutex); return -1; } + r = (*e->engineObj)->GetInterface(e->engineObj, SL_IID_ENGINE, &e->engine); + if (r != SL_RESULT_SUCCESS) { (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; pthread_mutex_unlock(&e->mutex); return -1; } + const SLInterfaceID ids[] = { SL_IID_VOLUME }; + const SLboolean req[] = { SL_BOOLEAN_FALSE }; + r = (*e->engine)->CreateOutputMix(e->engine, &e->outputMixObj, 1, ids, req); + if (r != SL_RESULT_SUCCESS) { (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; e->engine = NULL; pthread_mutex_unlock(&e->mutex); return -1; } + r = (*e->outputMixObj)->Realize(e->outputMixObj, SL_BOOLEAN_FALSE); + if (r != SL_RESULT_SUCCESS) { (*e->outputMixObj)->Destroy(e->outputMixObj); e->outputMixObj = NULL; (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; e->engine = NULL; pthread_mutex_unlock(&e->mutex); return -1; } + e->initialized = 1; + fprintf(stderr, "jsound: OpenSL ES engine initialized\n"); + pthread_mutex_unlock(&e->mutex); + return 0; +} + +static void buffer_callback(SLAndroidSimpleBufferQueueItf bq, void *context) { + (void)bq; (void)context; +} + +static int alloc_device(void) { + for (int i = 0; i < MAX_DEVICES; i++) { + if (!g_devices[i].in_use) { + memset(&g_devices[i], 0, sizeof(PcmDevice)); + g_devices[i].in_use = 1; + return i; + } + } + return -1; +} + +static void free_device(int id) { + if (id >= 0 && id < MAX_DEVICES) { + g_devices[id].in_use = 0; + } +} + +#define MIXER_TYPE_SOURCE 2 +#define MIXER_TYPE_TARGET 1 + +static jclass audioFormatClass = NULL; +static jclass encodingClass = NULL; +static jobject pcmSignedEncoding = NULL; + +static int init_jni_refs(JNIEnv *env) { + if (audioFormatClass) return 0; + jclass cls = (*env)->FindClass(env, "javax/sound/sampled/AudioFormat"); + if (!cls) return -1; + audioFormatClass = (jclass)(*env)->NewGlobalRef(env, cls); + + jclass encCls = (*env)->FindClass(env, "javax/sound/sampled/AudioFormat$Encoding"); + if (!encCls) return -1; + encodingClass = (jclass)(*env)->NewGlobalRef(env, encCls); + + jfieldID pcmSignedField = (*env)->GetStaticFieldID(env, encodingClass, "PCM_SIGNED", "Ljavax/sound/sampled/AudioFormat$Encoding;"); + if (pcmSignedField) { + jobject obj = (*env)->GetStaticObjectField(env, encodingClass, pcmSignedField); + if (obj) pcmSignedEncoding = (*env)->NewGlobalRef(env, obj); + } + return 0; +} + +JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nGetNumDevices(JNIEnv *env, jclass cls, jint mixerType) { + (void)env; (void)cls; + if (mixerType == MIXER_TYPE_SOURCE) return 1; + return 0; +} + +JNIEXPORT jobjectArray JNICALL Java_com_sun_media_sound_PortMixer_nGetDeviceDescription(JNIEnv *env, jclass cls, jint mixerType, jint deviceID) { + (void)cls; + if (mixerType != MIXER_TYPE_SOURCE || deviceID != 0) return NULL; + jclass stringClass = (*env)->FindClass(env, "java/lang/String"); + if (!stringClass) return NULL; + jobjectArray arr = (*env)->NewObjectArray(env, 4, stringClass, NULL); + if (!arr) return NULL; + (*env)->SetObjectArrayElement(env, arr, 0, (*env)->NewStringUTF(env, "Android PCM Output")); + (*env)->SetObjectArrayElement(env, arr, 1, (*env)->NewStringUTF(env, "FCL-Team")); + (*env)->SetObjectArrayElement(env, arr, 2, (*env)->NewStringUTF(env, "Android PCM audio via OpenSL ES")); + (*env)->SetObjectArrayElement(env, arr, 3, (*env)->NewStringUTF(env, "1.0")); + return arr; +} + +JNIEXPORT jlong JNICALL Java_com_sun_media_sound_PortMixer_nOpen(JNIEnv *env, jobject obj, jint mixerType, jint deviceID, jobject format, jint bufferSize) { + (void)obj; + if (mixerType != MIXER_TYPE_SOURCE || deviceID != 0) return -1; + if (!g_engine.initialized && init_engine(&g_engine) != 0) return -1; + if (init_jni_refs(env) != 0) return -1; + + int id = alloc_device(); + if (id < 0) return -1; + PcmDevice *dev = &g_devices[id]; + + jclass fmtCls = (*env)->GetObjectClass(env, format); + + jmethodID getEncoding = (*env)->GetMethodID(env, fmtCls, "getEncoding", "()Ljavax/sound/sampled/AudioFormat$Encoding;"); + jmethodID getSampleRate = (*env)->GetMethodID(env, fmtCls, "getSampleRate", "()F"); + jmethodID getSampleSize = (*env)->GetMethodID(env, fmtCls, "getSampleSizeInBits", "()I"); + jmethodID getChannels = (*env)->GetMethodID(env, fmtCls, "getChannels", "()I"); + jmethodID getFrameSize = (*env)->GetMethodID(env, fmtCls, "getFrameSize", "()I"); + jmethodID isBigEndian = (*env)->GetMethodID(env, fmtCls, "isBigEndian", "()Z"); + + if (!getEncoding || !getSampleRate || !getSampleSize || !getChannels) { + free_device(id); return -1; + } + + jfloat sampleRate = 44100.0f; + if (getSampleRate) sampleRate = (*env)->CallFloatMethod(env, format, getSampleRate); + int sampleSize = 16; + if (getSampleSize) sampleSize = (*env)->CallIntMethod(env, format, getSampleSize); + int channels = 2; + if (getChannels) channels = (*env)->CallIntMethod(env, format, getChannels); + int frameSize = (sampleSize / 8) * channels; + if (getFrameSize) frameSize = (*env)->CallIntMethod(env, format, getFrameSize); + + dev->channels = channels; + dev->sample_rate = (int)sampleRate; + dev->bits_per_sample = sampleSize; + dev->frame_size = frameSize; + dev->buffer_size = bufferSize > 0 ? bufferSize : 4096; + dev->state = 0; + dev->engine = &g_engine; + + SLDataFormat_PCM fmt; + fmt.formatType = SL_DATAFORMAT_PCM; + fmt.numChannels = channels; + fmt.samplesPerSec = (SLuint32)(sampleRate * 1000); + fmt.bitsPerSample = sampleSize; + fmt.containerSize = sampleSize; + fmt.channelMask = (channels == 1) ? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + fmt.endianness = SL_BYTEORDER_LITTLEENDIAN; + + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 }; + SLDataSource audioSrc = { &loc_bufq, &fmt }; + SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, g_engine.outputMixObj }; + SLDataSink audioSink = { &loc_outmix, NULL }; + SLInterfaceID ids[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; + SLboolean req[] = { SL_BOOLEAN_TRUE }; + + SLresult r = (*g_engine.engine)->CreateAudioPlayer(g_engine.engine, &dev->playerObj, &audioSrc, &audioSink, 1, ids, req); + if (r != SL_RESULT_SUCCESS) { free_device(id); return -1; } + r = (*dev->playerObj)->Realize(dev->playerObj, SL_BOOLEAN_FALSE); + if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } + r = (*dev->playerObj)->GetInterface(dev->playerObj, SL_IID_PLAY, &dev->player); + if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } + r = (*dev->playerObj)->GetInterface(dev->playerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &dev->bufferQueue); + if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } + r = (*dev->bufferQueue)->RegisterCallback(dev->bufferQueue, buffer_callback, dev); + if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } + + fprintf(stderr, "jsound: nOpen id=%d %dHz %dch %dbit buf=%d\n", id, dev->sample_rate, channels, sampleSize, bufferSize); + return (jlong)(intptr_t)dev; +} + +JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nStart(JNIEnv *env, jobject obj, jlong id) { + (void)env; (void)obj; + PcmDevice *dev = (PcmDevice*)(intptr_t)id; + if (!dev || !dev->in_use) return; + if (dev->player) { + (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_PLAYING); + } + dev->state = 1; +} + +JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nStop(JNIEnv *env, jobject obj, jlong id) { + (void)env; (void)obj; + PcmDevice *dev = (PcmDevice*)(intptr_t)id; + if (!dev || !dev->in_use) return; + if (dev->player) { + (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_STOPPED); + } + dev->state = 0; +} + +JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nClose(JNIEnv *env, jobject obj, jlong id) { + (void)env; (void)obj; + PcmDevice *dev = (PcmDevice*)(intptr_t)id; + if (!dev || !dev->in_use) return; + if (dev->player) { + (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_STOPPED); + } + if (dev->playerObj) { + (*dev->playerObj)->Destroy(dev->playerObj); + } + free_device((int)((intptr_t)id)); +} + +JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nWrite(JNIEnv *env, jobject obj, jlong id, jbyteArray b, jint off, jint len) { + (void)obj; + PcmDevice *dev = (PcmDevice*)(intptr_t)id; + if (!dev || !dev->in_use || !dev->bufferQueue) return -1; + + if (dev->state == 0) { + (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_PLAYING); + dev->state = 1; + } + + jbyte *data = (*env)->GetByteArrayElements(env, b, NULL); + if (!data) return -1; + + SLresult r = (*dev->bufferQueue)->Enqueue(dev->bufferQueue, data + off, len); + (*env)->ReleaseByteArrayElements(env, b, data, JNI_ABORT); + + if (r != SL_RESULT_SUCCESS) return -1; + return len; +} + +JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nRead(JNIEnv *env, jobject obj, jlong id, jbyteArray b, jint off, jint len) { + (void)env; (void)obj; (void)id; (void)b; (void)off; (void)len; + return -1; +} + +JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nAvailable(JNIEnv *env, jobject obj, jlong id) { + (void)env; (void)obj; + PcmDevice *dev = (PcmDevice*)(intptr_t)id; + if (!dev || !dev->in_use) return -1; + return dev->buffer_size * dev->frame_size; +} + +JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nDrain(JNIEnv *env, jobject obj, jlong id) { + (void)env; (void)obj; + PcmDevice *dev = (PcmDevice*)(intptr_t)id; + if (!dev || !dev->in_use) return; + if (dev->player) { + (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_STOPPED); + } +} + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + (void)vm; (void)reserved; + g_engine.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + fprintf(stderr, "jsound: library loaded\n"); + return JNI_VERSION_1_6; +} \ No newline at end of file From 92dd9230680e5738fde278dbdefc059f300a8e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 18:18:22 +0800 Subject: [PATCH 09/18] Add jsound library target (custom libjsound.so) --- FCLauncher/src/main/jni/CMakeLists.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/FCLauncher/src/main/jni/CMakeLists.txt b/FCLauncher/src/main/jni/CMakeLists.txt index be3c17a64f..bcfd1809c7 100644 --- a/FCLauncher/src/main/jni/CMakeLists.txt +++ b/FCLauncher/src/main/jni/CMakeLists.txt @@ -203,4 +203,23 @@ target_link_libraries( log dl OpenSLES +) +add_library( + jsound + SHARED + jsound/jsound.c +) + +target_link_libraries( + jsound + PRIVATE + log + dl + OpenSLES +) + +target_link_options( + jsound + PRIVATE + -Wl,-soname,libjsound.so ) \ No newline at end of file From 5e42d94d3f78c1b958baf1bcbd376c07c7886723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 18:20:32 +0800 Subject: [PATCH 10/18] Preload custom libjsound.so for javax.sound support From 85157ed647d0baa6dbbbb11070d4c32d978d8893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 19:55:44 +0800 Subject: [PATCH 11/18] fix: add custom libjsound.so with OpenSL ES PortMixer implementation - Create jsound.c with full PortMixer JNI using OpenSL ES buffer queue - Add jsound target to CMakeLists.txt linking OpenSLES - Remove libjsound.so skip in locateLibs() in FCLauncher.java - Custom libjsound.so provides audio playback via OpenSL ES on Android - Fixes UnsatisfiedLinkError for javax.sound on JDK 25 without ALSA --- .../com/tungsten/fclauncher/FCLauncher.java | 52 +- FCLauncher/src/main/jni/CMakeLists.txt | 28 +- FCLauncher/src/main/jni/jsound/jsound.c | 536 +++++++++++------- 3 files changed, 352 insertions(+), 264 deletions(-) diff --git a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java index 04229095e1..e07023283e 100644 --- a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java +++ b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java @@ -1,4 +1,4 @@ -package com.tungsten.fclauncher; +package com.tungsten.fclauncher; import static android.content.Context.MODE_PRIVATE; import static com.tungsten.fclauncher.utils.Architecture.ARCH_X86; @@ -340,17 +340,16 @@ private static void addCustomEnv(FCLConfig config, HashMap envMa } - private static void setUpJavaRuntime(FCLConfig config, FCLBridge bridge) throws IOException { - printTaskTitle(bridge, "DLOPEN"); - // Load ALSA stub for Java Sound (libjsound.so) compatibility - // libjsound.so in Android JDK 25+ depends on libasound.so.2 which is not available on Android - try { - File alsaStub = new File(config.getContext().getApplicationInfo().nativeLibraryDir, "libalsa_stub.so"); - if (alsaStub.exists()) { - bridge.dlopen(alsaStub.getAbsolutePath()); - } - } catch (Exception ignored) { - } +private static void setUpJavaRuntime(FCLConfig config, FCLBridge bridge) throws IOException { + printTaskTitle(bridge, "DLOPEN"); + // Load ALSA stub for ALSA-dependent system libraries + try { + File alsaStub = new File(config.getContext().getApplicationInfo().nativeLibraryDir, "libalsa_stub.so"); + if (alsaStub.exists()) { + bridge.dlopen(alsaStub.getAbsolutePath()); + } +} catch (Exception ignored) { + } String javaLibDir = config.getJavaPath() + getJavaLibDir(config.getJavaPath()); String jliLibDir = new File(javaLibDir + "/jli/libjli.so").exists() ? javaLibDir + "/jli" : javaLibDir; if (isJDK8(config.getJavaPath())) @@ -372,22 +371,19 @@ private static void setUpJavaRuntime(FCLConfig config, FCLBridge bridge) throws } } - public static ArrayList locateLibs(File path) { - ArrayList returnValue = new ArrayList<>(); - File[] list = path.listFiles(); - if (list != null) { - for (File f : list) { - // Skip libjsound.so - it depends on libasound.so.2 (ALSA) which is not - // available on Android. The ALSA stub (libalsa_stub.so) provides the - // necessary symbols and is loaded separately in setUpJavaRuntime. - if (f.isFile() && f.getName().endsWith(".so") && !f.getName().equals("libjsound.so")) { - returnValue.add(f); - } else if (f.isDirectory()) { - returnValue.addAll(locateLibs(f)); - } - } - } - return returnValue; +public static ArrayList locateLibs(File path) { + ArrayList returnValue = new ArrayList<>(); + File[] list = path.listFiles(); + if (list != null) { + for (File f : list) { + if (f.isFile() && f.getName().endsWith(".so")) { + returnValue.add(f); + } else if (f.isDirectory()) { + returnValue.addAll(locateLibs(f)); + } + } + } + return returnValue; } private static void setupGraphicAndSoundEngine(FCLConfig config, FCLBridge bridge) { diff --git a/FCLauncher/src/main/jni/CMakeLists.txt b/FCLauncher/src/main/jni/CMakeLists.txt index bcfd1809c7..6ed403c8f8 100644 --- a/FCLauncher/src/main/jni/CMakeLists.txt +++ b/FCLauncher/src/main/jni/CMakeLists.txt @@ -2,24 +2,19 @@ cmake_minimum_required(VERSION 3.18.1) project("fcl") -# 设置C标准 set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 11) -# 查找ByteHook库 find_package(bytehook REQUIRED CONFIG) -# 设置全局包含目录 include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) -# 检查架构 if(${ANDROID_ABI} STREQUAL "arm64-v8a") add_definitions(-DADRENO_POSSIBLE) endif() -# 模块:fcl add_library( fcl SHARED @@ -44,7 +39,6 @@ target_link_libraries( android ) -# 模块:driver_helper add_library( driver_helper SHARED @@ -66,7 +60,6 @@ target_link_libraries( android ) -# 仅在arm64-v8a架构下链接EGL和GLESv2 if(${ANDROID_ABI} STREQUAL "arm64-v8a") target_link_libraries( driver_helper @@ -76,7 +69,6 @@ if(${ANDROID_ABI} STREQUAL "arm64-v8a") ) endif() -# 模块:linkerhook add_library( linkerhook SHARED @@ -90,22 +82,18 @@ target_link_options( -z global ) -# 模块:awt_headless - 占位库 add_library( awt_headless SHARED - # 空库,仅作为占位 - ${CMAKE_CURRENT_SOURCE_DIR}/awt_headless/placeholder.c + awt_headless/placeholder.c ) -# 为占位库添加必要的链接选项 target_link_libraries( awt_headless PRIVATE log ) -# 模块:awt_xawt add_library( awt_xawt SHARED @@ -124,7 +112,6 @@ target_link_libraries( awt_headless ) -# 模块:pojavexec_awt add_library( pojavexec_awt SHARED @@ -137,7 +124,6 @@ target_link_libraries( fcl ) -# 模块:pojavexec add_library( pojavexec SHARED @@ -175,7 +161,6 @@ target_link_libraries( android ) -# 仅在arm64-v8a架构下链接EGL和GLESv2 if(${ANDROID_ABI} STREQUAL "arm64-v8a") target_link_libraries( pojavexec @@ -202,8 +187,8 @@ target_link_libraries( PRIVATE log dl - OpenSLES ) + add_library( jsound SHARED @@ -213,13 +198,8 @@ add_library( target_link_libraries( jsound PRIVATE + OpenSLES log dl - OpenSLES -) - -target_link_options( - jsound - PRIVATE - -Wl,-soname,libjsound.so + android ) \ No newline at end of file diff --git a/FCLauncher/src/main/jni/jsound/jsound.c b/FCLauncher/src/main/jni/jsound/jsound.c index 55f27f93a5..53f96dae6a 100644 --- a/FCLauncher/src/main/jni/jsound/jsound.c +++ b/FCLauncher/src/main/jni/jsound/jsound.c @@ -1,277 +1,389 @@ -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include -#define MAX_DEVICES 8 -#define DEVICE_NAME "Android PCM Output" +#define BUFFER_COUNT 4 +#define BUFFER_SIZE 4096 +#define RING_SIZE (256 * 1024) typedef struct { SLObjectItf engineObj; SLEngineItf engine; SLObjectItf outputMixObj; - int initialized; + SLObjectItf playerObj; + SLPlayItf play; + SLBufferQueueItf queue; + + char ring[RING_SIZE]; + volatile int writePos; + volatile int readPos; + + char buffers[BUFFER_COUNT][BUFFER_SIZE]; + volatile int enqueueHead; + volatile int enqueueTail; + volatile int buffersInQueue; + pthread_mutex_t mutex; -} AudioEngine; + pthread_cond_t dataCond; + pthread_cond_t spaceCond; + pthread_cond_t drainCond; -typedef struct { - int in_use; - int channels; - int sample_rate; - int bits_per_sample; - int frame_size; - int buffer_size; - int state; - SLObjectItf playerObj; - SLPlayItf player; - SLAndroidSimpleBufferQueueItf bufferQueue; - AudioEngine *engine; -} PcmDevice; - -static AudioEngine g_engine = {0}; - -static pthread_mutex_t g_device_mutex = PTHREAD_MUTEX_INITIALIZER; -static PcmDevice g_devices[MAX_DEVICES]; - -static int init_engine(AudioEngine *e) { - pthread_mutex_lock(&e->mutex); - if (e->initialized) { pthread_mutex_unlock(&e->mutex); return 0; } - SLresult r; - r = slCreateEngine(&e->engineObj, 0, NULL, 0, NULL, NULL); - if (r != SL_RESULT_SUCCESS) { pthread_mutex_unlock(&e->mutex); return -1; } - r = (*e->engineObj)->Realize(e->engineObj, SL_BOOLEAN_FALSE); - if (r != SL_RESULT_SUCCESS) { (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; pthread_mutex_unlock(&e->mutex); return -1; } - r = (*e->engineObj)->GetInterface(e->engineObj, SL_IID_ENGINE, &e->engine); - if (r != SL_RESULT_SUCCESS) { (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; pthread_mutex_unlock(&e->mutex); return -1; } - const SLInterfaceID ids[] = { SL_IID_VOLUME }; - const SLboolean req[] = { SL_BOOLEAN_FALSE }; - r = (*e->engine)->CreateOutputMix(e->engine, &e->outputMixObj, 1, ids, req); - if (r != SL_RESULT_SUCCESS) { (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; e->engine = NULL; pthread_mutex_unlock(&e->mutex); return -1; } - r = (*e->outputMixObj)->Realize(e->outputMixObj, SL_BOOLEAN_FALSE); - if (r != SL_RESULT_SUCCESS) { (*e->outputMixObj)->Destroy(e->outputMixObj); e->outputMixObj = NULL; (*e->engineObj)->Destroy(e->engineObj); e->engineObj = NULL; e->engine = NULL; pthread_mutex_unlock(&e->mutex); return -1; } - e->initialized = 1; - fprintf(stderr, "jsound: OpenSL ES engine initialized\n"); - pthread_mutex_unlock(&e->mutex); - return 0; + volatile int running; + volatile int draining; + volatile int drained; +} AudioCtx; + +static SLObjectItf gEngineObj = NULL; +static SLEngineItf gEngine = NULL; +static int gRefCount = 0; + +static int minInt(int a, int b) { return a < b ? a : b; } + +static int ringAvailable(AudioCtx *ctx) { + return (ctx->readPos - ctx->writePos - 1 + RING_SIZE) % RING_SIZE; } -static void buffer_callback(SLAndroidSimpleBufferQueueItf bq, void *context) { - (void)bq; (void)context; +static int ringUsed(AudioCtx *ctx) { + return (ctx->writePos - ctx->readPos + RING_SIZE) % RING_SIZE; } -static int alloc_device(void) { - for (int i = 0; i < MAX_DEVICES; i++) { - if (!g_devices[i].in_use) { - memset(&g_devices[i], 0, sizeof(PcmDevice)); - g_devices[i].in_use = 1; - return i; - } +static void ringWrite(AudioCtx *ctx, const char *src, int len) { + int part = minInt(len, RING_SIZE - ctx->writePos); + memcpy(ctx->ring + ctx->writePos, src, part); + if (part < len) { + memcpy(ctx->ring, src + part, len - part); } - return -1; + ctx->writePos = (ctx->writePos + len) % RING_SIZE; } -static void free_device(int id) { - if (id >= 0 && id < MAX_DEVICES) { - g_devices[id].in_use = 0; +static void ringRead(AudioCtx *ctx, char *dst, int len) { + int part = minInt(len, RING_SIZE - ctx->readPos); + memcpy(dst, ctx->ring + ctx->readPos, part); + if (part < len) { + memcpy(dst + part, ctx->ring, len - part); } + ctx->readPos = (ctx->readPos + len) % RING_SIZE; } -#define MIXER_TYPE_SOURCE 2 -#define MIXER_TYPE_TARGET 1 +static void bufferQueueCallback(SLBufferQueueItf caller, void *context, SLuint32 size) { + AudioCtx *ctx = (AudioCtx*)context; + int enqueueIdx = -1; -static jclass audioFormatClass = NULL; -static jclass encodingClass = NULL; -static jobject pcmSignedEncoding = NULL; + pthread_mutex_lock(&ctx->mutex); -static int init_jni_refs(JNIEnv *env) { - if (audioFormatClass) return 0; - jclass cls = (*env)->FindClass(env, "javax/sound/sampled/AudioFormat"); - if (!cls) return -1; - audioFormatClass = (jclass)(*env)->NewGlobalRef(env, cls); + if (ctx->buffersInQueue > 0) { + ctx->buffersInQueue--; + ctx->enqueueHead = (ctx->enqueueHead + 1) % BUFFER_COUNT; + } - jclass encCls = (*env)->FindClass(env, "javax/sound/sampled/AudioFormat$Encoding"); - if (!encCls) return -1; - encodingClass = (jclass)(*env)->NewGlobalRef(env, encCls); + if (ringUsed(ctx) >= BUFFER_SIZE) { + enqueueIdx = ctx->enqueueTail; + ringRead(ctx, ctx->buffers[enqueueIdx], BUFFER_SIZE); + ctx->enqueueTail = (ctx->enqueueTail + 1) % BUFFER_COUNT; + ctx->buffersInQueue++; + pthread_cond_signal(&ctx->spaceCond); + } else if (ctx->draining) { + if (ringUsed(ctx) == 0 && ctx->buffersInQueue == 0) { + ctx->drained = 1; + pthread_cond_signal(&ctx->drainCond); + } + enqueueIdx = ctx->enqueueTail; + memset(ctx->buffers[enqueueIdx], 0, BUFFER_SIZE); + ctx->enqueueTail = (ctx->enqueueTail + 1) % BUFFER_COUNT; + ctx->buffersInQueue++; + } else { + enqueueIdx = ctx->enqueueTail; + memset(ctx->buffers[enqueueIdx], 0, BUFFER_SIZE); + ctx->enqueueTail = (ctx->enqueueTail + 1) % BUFFER_COUNT; + ctx->buffersInQueue++; + } + pthread_mutex_unlock(&ctx->mutex); - jfieldID pcmSignedField = (*env)->GetStaticFieldID(env, encodingClass, "PCM_SIGNED", "Ljavax/sound/sampled/AudioFormat$Encoding;"); - if (pcmSignedField) { - jobject obj = (*env)->GetStaticObjectField(env, encodingClass, pcmSignedField); - if (obj) pcmSignedEncoding = (*env)->NewGlobalRef(env, obj); + if (enqueueIdx >= 0) { + (*caller)->Enqueue(caller, ctx->buffers[enqueueIdx], BUFFER_SIZE); } - return 0; } -JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nGetNumDevices(JNIEnv *env, jclass cls, jint mixerType) { - (void)env; (void)cls; - if (mixerType == MIXER_TYPE_SOURCE) return 1; - return 0; -} +static int initOpenSL(AudioCtx *ctx) { + SLresult result; -JNIEXPORT jobjectArray JNICALL Java_com_sun_media_sound_PortMixer_nGetDeviceDescription(JNIEnv *env, jclass cls, jint mixerType, jint deviceID) { - (void)cls; - if (mixerType != MIXER_TYPE_SOURCE || deviceID != 0) return NULL; - jclass stringClass = (*env)->FindClass(env, "java/lang/String"); - if (!stringClass) return NULL; - jobjectArray arr = (*env)->NewObjectArray(env, 4, stringClass, NULL); - if (!arr) return NULL; - (*env)->SetObjectArrayElement(env, arr, 0, (*env)->NewStringUTF(env, "Android PCM Output")); - (*env)->SetObjectArrayElement(env, arr, 1, (*env)->NewStringUTF(env, "FCL-Team")); - (*env)->SetObjectArrayElement(env, arr, 2, (*env)->NewStringUTF(env, "Android PCM audio via OpenSL ES")); - (*env)->SetObjectArrayElement(env, arr, 3, (*env)->NewStringUTF(env, "1.0")); - return arr; -} + pthread_mutex_lock(&ctx->mutex); + if (gEngineObj == NULL) { + result = slCreateEngine(&gEngineObj, 0, NULL, 0, NULL, NULL); + if (result != SL_RESULT_SUCCESS) { + pthread_mutex_unlock(&ctx->mutex); + return 0; + } + result = (*gEngineObj)->Realize(gEngineObj, SL_BOOLEAN_FALSE); + if (result != SL_RESULT_SUCCESS) { + (*gEngineObj)->Destroy(gEngineObj); + gEngineObj = NULL; + pthread_mutex_unlock(&ctx->mutex); + return 0; + } + result = (*gEngineObj)->GetInterface(gEngineObj, SL_IID_ENGINE, &gEngine); + if (result != SL_RESULT_SUCCESS) { + (*gEngineObj)->Destroy(gEngineObj); + gEngineObj = NULL; + pthread_mutex_unlock(&ctx->mutex); + return 0; + } + } + gRefCount++; + ctx->engineObj = gEngineObj; + ctx->engine = gEngine; + pthread_mutex_unlock(&ctx->mutex); + + result = (*ctx->engine)->CreateOutputMix(ctx->engine, &ctx->outputMixObj, 0, NULL, NULL); + if (result != SL_RESULT_SUCCESS) return 0; + result = (*ctx->outputMixObj)->Realize(ctx->outputMixObj, SL_BOOLEAN_FALSE); + if (result != SL_RESULT_SUCCESS) return 0; + + SLDataLocator_AndroidSimpleBufferQueue locBufQueue = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, BUFFER_COUNT + }; + SLDataFormat_PCM formatPcm = { + SL_DATAFORMAT_PCM, + 2, + SL_SAMPLINGRATE_44_1, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, + SL_BYTEORDER_LITTLEENDIAN + }; + SLDataSource audioSrc = { &locBufQueue, &formatPcm }; + + SLDataLocator_OutputMix locOutMix = { + SL_DATALOCATOR_OUTPUTMIX, ctx->outputMixObj + }; + SLDataSink audioSnk = { &locOutMix, NULL }; + + SLInterfaceID ids[] = { SL_IID_BUFFERQUEUE }; + SLboolean req[] = { SL_BOOLEAN_TRUE }; -JNIEXPORT jlong JNICALL Java_com_sun_media_sound_PortMixer_nOpen(JNIEnv *env, jobject obj, jint mixerType, jint deviceID, jobject format, jint bufferSize) { - (void)obj; - if (mixerType != MIXER_TYPE_SOURCE || deviceID != 0) return -1; - if (!g_engine.initialized && init_engine(&g_engine) != 0) return -1; - if (init_jni_refs(env) != 0) return -1; + result = (*ctx->engine)->CreateAudioPlayer(ctx->engine, &ctx->playerObj, + &audioSrc, &audioSnk, 1, ids, req); + if (result != SL_RESULT_SUCCESS) return 0; + result = (*ctx->playerObj)->Realize(ctx->playerObj, SL_BOOLEAN_FALSE); + if (result != SL_RESULT_SUCCESS) return 0; - int id = alloc_device(); - if (id < 0) return -1; - PcmDevice *dev = &g_devices[id]; + result = (*ctx->playerObj)->GetInterface(ctx->playerObj, SL_IID_PLAY, &ctx->play); + if (result != SL_RESULT_SUCCESS) return 0; - jclass fmtCls = (*env)->GetObjectClass(env, format); + result = (*ctx->playerObj)->GetInterface(ctx->playerObj, SL_IID_BUFFERQUEUE, &ctx->queue); + if (result != SL_RESULT_SUCCESS) return 0; - jmethodID getEncoding = (*env)->GetMethodID(env, fmtCls, "getEncoding", "()Ljavax/sound/sampled/AudioFormat$Encoding;"); - jmethodID getSampleRate = (*env)->GetMethodID(env, fmtCls, "getSampleRate", "()F"); - jmethodID getSampleSize = (*env)->GetMethodID(env, fmtCls, "getSampleSizeInBits", "()I"); - jmethodID getChannels = (*env)->GetMethodID(env, fmtCls, "getChannels", "()I"); - jmethodID getFrameSize = (*env)->GetMethodID(env, fmtCls, "getFrameSize", "()I"); - jmethodID isBigEndian = (*env)->GetMethodID(env, fmtCls, "isBigEndian", "()Z"); + result = (*ctx->queue)->RegisterCallback(ctx->queue, bufferQueueCallback, ctx); + if (result != SL_RESULT_SUCCESS) return 0; - if (!getEncoding || !getSampleRate || !getSampleSize || !getChannels) { - free_device(id); return -1; + int i; + for (i = 0; i < BUFFER_COUNT; i++) { + memset(ctx->buffers[ctx->enqueueTail], 0, BUFFER_SIZE); + result = (*ctx->queue)->Enqueue(ctx->queue, ctx->buffers[ctx->enqueueTail], BUFFER_SIZE); + if (result != SL_RESULT_SUCCESS) break; + ctx->enqueueTail = (ctx->enqueueTail + 1) % BUFFER_COUNT; + ctx->buffersInQueue++; } - jfloat sampleRate = 44100.0f; - if (getSampleRate) sampleRate = (*env)->CallFloatMethod(env, format, getSampleRate); - int sampleSize = 16; - if (getSampleSize) sampleSize = (*env)->CallIntMethod(env, format, getSampleSize); - int channels = 2; - if (getChannels) channels = (*env)->CallIntMethod(env, format, getChannels); - int frameSize = (sampleSize / 8) * channels; - if (getFrameSize) frameSize = (*env)->CallIntMethod(env, format, getFrameSize); - - dev->channels = channels; - dev->sample_rate = (int)sampleRate; - dev->bits_per_sample = sampleSize; - dev->frame_size = frameSize; - dev->buffer_size = bufferSize > 0 ? bufferSize : 4096; - dev->state = 0; - dev->engine = &g_engine; - - SLDataFormat_PCM fmt; - fmt.formatType = SL_DATAFORMAT_PCM; - fmt.numChannels = channels; - fmt.samplesPerSec = (SLuint32)(sampleRate * 1000); - fmt.bitsPerSample = sampleSize; - fmt.containerSize = sampleSize; - fmt.channelMask = (channels == 1) ? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - fmt.endianness = SL_BYTEORDER_LITTLEENDIAN; - - SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 }; - SLDataSource audioSrc = { &loc_bufq, &fmt }; - SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, g_engine.outputMixObj }; - SLDataSink audioSink = { &loc_outmix, NULL }; - SLInterfaceID ids[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; - SLboolean req[] = { SL_BOOLEAN_TRUE }; + result = (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PLAYING); + if (result != SL_RESULT_SUCCESS) return 0; - SLresult r = (*g_engine.engine)->CreateAudioPlayer(g_engine.engine, &dev->playerObj, &audioSrc, &audioSink, 1, ids, req); - if (r != SL_RESULT_SUCCESS) { free_device(id); return -1; } - r = (*dev->playerObj)->Realize(dev->playerObj, SL_BOOLEAN_FALSE); - if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } - r = (*dev->playerObj)->GetInterface(dev->playerObj, SL_IID_PLAY, &dev->player); - if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } - r = (*dev->playerObj)->GetInterface(dev->playerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &dev->bufferQueue); - if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } - r = (*dev->bufferQueue)->RegisterCallback(dev->bufferQueue, buffer_callback, dev); - if (r != SL_RESULT_SUCCESS) { (*dev->playerObj)->Destroy(dev->playerObj); free_device(id); return -1; } - - fprintf(stderr, "jsound: nOpen id=%d %dHz %dch %dbit buf=%d\n", id, dev->sample_rate, channels, sampleSize, bufferSize); - return (jlong)(intptr_t)dev; + return 1; } -JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nStart(JNIEnv *env, jobject obj, jlong id) { - (void)env; (void)obj; - PcmDevice *dev = (PcmDevice*)(intptr_t)id; - if (!dev || !dev->in_use) return; - if (dev->player) { - (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_PLAYING); +static void shutdownOpenSL(AudioCtx *ctx) { + if (ctx->play) + (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_STOPPED); + if (ctx->queue) + (*ctx->queue)->Clear(ctx->queue); + if (ctx->playerObj) { + (*ctx->playerObj)->Destroy(ctx->playerObj); + ctx->playerObj = NULL; + } + if (ctx->outputMixObj) { + (*ctx->outputMixObj)->Destroy(ctx->outputMixObj); + ctx->outputMixObj = NULL; } - dev->state = 1; -} -JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nStop(JNIEnv *env, jobject obj, jlong id) { - (void)env; (void)obj; - PcmDevice *dev = (PcmDevice*)(intptr_t)id; - if (!dev || !dev->in_use) return; - if (dev->player) { - (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_STOPPED); + pthread_mutex_lock(&ctx->mutex); + gRefCount--; + if (gRefCount == 0 && gEngineObj != NULL) { + (*gEngineObj)->Destroy(gEngineObj); + gEngineObj = NULL; + gEngine = NULL; } - dev->state = 0; + pthread_mutex_unlock(&ctx->mutex); } -JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nClose(JNIEnv *env, jobject obj, jlong id) { - (void)env; (void)obj; - PcmDevice *dev = (PcmDevice*)(intptr_t)id; - if (!dev || !dev->in_use) return; - if (dev->player) { - (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_STOPPED); - } - if (dev->playerObj) { - (*dev->playerObj)->Destroy(dev->playerObj); - } - free_device((int)((intptr_t)id)); +static AudioCtx* createCtx(void) { + AudioCtx *ctx = (AudioCtx*)calloc(1, sizeof(AudioCtx)); + if (!ctx) return NULL; + ctx->ringBufferSize = RING_SIZE; + ctx->writePos = 0; + ctx->readPos = 0; + ctx->buffersInQueue = 0; + ctx->enqueueHead = 0; + ctx->enqueueTail = 0; + ctx->running = 1; + ctx->draining = 0; + ctx->drained = 0; + ctx->engineObj = NULL; + ctx->engine = NULL; + ctx->outputMixObj = NULL; + ctx->playerObj = NULL; + ctx->play = NULL; + ctx->queue = NULL; + pthread_mutex_init(&ctx->mutex, NULL); + pthread_cond_init(&ctx->dataCond, NULL); + pthread_cond_init(&ctx->spaceCond, NULL); + pthread_cond_init(&ctx->drainCond, NULL); + return ctx; +} + +static void destroyCtx(AudioCtx *ctx) { + if (!ctx) return; + shutdownOpenSL(ctx); + pthread_mutex_destroy(&ctx->mutex); + pthread_cond_destroy(&ctx->dataCond); + pthread_cond_destroy(&ctx->spaceCond); + pthread_cond_destroy(&ctx->drainCond); + free(ctx); +} + +static jint JNICALL nGetNumDevices(JNIEnv *env, jclass clazz, jint type) { + return (type == 0) ? 1 : 0; } -JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nWrite(JNIEnv *env, jobject obj, jlong id, jbyteArray b, jint off, jint len) { - (void)obj; - PcmDevice *dev = (PcmDevice*)(intptr_t)id; - if (!dev || !dev->in_use || !dev->bufferQueue) return -1; +static jstring JNICALL nGetDeviceDescription(JNIEnv *env, jclass clazz, jint mixerIndex, jint deviceID) { + return (*env)->NewStringUTF(env, "Android OpenSL ES Audio"); +} - if (dev->state == 0) { - (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_PLAYING); - dev->state = 1; +static jlong JNICALL nOpen(JNIEnv *env, jclass clazz, jint mixerIndex, jint deviceID) { + AudioCtx *ctx = createCtx(); + if (!ctx) return 0; + if (!initOpenSL(ctx)) { + destroyCtx(ctx); + return 0; } + return (jlong)(intptr_t)ctx; +} + +static jint JNICALL nStart(JNIEnv *env, jobject thiz, jlong id) { + AudioCtx *ctx = (AudioCtx*)(intptr_t)id; + if (!ctx || !ctx->play) return -1; + ctx->running = 1; + SLresult result = (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PLAYING); + return (result == SL_RESULT_SUCCESS) ? 0 : -1; +} + +static jint JNICALL nStop(JNIEnv *env, jobject thiz, jlong id) { + AudioCtx *ctx = (AudioCtx*)(intptr_t)id; + if (!ctx || !ctx->play) return -1; + ctx->running = 0; + SLresult result = (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PAUSED); + return (result == SL_RESULT_SUCCESS) ? 0 : -1; +} + +static jint JNICALL nClose(JNIEnv *env, jobject thiz, jlong id) { + AudioCtx *ctx = (AudioCtx*)(intptr_t)id; + if (!ctx) return 0; + ctx->running = 0; + destroyCtx(ctx); + return 0; +} + +static jint JNICALL nWrite(JNIEnv *env, jobject thiz, jlong id, jbyteArray b, jint off, jint len) { + AudioCtx *ctx = (AudioCtx*)(intptr_t)id; + if (!ctx || !b) return -1; jbyte *data = (*env)->GetByteArrayElements(env, b, NULL); if (!data) return -1; - SLresult r = (*dev->bufferQueue)->Enqueue(dev->bufferQueue, data + off, len); - (*env)->ReleaseByteArrayElements(env, b, data, JNI_ABORT); + int written = 0; + while (written < len) { + pthread_mutex_lock(&ctx->mutex); + int avail = ringAvailable(ctx); + if (avail == 0) { + pthread_mutex_unlock(&ctx->mutex); + break; + } + int chunk = minInt(len - written, avail); + ringWrite(ctx, (const char*)(data + off + written), chunk); + written += chunk; + + while (ctx->buffersInQueue < BUFFER_COUNT && ringUsed(ctx) >= BUFFER_SIZE) { + int idx = ctx->enqueueTail; + ringRead(ctx, ctx->buffers[idx], BUFFER_SIZE); + (*ctx->queue)->Enqueue(ctx->queue, ctx->buffers[idx], BUFFER_SIZE); + ctx->enqueueTail = (ctx->enqueueTail + 1) % BUFFER_COUNT; + ctx->buffersInQueue++; + } + pthread_mutex_unlock(&ctx->mutex); + } - if (r != SL_RESULT_SUCCESS) return -1; - return len; + (*env)->ReleaseByteArrayElements(env, b, data, JNI_ABORT); + return written; } -JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nRead(JNIEnv *env, jobject obj, jlong id, jbyteArray b, jint off, jint len) { - (void)env; (void)obj; (void)id; (void)b; (void)off; (void)len; +static jint JNICALL nRead(JNIEnv *env, jobject thiz, jlong id, jbyteArray b, jint off, jint len) { return -1; } -JNIEXPORT jint JNICALL Java_com_sun_media_sound_PortMixer_nAvailable(JNIEnv *env, jobject obj, jlong id) { - (void)env; (void)obj; - PcmDevice *dev = (PcmDevice*)(intptr_t)id; - if (!dev || !dev->in_use) return -1; - return dev->buffer_size * dev->frame_size; +static jint JNICALL nAvailable(JNIEnv *env, jobject thiz, jlong id, jboolean isWrite) { + AudioCtx *ctx = (AudioCtx*)(intptr_t)id; + if (!ctx) return 0; + if (!isWrite) return 0; + pthread_mutex_lock(&ctx->mutex); + int avail = ringAvailable(ctx); + pthread_mutex_unlock(&ctx->mutex); + return avail; } -JNIEXPORT void JNICALL Java_com_sun_media_sound_PortMixer_nDrain(JNIEnv *env, jobject obj, jlong id) { - (void)env; (void)obj; - PcmDevice *dev = (PcmDevice*)(intptr_t)id; - if (!dev || !dev->in_use) return; - if (dev->player) { - (*dev->player)->SetPlayState(dev->player, SL_PLAYSTATE_STOPPED); +static jint JNICALL nDrain(JNIEnv *env, jobject thiz, jlong id) { + AudioCtx *ctx = (AudioCtx*)(intptr_t)id; + if (!ctx) return 0; + pthread_mutex_lock(&ctx->mutex); + ctx->draining = 1; + while (!ctx->drained && (ringUsed(ctx) > 0 || ctx->buffersInQueue > 0)) { + pthread_cond_wait(&ctx->drainCond, &ctx->mutex); } + ctx->draining = 0; + ctx->drained = 0; + pthread_mutex_unlock(&ctx->mutex); + return 0; } +static JNINativeMethod methods[] = { + {"nGetNumDevices", "(I)I", (void*)nGetNumDevices}, + {"nGetDeviceDescription", "(II)Ljava/lang/String;", (void*)nGetDeviceDescription}, + {"nOpen", "(II)J", (void*)nOpen}, + {"nStart", "(J)I", (void*)nStart}, + {"nStop", "(J)I", (void*)nStop}, + {"nClose", "(J)I", (void*)nClose}, + {"nWrite", "(J[BII)I", (void*)nWrite}, + {"nRead", "(J[BII)I", (void*)nRead}, + {"nAvailable", "(JZ)I", (void*)nAvailable}, + {"nDrain", "(J)I", (void*)nDrain}, +}; + JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { - (void)vm; (void)reserved; - g_engine.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - fprintf(stderr, "jsound: library loaded\n"); + JNIEnv *env; + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) + return JNI_ERR; + + jclass clazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixer"); + if (!clazz) { + clazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixerProvider"); + if (!clazz) + return JNI_ERR; + } + + if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods) / sizeof(methods[0])) != JNI_OK) + return JNI_ERR; + return JNI_VERSION_1_6; } \ No newline at end of file From 9cac96d264e822d4018184c6999d2e300221c0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 20:09:21 +0800 Subject: [PATCH 12/18] fix: remove BOM from FCLauncher.java --- .../src/main/java/com/tungsten/fclauncher/FCLauncher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java index e07023283e..d71fecc969 100644 --- a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java +++ b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java @@ -1,4 +1,4 @@ -package com.tungsten.fclauncher; +package com.tungsten.fclauncher; import static android.content.Context.MODE_PRIVATE; import static com.tungsten.fclauncher.utils.Architecture.ARCH_X86; From 8a3977c525dd3a3f148a7f6f2bdcb000c41bf735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 21:05:03 +0800 Subject: [PATCH 13/18] fix: correct callback signature and remove nonexistent field --- FCLauncher/src/main/jni/jsound/jsound.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FCLauncher/src/main/jni/jsound/jsound.c b/FCLauncher/src/main/jni/jsound/jsound.c index 53f96dae6a..4632e16a90 100644 --- a/FCLauncher/src/main/jni/jsound/jsound.c +++ b/FCLauncher/src/main/jni/jsound/jsound.c @@ -69,7 +69,7 @@ static void ringRead(AudioCtx *ctx, char *dst, int len) { ctx->readPos = (ctx->readPos + len) % RING_SIZE; } -static void bufferQueueCallback(SLBufferQueueItf caller, void *context, SLuint32 size) { +static void bufferQueueCallback(SLBufferQueueItf caller, void *context) { AudioCtx *ctx = (AudioCtx*)context; int enqueueIdx = -1; @@ -222,7 +222,6 @@ static void shutdownOpenSL(AudioCtx *ctx) { static AudioCtx* createCtx(void) { AudioCtx *ctx = (AudioCtx*)calloc(1, sizeof(AudioCtx)); if (!ctx) return NULL; - ctx->ringBufferSize = RING_SIZE; ctx->writePos = 0; ctx->readPos = 0; ctx->buffersInQueue = 0; From 69d38f7a0c7dbac811719f5117f733a738f03481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 22:06:03 +0800 Subject: [PATCH 14/18] fix: replace alsa_stub.c with simple stub (no OpenSL ES), add OpenSLES to alsa_stub target --- FCLauncher/src/main/jni/CMakeLists.txt | 1 + FCLauncher/src/main/jni/alsa_stub/alsa_stub.c | 343 +++--------------- 2 files changed, 52 insertions(+), 292 deletions(-) diff --git a/FCLauncher/src/main/jni/CMakeLists.txt b/FCLauncher/src/main/jni/CMakeLists.txt index 6ed403c8f8..913328c4ee 100644 --- a/FCLauncher/src/main/jni/CMakeLists.txt +++ b/FCLauncher/src/main/jni/CMakeLists.txt @@ -185,6 +185,7 @@ target_link_options( target_link_libraries( alsa_stub PRIVATE + OpenSLES log dl ) diff --git a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c index 15962d72ac..f5796e7454 100644 --- a/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c +++ b/FCLauncher/src/main/jni/alsa_stub/alsa_stub.c @@ -2,9 +2,6 @@ #include #include #include -#include -#include -#include #define FALSE 0 #define TRUE 1 @@ -42,57 +39,16 @@ typedef struct _snd_rawmidi snd_rawmidi_t; #define SND_PCM_STATE_PAUSED 6 #define SND_PCM_STATE_DISCONNECTED 8 -typedef struct { - unsigned int device; unsigned int subdevice; int stream; int card; +typedef struct { unsigned int device; unsigned int subdevice; int stream; int card; unsigned int subdevices_count; unsigned int subdevices_avail; - char id[64]; char name[256]; char subname[256]; -} snd_pcm_info_t; - + char id[64]; char name[256]; char subname[256]; } snd_pcm_info_t; typedef struct { char id[64]; char name[256]; char driver[64]; } snd_ctl_card_info_t; - -typedef struct { - unsigned int rate; unsigned int channels; unsigned int buffer_frames; - unsigned int period_frames; unsigned int periods; - snd_pcm_format_t format; snd_pcm_access_t access; - int bits; int sbits; int signed_fmt; int big_endian; int linear; -} snd_pcm_hw_params_t; - +typedef struct { unsigned int rate; unsigned int channels; unsigned int buffer_frames; + unsigned int period_frames; unsigned int periods; snd_pcm_format_t format; + snd_pcm_access_t access; int bits; int sbits; int signed_fmt; int big_endian; int linear; } snd_pcm_hw_params_t; typedef struct { unsigned int flags; } snd_pcm_sw_params_t; -struct _snd_pcm { - int stream; int state; char name[256]; - snd_pcm_hw_params_t hw; - SLObjectItf engineObj; SLEngineItf engine; - SLObjectItf outputMixObj; SLObjectItf playerObj; - SLPlayItf player; SLAndroidSimpleBufferQueueItf bufferQueue; - short *buffer; int buffer_size; int period_frames; - int frame_size; int write_pos; int play_pos; int buffered_frames; - pthread_mutex_t mutex; int engine_initialized; -}; - -static int g_engine_initialized = 0; -static SLObjectItf g_engineObj = NULL; -static SLEngineItf g_engine = NULL; -static SLObjectItf g_outputMixObj = NULL; -static pthread_mutex_t g_engine_mutex = PTHREAD_MUTEX_INITIALIZER; - -typedef struct { - snd_pcm_format_t alsa_fmt; int bits; int sbits; - int signed_fmt; int big_endian; int linear; -} format_entry_t; - -static format_entry_t format_table[] = { - { SND_PCM_FORMAT_U8, 8, 8, 0, 0, 1 }, - { SND_PCM_FORMAT_S16_LE, 16, 16, 1, 0, 1 }, - { SND_PCM_FORMAT_S32_LE, 32, 32, 1, 0, 1 }, - { -1, 0, 0, 0, 0, 0 }, -}; - -static format_entry_t* find_format(snd_pcm_format_t fmt) { - for (int i = 0; format_table[i].alsa_fmt >= 0; i++) - if (format_table[i].alsa_fmt == fmt) return &format_table[i]; - return NULL; -} +struct _snd_pcm { int stream; int state; char name[256]; snd_pcm_hw_params_t hw; }; static const char* alsa_error_str(int err) { switch (err) { @@ -110,111 +66,27 @@ static const char* alsa_error_str(int err) { } } -static int init_engine(void) { - pthread_mutex_lock(&g_engine_mutex); - if (g_engine_initialized) { pthread_mutex_unlock(&g_engine_mutex); return 0; } - SLresult r; - r = slCreateEngine(&g_engineObj, 0, NULL, 0, NULL, NULL); - if (r != SL_RESULT_SUCCESS) { pthread_mutex_unlock(&g_engine_mutex); return -5; } - r = (*g_engineObj)->Realize(g_engineObj, SL_BOOLEAN_FALSE); - if (r != SL_RESULT_SUCCESS) { (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } - r = (*g_engineObj)->GetInterface(g_engineObj, SL_IID_ENGINE, &g_engine); - if (r != SL_RESULT_SUCCESS) { (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } - const SLInterfaceID ids[] = { SL_IID_VOLUME }; - const SLboolean req[] = { SL_BOOLEAN_FALSE }; - r = (*g_engine)->CreateOutputMix(g_engine, &g_outputMixObj, 1, ids, req); - if (r != SL_RESULT_SUCCESS) { (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; g_engine = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } - r = (*g_outputMixObj)->Realize(g_outputMixObj, SL_BOOLEAN_FALSE); - if (r != SL_RESULT_SUCCESS) { (*g_outputMixObj)->Destroy(g_outputMixObj); g_outputMixObj = NULL; (*g_engineObj)->Destroy(g_engineObj); g_engineObj = NULL; g_engine = NULL; pthread_mutex_unlock(&g_engine_mutex); return -5; } - g_engine_initialized = 1; - fprintf(stderr, "ALSA stub: OpenSL ES engine initialized\n"); - pthread_mutex_unlock(&g_engine_mutex); - return 0; -} - -static void buffer_queue_callback(SLAndroidSimpleBufferQueueItf bq, void *context) { - (void)bq; (void)context; -} - -static int pcm_setup_player(snd_pcm_t *pcm) { - if (pcm->playerObj) { (*pcm->playerObj)->Destroy(pcm->playerObj); pcm->playerObj = NULL; pcm->player = NULL; pcm->bufferQueue = NULL; } - format_entry_t *fe = find_format(pcm->hw.format); - if (!fe) return -22; - pcm->frame_size = (pcm->hw.channels * fe->bits) / 8; - if (pcm->frame_size == 0) pcm->frame_size = 2; - pcm->buffer_size = pcm->hw.buffer_frames; - pcm->period_frames = pcm->hw.period_frames; - SLDataFormat_PCM fmt; - fmt.formatType = SL_DATAFORMAT_PCM; - fmt.numChannels = pcm->hw.channels; - fmt.samplesPerSec = pcm->hw.rate * 1000; - fmt.bitsPerSample = fe->bits; - fmt.containerSize = fe->bits; - fmt.channelMask = (pcm->hw.channels == 1) ? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - fmt.endianness = fe->big_endian ? SL_BYTEORDER_BIGENDIAN : SL_BYTEORDER_LITTLEENDIAN; - SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 }; - SLDataSource audioSrc = { &loc_bufq, &fmt }; - SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, g_outputMixObj }; - SLDataSink audioSink = { &loc_outmix, NULL }; - SLInterfaceID ids[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; - SLboolean req[] = { SL_BOOLEAN_TRUE }; - SLresult r = (*g_engine)->CreateAudioPlayer(g_engine, &pcm->playerObj, &audioSrc, &audioSink, 1, ids, req); - if (r != SL_RESULT_SUCCESS) return -5; - r = (*pcm->playerObj)->Realize(pcm->playerObj, SL_BOOLEAN_FALSE); - if (r != SL_RESULT_SUCCESS) { (*pcm->playerObj)->Destroy(pcm->playerObj); pcm->playerObj = NULL; return -5; } - r = (*pcm->playerObj)->GetInterface(pcm->playerObj, SL_IID_PLAY, &pcm->player); - if (r != SL_RESULT_SUCCESS) return -5; - r = (*pcm->playerObj)->GetInterface(pcm->playerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pcm->bufferQueue); - if (r != SL_RESULT_SUCCESS) return -5; - r = (*pcm->bufferQueue)->RegisterCallback(pcm->bufferQueue, buffer_queue_callback, pcm); - if (r != SL_RESULT_SUCCESS) return -5; - return 0; -} - int snd_strerror(int err) { return -1; } const char* snd_strerror_pointer(int err) { return alsa_error_str(err); } typedef void (*snd_lib_error_handler_t)(const char*,int,const char*,int,const char*,...); snd_lib_error_handler_t snd_lib_error_set_handler(snd_lib_error_handler_t h) { return NULL; } int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) { - if (!pcm) return -22; - if (!g_engine_initialized) { int ret = init_engine(); if (ret) { *pcm = NULL; return ret; } } - snd_pcm_t *p = (snd_pcm_t*)calloc(1, sizeof(snd_pcm_t)); - if (!p) return -12; - pthread_mutex_init(&p->mutex, NULL); - p->stream = stream; p->state = SND_PCM_STATE_OPEN; - strncpy(p->name, name, sizeof(p->name) - 1); - p->hw.rate = 44100; p->hw.channels = 2; - p->hw.format = SND_PCM_FORMAT_S16_LE; - p->hw.access = SND_PCM_ACCESS_RW_INTERLEAVED; - p->hw.buffer_frames = 4096; p->hw.period_frames = 1024; - p->hw.periods = 4; p->hw.bits = 16; p->hw.sbits = 16; - p->hw.signed_fmt = 1; p->hw.linear = 1; - p->engine_initialized = 1; - *pcm = p; - fprintf(stderr, "ALSA stub: snd_pcm_open(%s, stream=%d) = %p\n", name, stream, (void*)p); - return 0; -} - -int snd_pcm_close(snd_pcm_t *pcm) { - if (!pcm) return 0; - if (pcm->playerObj) { - if (pcm->player) (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); - (*pcm->playerObj)->Destroy(pcm->playerObj); - } - if (pcm->buffer) free(pcm->buffer); - pthread_mutex_destroy(&pcm->mutex); - free(pcm); + (void)name; (void)stream; (void)mode; + *pcm = (snd_pcm_t*)calloc(1, sizeof(snd_pcm_t)); + if (!*pcm) return -12; + (*pcm)->stream = stream; + (*pcm)->state = SND_PCM_STATE_OPEN; return 0; } +int snd_pcm_close(snd_pcm_t *pcm) { free(pcm); return 0; } int snd_pcm_info(snd_pcm_t *pcm, snd_pcm_info_t *info) { if (!pcm || !info) return -22; - info->card = 0; info->device = 0; info->subdevice = 0; - info->stream = pcm->stream; info->subdevices_count = 1; info->subdevices_avail = 1; - strcpy(info->id, "default"); - snprintf(info->name, sizeof(info->name), "Android Audio (%s)", pcm->name); - strcpy(info->subname, ""); + memset(info, 0, sizeof(*info)); + info->card = 0; info->device = 0; info->stream = pcm->stream; + info->subdevices_count = 1; info->subdevices_avail = 1; + strcpy(info->id, "default"); snprintf(info->name, sizeof(info->name), "ALSA stub (%s)", pcm->name); return 0; } @@ -233,93 +105,28 @@ int snd_pcm_info_get_stream(const snd_pcm_info_t *obj) { return obj->stream; } int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t **ptr) { *ptr = (snd_pcm_hw_params_t*)calloc(1, sizeof(snd_pcm_hw_params_t)); return *ptr ? 0 : -12; } void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj) { free(obj); } - -int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { - if (!params) return -22; memcpy(params, &pcm->hw, sizeof(snd_pcm_hw_params_t)); return 0; -} - -int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access) { - if (!params) return -22; params->access = access; return 0; -} - -int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val) { - if (!params) return -22; - format_entry_t *fe = find_format(val); - if (!fe) return -22; - params->format = val; params->bits = fe->bits; params->sbits = fe->sbits; - params->signed_fmt = fe->signed_fmt; params->big_endian = fe->big_endian; params->linear = fe->linear; - return 0; -} - -int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { - if (!params) return -22; if (val < 1 || val > 2) return -22; params->channels = val; return 0; -} - -int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - if (!params || !val) return -22; - unsigned int rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000 }; - unsigned int best = rates[0]; int best_diff = abs((int)*val - (int)rates[0]); - for (int i = 1; i < 8; i++) { int d = abs((int)*val - (int)rates[i]); if (d < best_diff) { best_diff = d; best = rates[i]; } } - params->rate = best; *val = best; if (dir) *dir = 0; - return 0; -} - +int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { if (!pcm || !params) return -22; memcpy(params, &pcm->hw, sizeof(*params)); return 0; } +int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access) { if (params) params->access = access; return 0; } +int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val) { if (params) { params->format = val; params->bits = 16; params->sbits = 16; params->signed_fmt = 1; params->linear = 1; } return 0; } +int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { if (params) params->channels = val; return 0; } +int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (params && val) { params->rate = *val; if (dir) *dir = 0; } return 0; } int snd_pcm_hw_params_set_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val) { return 0; } - -int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - if (!params || !val) return -22; - if (*val < 2) *val = 2; if (*val > 8) *val = 8; - params->periods = *val; if (dir) *dir = 0; - return 0; -} - -int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { - if (!params || !val) return -22; params->buffer_frames = *val; return 0; -} - -int snd_pcm_hw_params_set_buffer_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - if (!params || !val) return -22; - params->buffer_frames = (*val * params->rate) / 1000000; if (dir) *dir = 0; - return 0; -} - -int snd_pcm_hw_params_set_period_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { - if (!params || !val) return -22; - params->period_frames = (*val * params->rate) / 1000000; - if (params->period_frames < 64) params->period_frames = 64; - if (params->period_frames > params->buffer_frames) params->period_frames = params->buffer_frames / 4; - if (dir) *dir = 0; - return 0; -} - -int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { - if (!pcm || !params) return -22; - memcpy(&pcm->hw, params, sizeof(snd_pcm_hw_params_t)); - pcm->state = SND_PCM_STATE_SETUP; - if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { int ret = pcm_setup_player(pcm); if (ret) return ret; } - int fs = (pcm->hw.channels * pcm->hw.bits) / 8; if (fs == 0) fs = 2; - pcm->frame_size = fs; pcm->buffer_size = pcm->hw.buffer_frames; - if (pcm->buffer) free(pcm->buffer); - pcm->buffer = (short*)calloc(pcm->buffer_size, fs); - if (!pcm->buffer) return -12; - fprintf(stderr, "ALSA stub: hw_params: %dHz %dch fmt=%d buf=%d period=%d\n", - pcm->hw.rate, pcm->hw.channels, pcm->hw.format, pcm->hw.buffer_frames, pcm->hw.period_frames); - return 0; -} - -int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { - if (!pcm || !params) return -22; memcpy(params, &pcm->hw, sizeof(snd_pcm_hw_params_t)); return 0; -} - -int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *a) { if (!params || !a) return -22; *a = params->access; return 0; } -int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params, snd_pcm_format_t *f) { if (!params || !f) return -22; *f = params->format; return 0; } -int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *v) { if (!params || !v) return -22; *v = params->channels; return 0; } -int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (!params || !v) return -22; *v = params->rate; if (d) *d = 0; return 0; } -int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *v, int *d) { if (!params || !v) return -22; *v = params->period_frames; if (d) *d = 0; return 0; } -int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *v) { if (!params || !v) return -22; *v = params->buffer_frames; return 0; } +int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (params && val) { params->periods = *val; if (dir) *dir = 0; } return 0; } +int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val) { if (params && val) params->buffer_frames = *val; return 0; } +int snd_pcm_hw_params_set_buffer_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (params && val) { params->buffer_frames = (*val * params->rate) / 1000000; if (dir) *dir = 0; } return 0; } +int snd_pcm_hw_params_set_period_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) { if (params && val) { params->period_frames = (*val * params->rate) / 1000000; if (dir) *dir = 0; } return 0; } +int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { if (!pcm || !params) return -22; memcpy(&pcm->hw, params, sizeof(*params)); pcm->state = SND_PCM_STATE_SETUP; return 0; } +int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { if (!pcm || !params) return -22; memcpy(params, &pcm->hw, sizeof(*params)); return 0; } + +int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *a) { if (params && a) *a = params->access; return 0; } +int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params, snd_pcm_format_t *f) { if (params && f) *f = params->format; return 0; } +int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *v) { if (params && v) *v = params->channels; return 0; } +int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (params && v) { *v = params->rate; if (d) *d = 0; } return 0; } +int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *v, int *d) { if (params && v) { *v = params->period_frames; if (d) *d = 0; } return 0; } +int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *v) { if (params && v) *v = params->buffer_frames; return 0; } int snd_pcm_hw_params_get_sbits(const snd_pcm_hw_params_t *params) { return params ? params->sbits : 0; } -int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (!params || !v) return -22; *v = (params->buffer_frames * 1000000) / params->rate; if (d) *d = 0; return 0; } -int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (!params || !v) return -22; *v = (params->period_frames * 1000000) / params->rate; if (d) *d = 0; return 0; } +int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (params && v) { *v = (params->buffer_frames * 1000000) / (params->rate ? params->rate : 44100); if (d) *d = 0; } return 0; } +int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, unsigned int *v, int *d) { if (params && v) { *v = (params->period_frames * 1000000) / (params->rate ? params->rate : 44100); if (d) *d = 0; } return 0; } int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params) { (void)params; return 0; } int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr) { *ptr = (snd_pcm_sw_params_t*)calloc(1, sizeof(snd_pcm_sw_params_t)); return *ptr ? 0 : -12; } @@ -336,72 +143,30 @@ int snd_pcm_sw_params_get_boundary(const snd_pcm_sw_params_t *p, snd_pcm_uframes int snd_pcm_sw_params_set_period_event(snd_pcm_t *p, snd_pcm_sw_params_t *pp, int v) { return 0; } int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) { return 0; } -int snd_pcm_prepare(snd_pcm_t *pcm) { - if (!pcm) return -22; - pthread_mutex_lock(&pcm->mutex); - pcm->write_pos = 0; pcm->play_pos = 0; pcm->buffered_frames = 0; - if (pcm->player && pcm->stream == SND_PCM_STREAM_PLAYBACK) { - (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); - (*pcm->bufferQueue)->Clear(pcm->bufferQueue); - } - pcm->state = SND_PCM_STATE_PREPARED; - pthread_mutex_unlock(&pcm->mutex); - return 0; -} - -snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) { - if (!pcm || !buffer) return -22; - pthread_mutex_lock(&pcm->mutex); - if (pcm->state != SND_PCM_STATE_PREPARED && pcm->state != SND_PCM_STATE_RUNNING) { pthread_mutex_unlock(&pcm->mutex); return -22; } - if (pcm->stream != SND_PCM_STREAM_PLAYBACK) { pthread_mutex_unlock(&pcm->mutex); return -22; } - if (pcm->state == SND_PCM_STATE_PREPARED && pcm->player) { - SLresult r = (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_PLAYING); - if (r == SL_RESULT_SUCCESS) pcm->state = SND_PCM_STATE_RUNNING; - } - if (pcm->bufferQueue) { - SLresult r = (*pcm->bufferQueue)->Enqueue(pcm->bufferQueue, buffer, size * pcm->frame_size); - if (r != SL_RESULT_SUCCESS) { pthread_mutex_unlock(&pcm->mutex); return -5; } - pcm->buffered_frames += size; - } - pthread_mutex_unlock(&pcm->mutex); - return size; -} - -snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) { (void)buffer; (void)size; return -19; } - -int snd_pcm_drain(snd_pcm_t *pcm) { - if (!pcm) return -22; - if (pcm->player) (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); - pcm->state = SND_PCM_STATE_DRAINING; - return 0; -} - -int snd_pcm_drop(snd_pcm_t *pcm) { - if (!pcm) return -22; - if (pcm->player) { (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_STOPPED); (*pcm->bufferQueue)->Clear(pcm->bufferQueue); } - pcm->buffered_frames = 0; pcm->state = SND_PCM_STATE_SETUP; - return 0; -} - +int snd_pcm_prepare(snd_pcm_t *pcm) { if (pcm) pcm->state = SND_PCM_STATE_PREPARED; return 0; } +snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) { (void)pcm; (void)buffer; return size; } +snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) { (void)pcm; (void)buffer; (void)size; return -19; } +int snd_pcm_drain(snd_pcm_t *pcm) { if (pcm) pcm->state = SND_PCM_STATE_DRAINING; return 0; } +int snd_pcm_drop(snd_pcm_t *pcm) { if (pcm) pcm->state = SND_PCM_STATE_SETUP; return 0; } int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent) { (void)err; (void)silent; return snd_pcm_prepare(pcm); } snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm) { return pcm ? pcm->state : SND_PCM_STATE_DISCONNECTED; } -snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm) { return pcm ? (snd_pcm_sframes_t)(pcm->hw.buffer_frames - pcm->buffered_frames) : -22; } +snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm) { return pcm ? (snd_pcm_sframes_t)(pcm->hw.buffer_frames) : 0; } snd_pcm_sframes_t snd_pcm_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) { (void)frames; return 0; } -int snd_pcm_resume(snd_pcm_t *pcm) { if (!pcm) return -22; if (pcm->player) (*pcm->player)->SetPlayState(pcm->player, SL_PLAYSTATE_PLAYING); pcm->state = SND_PCM_STATE_RUNNING; return 0; } +int snd_pcm_resume(snd_pcm_t *pcm) { if (pcm) pcm->state = SND_PCM_STATE_RUNNING; return 0; } int snd_pcm_start(snd_pcm_t *pcm) { return snd_pcm_prepare(pcm); } int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) { (void)nonblock; return 0; } snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm) { return snd_pcm_avail_update(pcm); } -int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay) { if (!pcm || !delay) return -22; *delay = 0; return 0; } +int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay) { if (delay) *delay = 0; return 0; } int snd_pcm_wait(snd_pcm_t *pcm, int timeout) { (void)timeout; return 1; } snd_pcm_sframes_t snd_pcm_mmap_begin(snd_pcm_t *pcm, const void **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames) { (void)areas; (void)offset; (void)frames; return -77; } snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames) { (void)offset; (void)frames; return -77; } -int snd_pcm_format_physical_width(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->bits : 16; } -int snd_pcm_format_width(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->sbits : 16; } -int snd_pcm_format_signed(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->signed_fmt : 1; } -int snd_pcm_format_big_endian(snd_pcm_format_t format) { format_entry_t *fe = find_format(format); return fe ? fe->big_endian : 0; } +int snd_pcm_format_physical_width(snd_pcm_format_t format) { (void)format; return 16; } +int snd_pcm_format_width(snd_pcm_format_t format) { (void)format; return 16; } +int snd_pcm_format_signed(snd_pcm_format_t format) { (void)format; return 1; } +int snd_pcm_format_big_endian(snd_pcm_format_t format) { (void)format; return 0; } int snd_pcm_format_linear(snd_pcm_format_t format) { return 1; } -size_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) { format_entry_t *fe = find_format(format); return samples * ((fe ? fe->bits : 16) / 8); } +size_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) { (void)format; return samples * 2; } snd_pcm_format_t snd_pcm_build_linear_format(int w, int pw, int u, int be) { (void)w; (void)pw; (void)u; (void)be; return SND_PCM_FORMAT_S16_LE; } int snd_pcm_link(snd_pcm_t *a, snd_pcm_t *b) { (void)a; (void)b; return 0; } int snd_pcm_unlink(snd_pcm_t *p) { (void)p; return 0; } @@ -420,9 +185,7 @@ snd_mixer_elem_t* snd_mixer_elem_next(snd_mixer_elem_t *e) { (void)e; return NUL snd_mixer_elem_t* snd_mixer_elem_prev(snd_mixer_elem_t *e) { (void)e; return NULL; } int snd_mixer_handle_events(snd_mixer_t *m) { (void)m; return 0; } -int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr) { - *ptr = (snd_mixer_selem_id_t*)calloc(1, sizeof(snd_mixer_selem_id_t)); return *ptr ? 0 : -12; -} +int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr) { *ptr = (snd_mixer_selem_id_t*)calloc(1, sizeof(snd_mixer_selem_id_t)); return *ptr ? 0 : -12; } void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj) { free(obj); } void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t *obj, const char *val) { if (obj && val) strncpy(obj->name, val, sizeof(obj->name) - 1); } const char* snd_mixer_selem_id_get_name(snd_mixer_selem_id_t *obj) { return obj ? obj->name : ""; } @@ -466,11 +229,7 @@ int snd_mixer_selem_channel_id(snd_mixer_selem_channel_id_t c) { return (int)c; int snd_ctl_open(snd_ctl_t **h, const char *n, int m) { (void)n; (void)m; *h = NULL; return -19; } int snd_ctl_close(snd_ctl_t *h) { (void)h; return 0; } -int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr) { - *ptr = (snd_ctl_card_info_t*)calloc(1, sizeof(snd_ctl_card_info_t)); - if (*ptr) { strcpy((*ptr)->id, "Android"); strcpy((*ptr)->name, "Android Audio"); strcpy((*ptr)->driver, "alsa_stub"); } - return *ptr ? 0 : -12; -} +int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr) { *ptr = (snd_ctl_card_info_t*)calloc(1, sizeof(snd_ctl_card_info_t)); return *ptr ? 0 : -12; } void snd_ctl_card_info_free(snd_ctl_card_info_t *obj) { free(obj); } int snd_ctl_card_info(snd_ctl_t *h, snd_ctl_card_info_t *i) { (void)h; (void)i; return -19; } const char* snd_ctl_card_info_get_id(const snd_ctl_card_info_t *o) { return o ? o->id : ""; } From 386ba8e4ae735912219164c33853d561f3046412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Fri, 24 Jul 2026 22:46:49 +0800 Subject: [PATCH 15/18] fix: copy libjsound.so to JRE lib dir for Java 25 module system lookup --- .../com/tungsten/fclauncher/FCLauncher.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java index d71fecc969..ba372d1802 100644 --- a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java +++ b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java @@ -348,8 +348,25 @@ private static void setUpJavaRuntime(FCLConfig config, FCLBridge bridge) throws if (alsaStub.exists()) { bridge.dlopen(alsaStub.getAbsolutePath()); } -} catch (Exception ignored) { + } catch (Exception ignored) { } + // Copy jsound native lib to JRE lib dir for javax.sound module + // Java 25's module system uses System.loadLibrary("jsound") which searches + // the JRE's system library path, not java.library.path. + try { + File jsoundLib = new File(config.getContext().getApplicationInfo().nativeLibraryDir, "libjsound.so"); + if (jsoundLib.exists()) { + String javaLibDir = config.getJavaPath() + getJavaLibDir(config.getJavaPath()); + File target = new File(javaLibDir, "libjsound.so"); + if (!target.exists()) { + java.nio.file.Files.copy(jsoundLib.toPath(), target.toPath(), + java.nio.file.StandardCopyOption.REPLACE_EXISTING); + log(bridge, "Copied libjsound.so to " + target.getAbsolutePath()); + } + bridge.dlopen(jsoundLib.getAbsolutePath()); + } + } catch (Exception ignored) { + } String javaLibDir = config.getJavaPath() + getJavaLibDir(config.getJavaPath()); String jliLibDir = new File(javaLibDir + "/jli/libjli.so").exists() ? javaLibDir + "/jli" : javaLibDir; if (isJDK8(config.getJavaPath())) From 7dbdd62e3d44c45ecac3594fcf1e1eca7cb8de7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Sat, 25 Jul 2026 17:38:59 +0800 Subject: [PATCH 16/18] fix: split JNI method registration by class (PortMixerProvider, DirectAudioDeviceProvider, DirectAudioDevice) --- FCLauncher/src/main/jni/jsound/jsound.c | 41 +++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/FCLauncher/src/main/jni/jsound/jsound.c b/FCLauncher/src/main/jni/jsound/jsound.c index 4632e16a90..119df7f434 100644 --- a/FCLauncher/src/main/jni/jsound/jsound.c +++ b/FCLauncher/src/main/jni/jsound/jsound.c @@ -257,6 +257,10 @@ static jint JNICALL nGetNumDevices(JNIEnv *env, jclass clazz, jint type) { return (type == 0) ? 1 : 0; } +static jint JNICALL nGetNumDevicesBool(JNIEnv *env, jclass clazz, jboolean capture) { + return (capture == JNI_FALSE) ? 1 : 0; +} + static jstring JNICALL nGetDeviceDescription(JNIEnv *env, jclass clazz, jint mixerIndex, jint deviceID) { return (*env)->NewStringUTF(env, "Android OpenSL ES Audio"); } @@ -356,9 +360,16 @@ static jint JNICALL nDrain(JNIEnv *env, jobject thiz, jlong id) { return 0; } -static JNINativeMethod methods[] = { +static JNINativeMethod portMixerMethods[] = { {"nGetNumDevices", "(I)I", (void*)nGetNumDevices}, {"nGetDeviceDescription", "(II)Ljava/lang/String;", (void*)nGetDeviceDescription}, +}; + +static JNINativeMethod directAudioDevProvMethods[] = { + {"nGetNumDevices", "(Z)I", (void*)nGetNumDevicesBool}, +}; + +static JNINativeMethod directAudioMethods[] = { {"nOpen", "(II)J", (void*)nOpen}, {"nStart", "(J)I", (void*)nStart}, {"nStop", "(J)I", (void*)nStop}, @@ -374,15 +385,33 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; - jclass clazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixer"); - if (!clazz) { - clazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixerProvider"); - if (!clazz) + // Register PortMixerProvider native methods + jclass pmClazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixerProvider"); + if (!pmClazz) { + pmClazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixer"); + if (!pmClazz) return JNI_ERR; } - if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods) / sizeof(methods[0])) != JNI_OK) + if ((*env)->RegisterNatives(env, pmClazz, portMixerMethods, + sizeof(portMixerMethods) / sizeof(portMixerMethods[0])) != JNI_OK) return JNI_ERR; + // Register DirectAudioDeviceProvider native methods + jclass dapClazz = (*env)->FindClass(env, "com/sun/media/sound/DirectAudioDeviceProvider"); + if (dapClazz) { + if ((*env)->RegisterNatives(env, dapClazz, directAudioDevProvMethods, + sizeof(directAudioDevProvMethods) / sizeof(directAudioDevProvMethods[0])) != JNI_OK) + return JNI_ERR; + } + + // Register DirectAudioDevice native methods + jclass daClazz = (*env)->FindClass(env, "com/sun/media/sound/DirectAudioDevice"); + if (daClazz) { + if ((*env)->RegisterNatives(env, daClazz, directAudioMethods, + sizeof(directAudioMethods) / sizeof(directAudioMethods[0])) != JNI_OK) + return JNI_ERR; + } + return JNI_VERSION_1_6; } \ No newline at end of file From 6638cc0ec30b0932fc932354bba20ce9cf2a5463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Sun, 26 Jul 2026 19:35:12 +0800 Subject: [PATCH 17/18] fix: make PortMixerProvider registration optional in JDK 25 --- FCLauncher/src/main/jni/jsound/jsound.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/FCLauncher/src/main/jni/jsound/jsound.c b/FCLauncher/src/main/jni/jsound/jsound.c index 119df7f434..53f0a56c74 100644 --- a/FCLauncher/src/main/jni/jsound/jsound.c +++ b/FCLauncher/src/main/jni/jsound/jsound.c @@ -385,18 +385,15 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; - // Register PortMixerProvider native methods + // Register PortMixerProvider native methods (optional, may not exist in JDK 25) jclass pmClazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixerProvider"); - if (!pmClazz) { + if (!pmClazz) pmClazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixer"); - if (!pmClazz) - return JNI_ERR; + if (pmClazz) { + (*env)->RegisterNatives(env, pmClazz, portMixerMethods, + sizeof(portMixerMethods) / sizeof(portMixerMethods[0])); } - if ((*env)->RegisterNatives(env, pmClazz, portMixerMethods, - sizeof(portMixerMethods) / sizeof(portMixerMethods[0])) != JNI_OK) - return JNI_ERR; - // Register DirectAudioDeviceProvider native methods jclass dapClazz = (*env)->FindClass(env, "com/sun/media/sound/DirectAudioDeviceProvider"); if (dapClazz) { From 81502dc8c5dafd6af66a920ddd0f476ad2365487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E5=A2=A8=E9=9B=A8?= <3482045693@qq.com> Date: Mon, 27 Jul 2026 09:22:57 +0800 Subject: [PATCH 18/18] fix: rewrite jsound.c with correct JDK 25 JNI signatures (Java_* naming convention) --- FCLauncher/src/main/jni/jsound/jsound.c | 361 +++++++++++------------- 1 file changed, 160 insertions(+), 201 deletions(-) diff --git a/FCLauncher/src/main/jni/jsound/jsound.c b/FCLauncher/src/main/jni/jsound/jsound.c index 53f0a56c74..83a843f72e 100644 --- a/FCLauncher/src/main/jni/jsound/jsound.c +++ b/FCLauncher/src/main/jni/jsound/jsound.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -6,6 +6,7 @@ #include #include +#define MAX_DEVICES 8 #define BUFFER_COUNT 4 #define BUFFER_SIZE 4096 #define RING_SIZE (256 * 1024) @@ -35,11 +36,13 @@ typedef struct { volatile int running; volatile int draining; volatile int drained; + int inUse; } AudioCtx; static SLObjectItf gEngineObj = NULL; static SLEngineItf gEngine = NULL; static int gRefCount = 0; +static AudioCtx gDevices[MAX_DEVICES]; static int minInt(int a, int b) { return a < b ? a : b; } @@ -54,32 +57,27 @@ static int ringUsed(AudioCtx *ctx) { static void ringWrite(AudioCtx *ctx, const char *src, int len) { int part = minInt(len, RING_SIZE - ctx->writePos); memcpy(ctx->ring + ctx->writePos, src, part); - if (part < len) { + if (part < len) memcpy(ctx->ring, src + part, len - part); - } ctx->writePos = (ctx->writePos + len) % RING_SIZE; } static void ringRead(AudioCtx *ctx, char *dst, int len) { int part = minInt(len, RING_SIZE - ctx->readPos); memcpy(dst, ctx->ring + ctx->readPos, part); - if (part < len) { + if (part < len) memcpy(dst + part, ctx->ring, len - part); - } ctx->readPos = (ctx->readPos + len) % RING_SIZE; } static void bufferQueueCallback(SLBufferQueueItf caller, void *context) { AudioCtx *ctx = (AudioCtx*)context; int enqueueIdx = -1; - pthread_mutex_lock(&ctx->mutex); - if (ctx->buffersInQueue > 0) { ctx->buffersInQueue--; ctx->enqueueHead = (ctx->enqueueHead + 1) % BUFFER_COUNT; } - if (ringUsed(ctx) >= BUFFER_SIZE) { enqueueIdx = ctx->enqueueTail; ringRead(ctx, ctx->buffers[enqueueIdx], BUFFER_SIZE); @@ -91,47 +89,31 @@ static void bufferQueueCallback(SLBufferQueueItf caller, void *context) { ctx->drained = 1; pthread_cond_signal(&ctx->drainCond); } + memset(ctx->buffers[ctx->enqueueTail], 0, BUFFER_SIZE); enqueueIdx = ctx->enqueueTail; - memset(ctx->buffers[enqueueIdx], 0, BUFFER_SIZE); ctx->enqueueTail = (ctx->enqueueTail + 1) % BUFFER_COUNT; ctx->buffersInQueue++; } else { + memset(ctx->buffers[ctx->enqueueTail], 0, BUFFER_SIZE); enqueueIdx = ctx->enqueueTail; - memset(ctx->buffers[enqueueIdx], 0, BUFFER_SIZE); ctx->enqueueTail = (ctx->enqueueTail + 1) % BUFFER_COUNT; ctx->buffersInQueue++; } pthread_mutex_unlock(&ctx->mutex); - - if (enqueueIdx >= 0) { + if (enqueueIdx >= 0) (*caller)->Enqueue(caller, ctx->buffers[enqueueIdx], BUFFER_SIZE); - } } static int initOpenSL(AudioCtx *ctx) { SLresult result; - pthread_mutex_lock(&ctx->mutex); if (gEngineObj == NULL) { result = slCreateEngine(&gEngineObj, 0, NULL, 0, NULL, NULL); - if (result != SL_RESULT_SUCCESS) { - pthread_mutex_unlock(&ctx->mutex); - return 0; - } + if (result != SL_RESULT_SUCCESS) { pthread_mutex_unlock(&ctx->mutex); return 0; } result = (*gEngineObj)->Realize(gEngineObj, SL_BOOLEAN_FALSE); - if (result != SL_RESULT_SUCCESS) { - (*gEngineObj)->Destroy(gEngineObj); - gEngineObj = NULL; - pthread_mutex_unlock(&ctx->mutex); - return 0; - } + if (result != SL_RESULT_SUCCESS) { (*gEngineObj)->Destroy(gEngineObj); gEngineObj = NULL; pthread_mutex_unlock(&ctx->mutex); return 0; } result = (*gEngineObj)->GetInterface(gEngineObj, SL_IID_ENGINE, &gEngine); - if (result != SL_RESULT_SUCCESS) { - (*gEngineObj)->Destroy(gEngineObj); - gEngineObj = NULL; - pthread_mutex_unlock(&ctx->mutex); - return 0; - } + if (result != SL_RESULT_SUCCESS) { (*gEngineObj)->Destroy(gEngineObj); gEngineObj = NULL; pthread_mutex_unlock(&ctx->mutex); return 0; } } gRefCount++; ctx->engineObj = gEngineObj; @@ -143,45 +125,28 @@ static int initOpenSL(AudioCtx *ctx) { result = (*ctx->outputMixObj)->Realize(ctx->outputMixObj, SL_BOOLEAN_FALSE); if (result != SL_RESULT_SUCCESS) return 0; - SLDataLocator_AndroidSimpleBufferQueue locBufQueue = { - SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, BUFFER_COUNT - }; - SLDataFormat_PCM formatPcm = { - SL_DATAFORMAT_PCM, - 2, - SL_SAMPLINGRATE_44_1, - SL_PCMSAMPLEFORMAT_FIXED_16, - SL_PCMSAMPLEFORMAT_FIXED_16, - SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, - SL_BYTEORDER_LITTLEENDIAN - }; + SLDataLocator_AndroidSimpleBufferQueue locBufQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, BUFFER_COUNT }; + SLDataFormat_PCM formatPcm = { SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, + SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN }; SLDataSource audioSrc = { &locBufQueue, &formatPcm }; - - SLDataLocator_OutputMix locOutMix = { - SL_DATALOCATOR_OUTPUTMIX, ctx->outputMixObj - }; + SLDataLocator_OutputMix locOutMix = { SL_DATALOCATOR_OUTPUTMIX, ctx->outputMixObj }; SLDataSink audioSnk = { &locOutMix, NULL }; - SLInterfaceID ids[] = { SL_IID_BUFFERQUEUE }; SLboolean req[] = { SL_BOOLEAN_TRUE }; - result = (*ctx->engine)->CreateAudioPlayer(ctx->engine, &ctx->playerObj, - &audioSrc, &audioSnk, 1, ids, req); + result = (*ctx->engine)->CreateAudioPlayer(ctx->engine, &ctx->playerObj, &audioSrc, &audioSnk, 1, ids, req); if (result != SL_RESULT_SUCCESS) return 0; result = (*ctx->playerObj)->Realize(ctx->playerObj, SL_BOOLEAN_FALSE); if (result != SL_RESULT_SUCCESS) return 0; - result = (*ctx->playerObj)->GetInterface(ctx->playerObj, SL_IID_PLAY, &ctx->play); if (result != SL_RESULT_SUCCESS) return 0; - result = (*ctx->playerObj)->GetInterface(ctx->playerObj, SL_IID_BUFFERQUEUE, &ctx->queue); if (result != SL_RESULT_SUCCESS) return 0; - result = (*ctx->queue)->RegisterCallback(ctx->queue, bufferQueueCallback, ctx); if (result != SL_RESULT_SUCCESS) return 0; - int i; - for (i = 0; i < BUFFER_COUNT; i++) { + for (int i = 0; i < BUFFER_COUNT; i++) { memset(ctx->buffers[ctx->enqueueTail], 0, BUFFER_SIZE); result = (*ctx->queue)->Enqueue(ctx->queue, ctx->buffers[ctx->enqueueTail], BUFFER_SIZE); if (result != SL_RESULT_SUCCESS) break; @@ -190,25 +155,14 @@ static int initOpenSL(AudioCtx *ctx) { } result = (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PLAYING); - if (result != SL_RESULT_SUCCESS) return 0; - - return 1; + return (result == SL_RESULT_SUCCESS) ? 1 : 0; } static void shutdownOpenSL(AudioCtx *ctx) { - if (ctx->play) - (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_STOPPED); - if (ctx->queue) - (*ctx->queue)->Clear(ctx->queue); - if (ctx->playerObj) { - (*ctx->playerObj)->Destroy(ctx->playerObj); - ctx->playerObj = NULL; - } - if (ctx->outputMixObj) { - (*ctx->outputMixObj)->Destroy(ctx->outputMixObj); - ctx->outputMixObj = NULL; - } - + if (ctx->play) (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_STOPPED); + if (ctx->queue) (*ctx->queue)->Clear(ctx->queue); + if (ctx->playerObj) { (*ctx->playerObj)->Destroy(ctx->playerObj); ctx->playerObj = NULL; } + if (ctx->outputMixObj) { (*ctx->outputMixObj)->Destroy(ctx->outputMixObj); ctx->outputMixObj = NULL; } pthread_mutex_lock(&ctx->mutex); gRefCount--; if (gRefCount == 0 && gEngineObj != NULL) { @@ -219,105 +173,161 @@ static void shutdownOpenSL(AudioCtx *ctx) { pthread_mutex_unlock(&ctx->mutex); } -static AudioCtx* createCtx(void) { - AudioCtx *ctx = (AudioCtx*)calloc(1, sizeof(AudioCtx)); - if (!ctx) return NULL; - ctx->writePos = 0; - ctx->readPos = 0; - ctx->buffersInQueue = 0; - ctx->enqueueHead = 0; - ctx->enqueueTail = 0; - ctx->running = 1; - ctx->draining = 0; - ctx->drained = 0; - ctx->engineObj = NULL; - ctx->engine = NULL; - ctx->outputMixObj = NULL; - ctx->playerObj = NULL; - ctx->play = NULL; - ctx->queue = NULL; - pthread_mutex_init(&ctx->mutex, NULL); - pthread_cond_init(&ctx->dataCond, NULL); - pthread_cond_init(&ctx->spaceCond, NULL); - pthread_cond_init(&ctx->drainCond, NULL); - return ctx; +static int allocDeviceId(void) { + for (int i = 0; i < MAX_DEVICES; i++) { + if (!gDevices[i].inUse) { + memset(&gDevices[i], 0, sizeof(AudioCtx)); + gDevices[i].inUse = 1; + gDevices[i].writePos = 0; + gDevices[i].readPos = 0; + gDevices[i].buffersInQueue = 0; + gDevices[i].enqueueHead = 0; + gDevices[i].enqueueTail = 0; + gDevices[i].running = 1; + gDevices[i].draining = 0; + gDevices[i].drained = 0; + gDevices[i].engineObj = NULL; + gDevices[i].engine = NULL; + gDevices[i].outputMixObj = NULL; + gDevices[i].playerObj = NULL; + gDevices[i].play = NULL; + gDevices[i].queue = NULL; + pthread_mutex_init(&gDevices[i].mutex, NULL); + pthread_cond_init(&gDevices[i].dataCond, NULL); + pthread_cond_init(&gDevices[i].spaceCond, NULL); + pthread_cond_init(&gDevices[i].drainCond, NULL); + return i; + } + } + return -1; } -static void destroyCtx(AudioCtx *ctx) { - if (!ctx) return; +static void freeDeviceId(int id) { + if (id < 0 || id >= MAX_DEVICES) return; + AudioCtx *ctx = &gDevices[id]; + if (!ctx->inUse) return; shutdownOpenSL(ctx); pthread_mutex_destroy(&ctx->mutex); pthread_cond_destroy(&ctx->dataCond); pthread_cond_destroy(&ctx->spaceCond); pthread_cond_destroy(&ctx->drainCond); - free(ctx); + memset(ctx, 0, sizeof(AudioCtx)); +} + +// ==================== DirectAudioDeviceProvider ==================== + +JNIEXPORT jint JNICALL Java_com_sun_media_sound_DirectAudioDeviceProvider_nGetNumDevices(JNIEnv *env, jclass clazz) { + return 1; +} + +JNIEXPORT jobject JNICALL Java_com_sun_media_sound_DirectAudioDeviceProvider_nNewDirectAudioDeviceInfo(JNIEnv *env, jclass clazz, jint deviceIndex) { + jclass infoClass = (*env)->FindClass(env, "com/sun/media/sound/DirectAudioDeviceProvider$DirectAudioDeviceInfo"); + if (!infoClass) { (*env)->ExceptionClear(env); return NULL; } + jmethodID ctor = (*env)->GetMethodID(env, infoClass, "", "(I)V"); + if (!ctor) { + (*env)->ExceptionClear(env); + ctor = (*env)->GetMethodID(env, infoClass, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIII)V"); + if (!ctor) { (*env)->ExceptionClear(env); return NULL; } + jstring name = (*env)->NewStringUTF(env, "Android OpenSL ES Audio"); + jstring vendor = (*env)->NewStringUTF(env, "Android"); + jstring desc = (*env)->NewStringUTF(env, "OpenSL ES Audio Device"); + jstring ver = (*env)->NewStringUTF(env, "1.0"); + jobject obj = (*env)->NewObject(env, infoClass, ctor, name, vendor, desc, ver, deviceIndex, 2, 65536, 1); + (*env)->DeleteLocalRef(env, name); + (*env)->DeleteLocalRef(env, vendor); + (*env)->DeleteLocalRef(env, desc); + (*env)->DeleteLocalRef(env, ver); + return obj; + } + return (*env)->NewObject(env, infoClass, ctor, deviceIndex); +} + +// ==================== DirectAudioDevice ==================== + +JNIEXPORT jint JNICALL Java_com_sun_media_sound_DirectAudioDevice_nAvailable(JNIEnv *env, jclass clazz, jlong id) { + if (id < 0 || id >= MAX_DEVICES) return 0; + AudioCtx *ctx = &gDevices[(int)id]; + if (!ctx->inUse) return 0; + pthread_mutex_lock(&ctx->mutex); + int avail = ringAvailable(ctx); + pthread_mutex_unlock(&ctx->mutex); + return avail; } -static jint JNICALL nGetNumDevices(JNIEnv *env, jclass clazz, jint type) { - return (type == 0) ? 1 : 0; +JNIEXPORT jint JNICALL Java_com_sun_media_sound_DirectAudioDevice_nGetBufferSize(JNIEnv *env, jclass clazz, jlong id) { + return BUFFER_SIZE * BUFFER_COUNT; } -static jint JNICALL nGetNumDevicesBool(JNIEnv *env, jclass clazz, jboolean capture) { - return (capture == JNI_FALSE) ? 1 : 0; +JNIEXPORT jlong JNICALL Java_com_sun_media_sound_DirectAudioDevice_nGetBytePosition(JNIEnv *env, jclass clazz, jlong id, jboolean isBytePos) { + AudioCtx *ctx = (id >= 0 && id < MAX_DEVICES) ? &gDevices[(int)id] : NULL; + if (!ctx || !ctx->inUse) return 0; + pthread_mutex_lock(&ctx->mutex); + jlong pos = (jlong)ctx->writePos; + pthread_mutex_unlock(&ctx->mutex); + return pos; } -static jstring JNICALL nGetDeviceDescription(JNIEnv *env, jclass clazz, jint mixerIndex, jint deviceID) { - return (*env)->NewStringUTF(env, "Android OpenSL ES Audio"); +JNIEXPORT void JNICALL Java_com_sun_media_sound_DirectAudioDevice_nSetBytePosition(JNIEnv *env, jclass clazz, jlong id, jlong pos, jboolean isBytePos) { } -static jlong JNICALL nOpen(JNIEnv *env, jclass clazz, jint mixerIndex, jint deviceID) { - AudioCtx *ctx = createCtx(); - if (!ctx) return 0; - if (!initOpenSL(ctx)) { - destroyCtx(ctx); - return 0; - } - return (jlong)(intptr_t)ctx; +JNIEXPORT jint JNICALL Java_com_sun_media_sound_DirectAudioDevice_nOpen(JNIEnv *env, jclass clazz, jlong id, jboolean isSource, jboolean isOutput, jfloat sampleRate, jint sampleSizeInBits, jint channels, jint bufferSize, jint encoding) { + if (!isOutput || !isSource) return -1; + int devId = allocDeviceId(); + if (devId < 0) return -1; + AudioCtx *ctx = &gDevices[devId]; + if (!initOpenSL(ctx)) { freeDeviceId(devId); return -1; } + return devId; } -static jint JNICALL nStart(JNIEnv *env, jobject thiz, jlong id) { - AudioCtx *ctx = (AudioCtx*)(intptr_t)id; - if (!ctx || !ctx->play) return -1; - ctx->running = 1; - SLresult result = (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PLAYING); - return (result == SL_RESULT_SUCCESS) ? 0 : -1; +JNIEXPORT void JNICALL Java_com_sun_media_sound_DirectAudioDevice_nClose(JNIEnv *env, jclass clazz, jlong id) { + freeDeviceId((int)id); } -static jint JNICALL nStop(JNIEnv *env, jobject thiz, jlong id) { - AudioCtx *ctx = (AudioCtx*)(intptr_t)id; - if (!ctx || !ctx->play) return -1; - ctx->running = 0; - SLresult result = (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PAUSED); - return (result == SL_RESULT_SUCCESS) ? 0 : -1; +JNIEXPORT void JNICALL Java_com_sun_media_sound_DirectAudioDevice_nStart(JNIEnv *env, jclass clazz, jlong id, jboolean isSource) { + AudioCtx *ctx = (id >= 0 && id < MAX_DEVICES) ? &gDevices[(int)id] : NULL; + if (!ctx || !ctx->inUse || !ctx->play) return; + ctx->running = 1; + (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PLAYING); } -static jint JNICALL nClose(JNIEnv *env, jobject thiz, jlong id) { - AudioCtx *ctx = (AudioCtx*)(intptr_t)id; - if (!ctx) return 0; +JNIEXPORT void JNICALL Java_com_sun_media_sound_DirectAudioDevice_nStop(JNIEnv *env, jclass clazz, jlong id, jboolean isSource) { + AudioCtx *ctx = (id >= 0 && id < MAX_DEVICES) ? &gDevices[(int)id] : NULL; + if (!ctx || !ctx->inUse || !ctx->play) return; ctx->running = 0; - destroyCtx(ctx); - return 0; + (*ctx->play)->SetPlayState(ctx->play, SL_PLAYSTATE_PAUSED); } -static jint JNICALL nWrite(JNIEnv *env, jobject thiz, jlong id, jbyteArray b, jint off, jint len) { - AudioCtx *ctx = (AudioCtx*)(intptr_t)id; - if (!ctx || !b) return -1; +JNIEXPORT void JNICALL Java_com_sun_media_sound_DirectAudioDevice_nFlush(JNIEnv *env, jclass clazz, jlong id, jboolean isSource) { + AudioCtx *ctx = (id >= 0 && id < MAX_DEVICES) ? &gDevices[(int)id] : NULL; + if (!ctx || !ctx->inUse || !ctx->queue) return; + pthread_mutex_lock(&ctx->mutex); + ctx->writePos = 0; + ctx->readPos = 0; + ctx->buffersInQueue = 0; + ctx->enqueueHead = 0; + ctx->enqueueTail = 0; + pthread_mutex_unlock(&ctx->mutex); + (*ctx->queue)->Clear(ctx->queue); + for (int i = 0; i < BUFFER_COUNT; i++) { + memset(ctx->buffers[i], 0, BUFFER_SIZE); + (*ctx->queue)->Enqueue(ctx->queue, ctx->buffers[i], BUFFER_SIZE); + } +} +JNIEXPORT jint JNICALL Java_com_sun_media_sound_DirectAudioDevice_nWrite(JNIEnv *env, jclass clazz, jlong id, jbyteArray b, jint off, jint len) { + AudioCtx *ctx = (id >= 0 && id < MAX_DEVICES) ? &gDevices[(int)id] : NULL; + if (!ctx || !ctx->inUse || !b) return -1; jbyte *data = (*env)->GetByteArrayElements(env, b, NULL); if (!data) return -1; - int written = 0; while (written < len) { pthread_mutex_lock(&ctx->mutex); int avail = ringAvailable(ctx); - if (avail == 0) { - pthread_mutex_unlock(&ctx->mutex); - break; - } + if (avail == 0) { pthread_mutex_unlock(&ctx->mutex); break; } int chunk = minInt(len - written, avail); ringWrite(ctx, (const char*)(data + off + written), chunk); written += chunk; - while (ctx->buffersInQueue < BUFFER_COUNT && ringUsed(ctx) >= BUFFER_SIZE) { int idx = ctx->enqueueTail; ringRead(ctx, ctx->buffers[idx], BUFFER_SIZE); @@ -327,88 +337,37 @@ static jint JNICALL nWrite(JNIEnv *env, jobject thiz, jlong id, jbyteArray b, ji } pthread_mutex_unlock(&ctx->mutex); } - (*env)->ReleaseByteArrayElements(env, b, data, JNI_ABORT); return written; } -static jint JNICALL nRead(JNIEnv *env, jobject thiz, jlong id, jbyteArray b, jint off, jint len) { +JNIEXPORT jint JNICALL Java_com_sun_media_sound_DirectAudioDevice_nRead(JNIEnv *env, jclass clazz, jlong id, jbyteArray b, jint off, jint len) { return -1; } -static jint JNICALL nAvailable(JNIEnv *env, jobject thiz, jlong id, jboolean isWrite) { - AudioCtx *ctx = (AudioCtx*)(intptr_t)id; - if (!ctx) return 0; - if (!isWrite) return 0; - pthread_mutex_lock(&ctx->mutex); - int avail = ringAvailable(ctx); - pthread_mutex_unlock(&ctx->mutex); - return avail; +JNIEXPORT jobjectArray JNICALL Java_com_sun_media_sound_DirectAudioDevice_nGetFormats(JNIEnv *env, jclass clazz, jlong id, jboolean isSource, jint sampleSizeInBits, jint channels, jfloat sampleRate, jint encoding) { + return NULL; } -static jint JNICALL nDrain(JNIEnv *env, jobject thiz, jlong id) { - AudioCtx *ctx = (AudioCtx*)(intptr_t)id; - if (!ctx) return 0; +JNIEXPORT jboolean JNICALL Java_com_sun_media_sound_DirectAudioDevice_nIsStillDraining(JNIEnv *env, jclass clazz, jlong id, jboolean isSource) { + AudioCtx *ctx = (id >= 0 && id < MAX_DEVICES) ? &gDevices[(int)id] : NULL; + if (!ctx || !ctx->inUse) return JNI_FALSE; pthread_mutex_lock(&ctx->mutex); - ctx->draining = 1; - while (!ctx->drained && (ringUsed(ctx) > 0 || ctx->buffersInQueue > 0)) { - pthread_cond_wait(&ctx->drainCond, &ctx->mutex); - } - ctx->draining = 0; - ctx->drained = 0; + jboolean draining = (ctx->draining || ringUsed(ctx) > 0 || ctx->buffersInQueue > 0) ? JNI_TRUE : JNI_FALSE; pthread_mutex_unlock(&ctx->mutex); - return 0; + return draining; +} + +JNIEXPORT jboolean JNICALL Java_com_sun_media_sound_DirectAudioDevice_nRequiresServicing(JNIEnv *env, jclass clazz, jlong id, jboolean isSource) { + return JNI_FALSE; } -static JNINativeMethod portMixerMethods[] = { - {"nGetNumDevices", "(I)I", (void*)nGetNumDevices}, - {"nGetDeviceDescription", "(II)Ljava/lang/String;", (void*)nGetDeviceDescription}, -}; - -static JNINativeMethod directAudioDevProvMethods[] = { - {"nGetNumDevices", "(Z)I", (void*)nGetNumDevicesBool}, -}; - -static JNINativeMethod directAudioMethods[] = { - {"nOpen", "(II)J", (void*)nOpen}, - {"nStart", "(J)I", (void*)nStart}, - {"nStop", "(J)I", (void*)nStop}, - {"nClose", "(J)I", (void*)nClose}, - {"nWrite", "(J[BII)I", (void*)nWrite}, - {"nRead", "(J[BII)I", (void*)nRead}, - {"nAvailable", "(JZ)I", (void*)nAvailable}, - {"nDrain", "(J)I", (void*)nDrain}, -}; +JNIEXPORT void JNICALL Java_com_sun_media_sound_DirectAudioDevice_nService(JNIEnv *env, jclass clazz, jlong id, jboolean isSource) { +} JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; - - // Register PortMixerProvider native methods (optional, may not exist in JDK 25) - jclass pmClazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixerProvider"); - if (!pmClazz) - pmClazz = (*env)->FindClass(env, "com/sun/media/sound/PortMixer"); - if (pmClazz) { - (*env)->RegisterNatives(env, pmClazz, portMixerMethods, - sizeof(portMixerMethods) / sizeof(portMixerMethods[0])); - } - - // Register DirectAudioDeviceProvider native methods - jclass dapClazz = (*env)->FindClass(env, "com/sun/media/sound/DirectAudioDeviceProvider"); - if (dapClazz) { - if ((*env)->RegisterNatives(env, dapClazz, directAudioDevProvMethods, - sizeof(directAudioDevProvMethods) / sizeof(directAudioDevProvMethods[0])) != JNI_OK) - return JNI_ERR; - } - - // Register DirectAudioDevice native methods - jclass daClazz = (*env)->FindClass(env, "com/sun/media/sound/DirectAudioDevice"); - if (daClazz) { - if ((*env)->RegisterNatives(env, daClazz, directAudioMethods, - sizeof(directAudioMethods) / sizeof(directAudioMethods[0])) != JNI_OK) - return JNI_ERR; - } - return JNI_VERSION_1_6; -} \ No newline at end of file +}