Browse Source

Reduce Android call playback startup buffering and log output queue

master
evgeny 5 days ago
parent
commit
d1cbb64884
  1. 12
      doc/call_video.txt
  2. 6
      doc/chat_call_arch.md
  3. 19
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallAudioEngine.kt

12
doc/call_video.txt

@ -598,6 +598,18 @@ QCamera и восстанавливает его после остановки;
не мешает работе камеры. Лог video local preview отдельно показывает частоту и время обновления миниатюры.
Общий check.sh: 154/154, proxy 9/9, burst/load PASS; Qt codec/clock и Android 81 unit PASS.
Диагностика задержки desktop -> SM: локальный preview быстрый, но capture_age_us=-180000..-216000,
TX queue=6–8, queue_age_us=184000..248000, tail_ahead_us около 200000.
Первый запоздавший callback камеры привязывал относительный PTS к слишком позднему now;
следующие кадры оказывались в будущем и корректно ожидали audio barrier.
CallVideoCaptureClock теперь ограничивает mapping временем прихода каждого кадра и корректирует
offset только назад; поздний callback не увеличивает offset. Регрессия исходного PTS начинает новую привязку.
Тест моделирует 220мс задержки первого callback, её постепенное исчезновение, поздний callback и перезапуск PTS.
capture_age_us/clock_correction_us показывают привязку; queue_age_us использует только BOOTTIME,
tail_ahead_us сравнивает media PTS в MONOTONIC. Разность BOOTTIME и MONOTONIC не является задержкой.
Qt encoder/clock PASS; изменение требует повторного живого звонка после перезапуска ChatGUI.
Лог исходного дефекта: /tmp/call-latency-new-sample.log.
Подтверждённая причина фризов раз в 1–2с: коррекция capture timestamp меняла audio source_epoch,
а обе платформы сбрасывали video decoder и ждали KEY. Теперь source_epoch обновляет
аудиопривязку без потери video references, готовых кадров и последнего показанного PTS.

6
doc/chat_call_arch.md

@ -134,6 +134,12 @@ capture/play потоки независимо создают и запуска
INFO-логи Android показывают длительности настройки режима, регистрации callback, BT-профиля, выбора маршрута,
создания/старта устройств, первого чтения микрофона и первой записи в AudioTrack, а также готовность другого направления.
Общий `call_audio` логирует время ожидания первого PCM; запись в AudioTrack может содержать начальную тишину.
Порог запуска AudioTrack — два блока PCM (40мс), отдельно от ёмкости буфера на API 31+;
на API 26–30 запрашивается такой эффективный размер буфера, с учётом ограничений устройства.
Прежнее заполнение всей ёмкости на Samsung оставляло около 140мс между written и played
(на desktop 60–80мс). Лог `playback buffering` показывает принятый порог и ёмкость;
ежесекундный `clock` — queuedUs, текущий порог и underruns, прирост underruns имеет уровень WARN.
Это очередь AudioTrack, а не полная сквозная задержка и не сетевой jitter reserve.
## 7. Буфер звонка и восстановление

19
tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallAudioEngine.kt

@ -9,6 +9,7 @@ import android.media.AudioRecord
import android.media.AudioTimestamp
import android.media.AudioTrack
import android.media.MediaRecorder
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
@ -175,6 +176,14 @@ class CallAudioEngine {
return@runWorker
}
applyTrackOutputDevice(router.currentOutputDevice)
// Ёмкость устройства не должна становиться постоянной задержкой звонка.
// Два блока оставляют запас на пробуждение playback-потока каждые 20 мс.
val startFrames = FRAME_SAMPLES * 2
val actualStart = if (Build.VERSION.SDK_INT >= 31) track.setStartThresholdInFrames(startFrames)
else track.setBufferSizeInFrames(startFrames)
LogManager.addLog(if (actualStart > 0) "INFO" else "WARN", "CallAudio",
"playback buffering id=$id requestedStart=$startFrames actualStart=$actualStart " +
"bufferFrames=${track.bufferSizeInFrames} capacityFrames=${track.bufferCapacityInFrames}")
val trackCreateMs = SystemClock.elapsedRealtime() - createdAt
val playedAt = SystemClock.elapsedRealtime()
track.play()
@ -431,6 +440,7 @@ class CallAudioEngine {
val head = CallPlaybackHead()
var clockFailed = false
var logAtUs = 0L
var lastUnderruns = 0
while (active) {
val track = audioTrack ?: return
val n = NativeLib.callAudioPull(callId, buf)
@ -474,9 +484,14 @@ class CallAudioEngine {
}
if (nowUs - logAtUs >= 1_000_000 && NativeLib.callAudioClock(callId, nowUs, snapshot)) {
logAtUs = nowUs
LogManager.addLog("DEBUG", "CallAudio",
val underruns = track.underrunCount
val threshold = if (Build.VERSION.SDK_INT >= 31) track.startThresholdInFrames else track.bufferSizeInFrames
val queuedUs = (snapshot[3] - snapshot[2]).coerceAtLeast(0) * 1_000_000 / SAMPLE_RATE
LogManager.addLog(if (underruns > lastUnderruns) "WARN" else "DEBUG", "CallAudio",
"clock epoch=${snapshot[0]} source=${snapshot[1]} played=${snapshot[2]} written=${snapshot[3]} " +
"ptsUs=${snapshot[4]} valid=${snapshot[5]} measured=${snapshot[6]} heldUs=${snapshot[7]}")
"ptsUs=${snapshot[4]} valid=${snapshot[5]} measured=${snapshot[6]} heldUs=${snapshot[7]} " +
"queuedUs=$queuedUs bufferFrames=${track.bufferSizeInFrames} startFrames=$threshold underruns=$underruns")
lastUnderruns = underruns
}
}

Loading…
Cancel
Save