From a1e8bfe80a2162c21a8804e639d5f0cd0c6efe9a Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 8 Oct 2026 14:28:08 +0200 Subject: [PATCH] android: keep radio VAD blocked until AudioTrack plays the receive tail --- .../main/java/com/utun/chat/data/NativeLib.kt | 4 +-- .../com/utun/chat/data/RadioAudioEngine.kt | 28 +++++++++++++++---- .../jni_bridge/android_jni_bridge.c | 3 +- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt index 25c51be0..0834e5af 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt @@ -210,7 +210,7 @@ object NativeLib { fun radioSubscribers(groupId: Long): Int = nativeRadioSubscribers(groupId) fun radioAudioStart(groupId: Long, channels: Int): Boolean = nativeRadioAudioStart(groupId, channels) fun radioAudioFeed(groupId: Long, samples: ShortArray): Boolean = nativeRadioAudioFeed(groupId, samples) - fun radioAudioPull(groupId: Long, out: ShortArray): Int = nativeRadioAudioPull(groupId, out) + fun radioAudioPull(groupId: Long, out: ShortArray, playedFrames: Long): Int = nativeRadioAudioPull(groupId, out, playedFrames) fun radioAudioStop() { nativeRadioAudioStop() } fun radioCaptureStart(groupId: Long, channels: Int, vad: Boolean): Boolean = nativeRadioCaptureStart(groupId, channels, vad) fun radioCaptureStop() { nativeRadioCaptureStop() } @@ -334,7 +334,7 @@ object NativeLib { private external fun nativeRadioSubscribers(groupId: Long): Int private external fun nativeRadioAudioStart(groupId: Long, channels: Int): Boolean private external fun nativeRadioAudioFeed(groupId: Long, samples: ShortArray): Boolean - private external fun nativeRadioAudioPull(groupId: Long, out: ShortArray): Int + private external fun nativeRadioAudioPull(groupId: Long, out: ShortArray, playedFrames: Long): Int private external fun nativeRadioAudioStop() private external fun nativeRadioCaptureStart(groupId: Long, channels: Int, vad: Boolean): Boolean private external fun nativeRadioCaptureStop() diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt index 686a6d6c..e0b45dc6 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt @@ -692,14 +692,32 @@ class RadioAudioEngine { private fun playLoop() { val frame = FRAME_SAMPLES * PLAY_CHANNELS val buf = ShortArray(frame) - val silence = ShortArray(frame) val frameNs = FRAME_MS * 1_000_000L /* 20мс в наносекундах */ var nextNs = System.nanoTime() + var previousHead = 0L + var playedFrames = 0L + val group = audioGroup + val track = audioTrack ?: return + LogManager.addLog("DEBUG", "radio", "playback progress tracking group=$group guard=200ms") while (playbackRunning) { - val track = audioTrack ?: return - val n = NativeLib.radioAudioPull(audioGroup, buf) - if (n > 0) track.write(buf, 0, n) - if (n < frame) track.write(silence, 0, frame - n) + /* AudioTrack возвращает unsigned 32-bit позицию; разворачиваем wrap (~25 часов @48k). */ + val head = track.playbackHeadPosition.toLong() and 0xffffffffL + playedFrames += (head - previousHead) and 0xffffffffL + previousHead = head + val n = NativeLib.radioAudioPull(group, buf, playedFrames) + if (n < frame) buf.fill(0, n.coerceAtLeast(0), frame) + var written = 0 + while (written < frame && playbackRunning) { + val count = track.write(buf, written, frame - written) + if (count <= 0) { + if (playbackRunning) { + LogManager.addLog("ERROR", "radio", "playback write failed group=$group rc=$count written=$written/$frame") + mainHandler.post { if (audioTrack === track) stop() } + } + return + } + written += count + } /* абсолютный deadline: не даём Thread.sleep накопить систематический дрейф */ nextNs += frameNs diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 44cce9f6..9996c57f 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -2735,12 +2735,13 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRadioVadMode( } JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeRadioAudioPull( - JNIEnv* env, jobject thiz, jlong groupId, jshortArray out) { + JNIEnv* env, jobject thiz, jlong groupId, jshortArray out, jlong playedFrames) { (void)thiz; if (!out) return 0; jsize len = (*env)->GetArrayLength(env, out); jshort* data = (*env)->GetShortArrayElements(env, out, NULL); if (!data) return 0; + radio_audio_playback_progress((uint64_t)groupId, (uint64_t)playedFrames, 0); int n = utun_bridge_radio_audio_pull((uint64_t)groupId, (int16_t*)data, (int)len); (*env)->ReleaseShortArrayElements(env, out, data, 0); return (jint)n;