From 70b91cc7bb04adbda2f84114f9e7d06c427961a4 Mon Sep 17 00:00:00 2001 From: Bruno Deanoz Date: Wed, 24 Jun 2026 14:00:52 +0200 Subject: [PATCH] optimize Live Call latency: 50ms chunks, smaller playback buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Capture chunks 100ms → 50ms: halves input latency, model hears speech 50ms sooner per chunk - Playback buffer minBuf*4 → minBuf*2: reduces output buffering delay while LOW_LATENCY mode keeps playback glitch-free --- .../java/dev/kaizen/app/live/AudioPipeline.kt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt b/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt index 5ac6bad..dfe2c66 100644 --- a/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt +++ b/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt @@ -20,15 +20,14 @@ import kotlin.math.sqrt /** * Bidirectional audio pipeline for Live Call. * - * Capture: AudioRecord (16kHz PCM16 mono, VOICE_COMMUNICATION for hardware AEC) - * → 100ms chunks → Base64 → [onChunk] callback. + * Capture: AudioRecord (16kHz PCM16 mono, VOICE_COMMUNICATION + AEC) + * → 50ms chunks → Base64 → [onChunk] callback. * * Playback: AudioTrack (24kHz PCM16 mono, LOW_LATENCY, MODE_STREAM) * ← PCM16 chunks from server. * - * Mic-ducking: gain reduced to 10% while the KI speaks (prevents echo feedback - * loop on devices with weak AEC). Not a digital gain — we skip sending chunks - * whose RMS is below the ducking threshold. + * Echo prevention: AcousticEchoCanceler (hardware) + hard ducking + * (zero mic chunks while model speaks). */ class AudioPipeline(private val context: Context) { @@ -41,11 +40,9 @@ class AudioPipeline(private val context: Context) { private const val CHANNEL_OUT = AudioFormat.CHANNEL_OUT_MONO private const val ENCODING = AudioFormat.ENCODING_PCM_16BIT - private const val CHUNK_MS = 100 - private const val CAPTURE_CHUNK_SAMPLES = CAPTURE_SAMPLE_RATE * CHUNK_MS / 1000 // 1600 - private const val CAPTURE_CHUNK_BYTES = CAPTURE_CHUNK_SAMPLES * 2 // 3200 - - private const val DUCKING_GAIN = 0.10f + private const val CHUNK_MS = 50 + private const val CAPTURE_CHUNK_SAMPLES = CAPTURE_SAMPLE_RATE * CHUNK_MS / 1000 // 800 + private const val CAPTURE_CHUNK_BYTES = CAPTURE_CHUNK_SAMPLES * 2 // 1600 } var onChunk: ((base64Pcm: String) -> Unit)? = null @@ -164,7 +161,7 @@ class AudioPipeline(private val context: Context) { .setChannelMask(CHANNEL_OUT) .build() ) - .setBufferSizeInBytes(maxOf(minBuf * 4, 24_000 * 2)) + .setBufferSizeInBytes(maxOf(minBuf * 2, 24_000)) .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY) .setTransferMode(AudioTrack.MODE_STREAM) .build()