optimize Live Call latency: 50ms chunks, smaller playback buffer
- 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
This commit is contained in:
parent
965f1876bf
commit
70b91cc7bb
1 changed files with 8 additions and 11 deletions
|
|
@ -20,15 +20,14 @@ import kotlin.math.sqrt
|
||||||
/**
|
/**
|
||||||
* Bidirectional audio pipeline for Live Call.
|
* Bidirectional audio pipeline for Live Call.
|
||||||
*
|
*
|
||||||
* Capture: AudioRecord (16kHz PCM16 mono, VOICE_COMMUNICATION for hardware AEC)
|
* Capture: AudioRecord (16kHz PCM16 mono, VOICE_COMMUNICATION + AEC)
|
||||||
* → 100ms chunks → Base64 → [onChunk] callback.
|
* → 50ms chunks → Base64 → [onChunk] callback.
|
||||||
*
|
*
|
||||||
* Playback: AudioTrack (24kHz PCM16 mono, LOW_LATENCY, MODE_STREAM)
|
* Playback: AudioTrack (24kHz PCM16 mono, LOW_LATENCY, MODE_STREAM)
|
||||||
* ← PCM16 chunks from server.
|
* ← PCM16 chunks from server.
|
||||||
*
|
*
|
||||||
* Mic-ducking: gain reduced to 10% while the KI speaks (prevents echo feedback
|
* Echo prevention: AcousticEchoCanceler (hardware) + hard ducking
|
||||||
* loop on devices with weak AEC). Not a digital gain — we skip sending chunks
|
* (zero mic chunks while model speaks).
|
||||||
* whose RMS is below the ducking threshold.
|
|
||||||
*/
|
*/
|
||||||
class AudioPipeline(private val context: Context) {
|
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 CHANNEL_OUT = AudioFormat.CHANNEL_OUT_MONO
|
||||||
private const val ENCODING = AudioFormat.ENCODING_PCM_16BIT
|
private const val ENCODING = AudioFormat.ENCODING_PCM_16BIT
|
||||||
|
|
||||||
private const val CHUNK_MS = 100
|
private const val CHUNK_MS = 50
|
||||||
private const val CAPTURE_CHUNK_SAMPLES = CAPTURE_SAMPLE_RATE * CHUNK_MS / 1000 // 1600
|
private const val CAPTURE_CHUNK_SAMPLES = CAPTURE_SAMPLE_RATE * CHUNK_MS / 1000 // 800
|
||||||
private const val CAPTURE_CHUNK_BYTES = CAPTURE_CHUNK_SAMPLES * 2 // 3200
|
private const val CAPTURE_CHUNK_BYTES = CAPTURE_CHUNK_SAMPLES * 2 // 1600
|
||||||
|
|
||||||
private const val DUCKING_GAIN = 0.10f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var onChunk: ((base64Pcm: String) -> Unit)? = null
|
var onChunk: ((base64Pcm: String) -> Unit)? = null
|
||||||
|
|
@ -164,7 +161,7 @@ class AudioPipeline(private val context: Context) {
|
||||||
.setChannelMask(CHANNEL_OUT)
|
.setChannelMask(CHANNEL_OUT)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setBufferSizeInBytes(maxOf(minBuf * 4, 24_000 * 2))
|
.setBufferSizeInBytes(maxOf(minBuf * 2, 24_000))
|
||||||
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
|
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
|
||||||
.setTransferMode(AudioTrack.MODE_STREAM)
|
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue