diff --git a/skeleton/SYSTEM/tg5040/bin/suspend b/skeleton/SYSTEM/tg5040/bin/suspend index 78d7c7934..b208bd8b0 100644 --- a/skeleton/SYSTEM/tg5040/bin/suspend +++ b/skeleton/SYSTEM/tg5040/bin/suspend @@ -13,9 +13,41 @@ sleep_retval=1 asound_state_dir=/tmp/asound-suspend +read_wakeup_count() { + local tmp="/tmp/wakeup_count.$$" + local pid + + cat /sys/power/wakeup_count >"$tmp" 2>/dev/null & + pid=$! + + # Guard against kernels that can block indefinitely on wakeup_count reads. + for _ in 1 2 3 4 5; do + kill -0 "$pid" 2>/dev/null || break + usleep 200000 + done + + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + wait "$pid" 2>/dev/null + rm -f "$tmp" + return 1 + fi + + wait "$pid" 2>/dev/null + cat "$tmp" 2>/dev/null + rm -f "$tmp" + return 0 +} + log_wakeup_sources() { local label="$1" - >&2 echo " [$label] wakeup_count: $(cat /sys/power/wakeup_count 2>/dev/null)" + local wakeup_count + wakeup_count="$(read_wakeup_count 2>/dev/null || true)" + if [ -n "$wakeup_count" ]; then + >&2 echo " [$label] wakeup_count: $wakeup_count" + else + >&2 echo " [$label] wakeup_count: unavailable (read timed out)" + fi if [ -f /sys/power/pm_wakeup_irq ]; then >&2 echo " [$label] last wakeup IRQ: $(cat /sys/power/pm_wakeup_irq 2>/dev/null)" fi @@ -101,7 +133,7 @@ for i in $(seq 1 $sleep_max_tries); do # Synchronize with wakeup events: read wakeup_count and write it back. # If a wakeup event occurred since reading, the write fails and we retry. - wakeup_count=$(cat /sys/power/wakeup_count 2>/dev/null) + wakeup_count="$(read_wakeup_count 2>/dev/null || true)" if [ -n "$wakeup_count" ]; then if ! echo "$wakeup_count" >/sys/power/wakeup_count 2>/dev/null; then >&2 echo "Deep sleep: wakeup event detected before sleep (wakeup_count=$wakeup_count), retrying in 1 second" diff --git a/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh b/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh index 1cf5abdb2..4003961c2 100755 --- a/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh +++ b/skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh @@ -3,6 +3,17 @@ bt_hciattach="hciattach" TRIMUI_MODEL=`strings /usr/trimui/bin/MainUI | grep ^Trimui` DEVICE_NAME="$TRIMUI_MODEL (NextUI)" +RFKILL_BIN="/mnt/SDCARD/.system/tg5040/bin/rfkill.elf" + +unblock_bluetooth() { + if [ -x "$RFKILL_BIN" ]; then + "$RFKILL_BIN" unblock bluetooth + elif command -v rfkill.elf >/dev/null 2>&1; then + rfkill.elf unblock bluetooth + else + rfkill unblock bluetooth + fi +} reset_bluetooth_power() { echo 0 > /sys/class/rfkill/rfkill0/state; @@ -36,7 +47,7 @@ start_hci_attach() { } start_bt() { - rfkill.elf unblock bluetooth + unblock_bluetooth if [ -d "/sys/class/bluetooth/hci0" ];then echo "Bluetooth init has been completed!!" @@ -61,8 +72,7 @@ start_bt() { a=`ps | grep bluealsa | grep -v grep` [ -z "$a" ] && { - # bluealsa -p a2dp-source --keep-alive=-1 & - bluealsa -p a2dp-source & + bluealsa -p a2dp-source --keep-alive=10 & sleep 1 # Power on adapter bluetoothctl power on 2>/dev/null @@ -98,7 +108,7 @@ start_bt() { } ble_start() { - rfkill.elf unblock bluetooth + unblock_bluetooth if [ -d "/sys/class/bluetooth/hci0" ];then echo "Bluetooth init has been completed!!" @@ -173,4 +183,4 @@ case "$1" in echo "Usage: $0 {start|stop|restart}" exit 1 ;; -esac \ No newline at end of file +esac diff --git a/skeleton/SYSTEM/tg5050/bin/suspend b/skeleton/SYSTEM/tg5050/bin/suspend index dd5483c96..d345511e0 100644 --- a/skeleton/SYSTEM/tg5050/bin/suspend +++ b/skeleton/SYSTEM/tg5050/bin/suspend @@ -13,9 +13,41 @@ sleep_retval= asound_state_dir=/tmp/asound-suspend +read_wakeup_count() { + local tmp="/tmp/wakeup_count.$$" + local pid + + cat /sys/power/wakeup_count >"$tmp" 2>/dev/null & + pid=$! + + # Guard against kernels that can block indefinitely on wakeup_count reads. + for _ in 1 2 3 4 5; do + kill -0 "$pid" 2>/dev/null || break + usleep 200000 + done + + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + wait "$pid" 2>/dev/null + rm -f "$tmp" + return 1 + fi + + wait "$pid" 2>/dev/null + cat "$tmp" 2>/dev/null + rm -f "$tmp" + return 0 +} + log_wakeup_sources() { local label="$1" - >&2 echo " [$label] wakeup_count: $(cat /sys/power/wakeup_count 2>/dev/null)" + local wakeup_count + wakeup_count="$(read_wakeup_count 2>/dev/null || true)" + if [ -n "$wakeup_count" ]; then + >&2 echo " [$label] wakeup_count: $wakeup_count" + else + >&2 echo " [$label] wakeup_count: unavailable (read timed out)" + fi if [ -f /sys/power/pm_wakeup_irq ]; then >&2 echo " [$label] last wakeup IRQ: $(cat /sys/power/pm_wakeup_irq 2>/dev/null)" fi @@ -93,7 +125,7 @@ for i in $(seq 1 $sleep_max_tries); do # Synchronize with wakeup events: read wakeup_count and write it back. # If a wakeup event occurred since reading, the write fails and we retry. - wakeup_count=$(cat /sys/power/wakeup_count 2>/dev/null) + wakeup_count="$(read_wakeup_count 2>/dev/null || true)" if [ -n "$wakeup_count" ]; then if ! echo "$wakeup_count" >/sys/power/wakeup_count 2>/dev/null; then >&2 echo "Deep sleep: wakeup event detected before sleep (wakeup_count=$wakeup_count), retrying in 1 second" diff --git a/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh b/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh index ebb39c909..1adb82b17 100755 --- a/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh +++ b/skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh @@ -69,8 +69,7 @@ start_bt() { a=`ps | grep bluealsa | grep -v grep` [ -z "$a" ] && { - # bluealsa -p a2dp-source --keep-alive=-1 & - bluealsa -p a2dp-source & + bluealsa -p a2dp-source --keep-alive=10 & sleep 1 # Power on adapter bluetoothctl power on 2>/dev/null @@ -156,4 +155,4 @@ case "$1" in ;; esac -exit 0 \ No newline at end of file +exit 0 diff --git a/workspace/all/audiomon/audiomon.cpp b/workspace/all/audiomon/audiomon.cpp index 113d1fe78..78bab9d33 100644 --- a/workspace/all/audiomon/audiomon.cpp +++ b/workspace/all/audiomon/audiomon.cpp @@ -117,6 +117,7 @@ void recoverPreviousConfig() { connected_a2dp_mac = content.substr(pos, end - pos); log("Restored BT state from .asoundrc: " + connected_a2dp_mac); + SetAudioSink(AUDIO_SINK_BLUETOOTH); } std::string pathToMac(const std::string& path) { @@ -173,6 +174,36 @@ bool isUsbAudioDevice(struct udev_device* dev) { return false; } +// Returns false if the device is truly gone (query fails or Connected=false). +// Used to filter out transient disconnects BlueZ emits during A2DP negotiation. +bool isDeviceConnected(DBusConnection* conn, const std::string& path) { + DBusMessage* msg = dbus_message_new_method_call("org.bluez", path.c_str(), + "org.freedesktop.DBus.Properties", "Get"); + if (!msg) return false; + + const char* iface = "org.bluez.Device1"; + const char* prop = "Connected"; + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + DBusMessage* reply = dbus_connection_send_with_reply_and_block(conn, msg, 1000, nullptr); + dbus_message_unref(msg); + if (!reply) return false; + + DBusMessageIter iter, variant; + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &variant); + + dbus_bool_t connected = false; + if (dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_BOOLEAN) + dbus_message_iter_get_basic(&variant, &connected); + + dbus_message_unref(reply); + return connected; +} + bool hasUUID(DBusConnection* conn, const std::string& path, const std::string& uuid) { DBusMessage* msg = dbus_message_new_method_call("org.bluez", path.c_str(), "org.freedesktop.DBus.Properties", "Get"); if (!msg) return false; @@ -235,6 +266,13 @@ void handleDeviceDisconnected(DBusConnection* conn, const std::string& path) { // device's service cache may already be gone, causing hasUUID to return false // and silently skip the audio switch-back. if (hasUUID(conn, path, UUID_A2DP) || (!connected_a2dp_mac.empty() && mac == connected_a2dp_mac)) { + // BlueZ emits Connected=false/true in rapid succession during A2DP profile + // negotiation (e.g. JustWorksRepairing). Verify the device is truly gone + // before tearing down the transport keep-alive and audio routing. + if (isDeviceConnected(conn, path)) { + log("Transient disconnect for " + mac + " (device still connected per BlueZ), ignoring"); + return; + } log("Audio device disconnected: " + mac); connected_a2dp_mac.clear(); clearAudioFile(); @@ -245,7 +283,7 @@ void handleDeviceDisconnected(DBusConnection* conn, const std::string& path) { if (system("pidof bluealsa > /dev/null 2>&1") != 0) break; usleep(100000); // 100ms } - system("bluealsa -p a2dp-source &"); + system("bluealsa -p a2dp-source --keep-alive=10 &"); // TODO: we could maintain a stack here, if USBC was connected before and restore that instead SetAudioSink(AUDIO_SINK_DEFAULT); diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 1ac3fe063..b853bfa96 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -2888,7 +2888,12 @@ void SND_init(double sample_rate, double frame_rate) LOG_info("We now have audio device #%d\n", snd.device_id); - snd.frame_count = ((float)spec_out.freq / SCREEN_FPS) * 8; // buffer size based on sample rate out (times 12 samples headroom) + // Increase headroom for BT + high-rate cores (e.g. 65536 Hz GBA input) + // to reduce underruns/crackle after resampling. + int buffer_headroom = 8; + if (PLAT_btIsConnected() && sample_rate > 48000.0) + buffer_headroom = 12; + snd.frame_count = ((float)spec_out.freq / SCREEN_FPS) * buffer_headroom; perf.buffer_size = snd.frame_count; snd.sample_rate_in = sample_rate; snd.sample_rate_out = spec_out.freq; diff --git a/workspace/tg5040/platform/platform.c b/workspace/tg5040/platform/platform.c index 6c975993f..3dda41199 100644 --- a/workspace/tg5040/platform/platform.c +++ b/workspace/tg5040/platform/platform.c @@ -332,9 +332,15 @@ void PLAT_setRumble(int strength) { } int PLAT_pickSampleRate(int requested, int max) { - // bluetooth: allow limiting the maximum to improve compatibility - if(PLAT_bluetoothConnected()) - return MIN(requested, CFG_getBluetoothSamplingrateLimit()); + // bluetooth: allow limiting the maximum to improve compatibility. + // Some cores request non-standard rates (>48k). For those, prefer 44.1k + // over 48k to avoid unstable A2DP renegotiation on certain headsets. + if(PLAT_bluetoothConnected()) { + int limit = MIN(max, CFG_getBluetoothSamplingrateLimit()); + if (requested > 48000) + return MIN(44100, limit); + return MIN(requested, limit); + } return MIN(requested, max); } diff --git a/workspace/tg5050/platform/platform.c b/workspace/tg5050/platform/platform.c index c20ebdc7c..07608ff1f 100644 --- a/workspace/tg5050/platform/platform.c +++ b/workspace/tg5050/platform/platform.c @@ -370,9 +370,15 @@ void PLAT_setRumble(int strength) { } int PLAT_pickSampleRate(int requested, int max) { - // bluetooth: allow limiting the maximum to improve compatibility - if(PLAT_bluetoothConnected()) - return MIN(requested, CFG_getBluetoothSamplingrateLimit()); + // bluetooth: allow limiting the maximum to improve compatibility. + // Some cores request non-standard rates (>48k). For those, prefer 44.1k + // over 48k to avoid unstable A2DP renegotiation on certain headsets. + if(PLAT_bluetoothConnected()) { + int limit = MIN(max, CFG_getBluetoothSamplingrateLimit()); + if (requested > 48000) + return MIN(44100, limit); + return MIN(requested, limit); + } return MIN(requested, max); }