аудио: убрать «просадки» — запас буферов + PLC на пропусках
- glasses: очередь capture→Opus 4→12 кадров (джиттер планировщика больше не роняет кадры до присвоения seq, отчего телефон не видел потерь); буфер AudioRecord 2→4 кадра - glasses: диагностика captured/encoded/dropped в логе (видно потери на источнике) - phone: AudioTrack-буфер 1→4 кадра (редкие underrun'ы давали щелчки); на пропуске seq вставляем PLC-кадры (было — сразу склейка); в лог добавлен underruns
This commit is contained in:
@@ -60,7 +60,9 @@ class AudioPlaybackManager(private val context: Context) {
|
||||
val encoding = AudioFormat.ENCODING_PCM_16BIT
|
||||
|
||||
val minBuffer = AudioTrack.getMinBufferSize(sampleRate, channelMask, encoding)
|
||||
val bufferSize = maxOf(minBuffer, AudioConfig.FRAME_BYTES)
|
||||
// Четыре кадра (40 мс) запаса: при буфере меньше этого редкие подвисания потока
|
||||
// приёма/GC опустошали очередь и давали слышимый «провал» (underrun).
|
||||
val bufferSize = maxOf(minBuffer, AudioConfig.FRAME_BYTES * 4)
|
||||
|
||||
track = AudioTrack.Builder()
|
||||
.setAudioAttributes(
|
||||
@@ -104,12 +106,25 @@ class AudioPlaybackManager(private val context: Context) {
|
||||
onMessage = { datagram ->
|
||||
if (!AudioDatagram.hasPayload(datagram)) return@connect
|
||||
val seq = AudioDatagram.seq(datagram)
|
||||
val t = track ?: return@connect
|
||||
if (lastSeq >= 0) {
|
||||
// Оборачивание Int через переполнение даёт «огромный» gap — отсекаем.
|
||||
val gap = seq - lastSeq - 1
|
||||
if (gap in 1 until 1000) lost += gap
|
||||
if (gap in 1 until MAX_GAP) {
|
||||
lost += gap
|
||||
// PLC: синтезируем заменители потерянных кадров, чтобы на месте
|
||||
// пропуска не было резкого «щелчка» на стыке.
|
||||
repeat(minOf(gap, MAX_CONCEAL)) {
|
||||
val n = try {
|
||||
decoder.decodeLost(pcmOut)
|
||||
} catch (e: Throwable) {
|
||||
-1
|
||||
}
|
||||
if (n > 0) t.write(pcmOut, 0, n * AudioConfig.CHANNELS)
|
||||
}
|
||||
}
|
||||
}
|
||||
lastSeq = seq
|
||||
val t = track ?: return@connect
|
||||
val decoded = try {
|
||||
decoder.decode(
|
||||
datagram,
|
||||
@@ -126,7 +141,7 @@ class AudioPlaybackManager(private val context: Context) {
|
||||
}
|
||||
frames++
|
||||
if (frames % 200 == 0) {
|
||||
Log.i(TAG, "playing: frames=$frames lost=$lost lastSeq=$seq")
|
||||
Log.i(TAG, "playing: frames=$frames lost=$lost underruns=${t.underrunCount} lastSeq=$seq")
|
||||
}
|
||||
},
|
||||
onConnected = {
|
||||
@@ -167,5 +182,11 @@ class AudioPlaybackManager(private val context: Context) {
|
||||
|
||||
private companion object {
|
||||
const val TAG = "AudioPlaybackMgr"
|
||||
|
||||
/** Пропуск seq больше этого — оборачивание Int или сбой, а не реальные потери. */
|
||||
const val MAX_GAP = 100
|
||||
|
||||
/** Максимум кадров подряд «дотягиваем» PLC, чтобы не залипнуть на длинной потере. */
|
||||
const val MAX_CONCEAL = 5
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user