Reduce Android call playback startup buffering and log output queue
This commit is contained in:
1 parent
4c2d8aa4cc
commit
d1cbb64884
3 files changed
+35
-2
No files matched your search
@@ -598,6 +598,18 @@ QCamera и восстанавливает его после остановки;
|
|||||||
не мешает работе камеры. Лог video local preview отдельно показывает частоту и время обновления миниатюры.
|
не мешает работе камеры. Лог video local preview отдельно показывает частоту и время обновления миниатюры.
|
||||||
Общий check.sh: 154/154, proxy 9/9, burst/load PASS; Qt codec/clock и Android 81 unit PASS.
|
Общий 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,
|
Подтверждённая причина фризов раз в 1–2с: коррекция capture timestamp меняла audio source_epoch,
|
||||||
а обе платформы сбрасывали video decoder и ждали KEY. Теперь source_epoch обновляет
|
а обе платформы сбрасывали video decoder и ждали KEY. Теперь source_epoch обновляет
|
||||||
аудиопривязку без потери video references, готовых кадров и последнего показанного PTS.
|
аудиопривязку без потери video references, готовых кадров и последнего показанного PTS.
|
||||||
|
|||||||
@@ -134,6 +134,12 @@ capture/play потоки независимо создают и запуска
|
|||||||
INFO-логи Android показывают длительности настройки режима, регистрации callback, BT-профиля, выбора маршрута,
|
INFO-логи Android показывают длительности настройки режима, регистрации callback, BT-профиля, выбора маршрута,
|
||||||
создания/старта устройств, первого чтения микрофона и первой записи в AudioTrack, а также готовность другого направления.
|
создания/старта устройств, первого чтения микрофона и первой записи в AudioTrack, а также готовность другого направления.
|
||||||
Общий `call_audio` логирует время ожидания первого PCM; запись в 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. Буфер звонка и восстановление
|
## 7. Буфер звонка и восстановление
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.media.AudioRecord
|
|||||||
import android.media.AudioTimestamp
|
import android.media.AudioTimestamp
|
||||||
import android.media.AudioTrack
|
import android.media.AudioTrack
|
||||||
import android.media.MediaRecorder
|
import android.media.MediaRecorder
|
||||||
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
@@ -175,6 +176,14 @@ class CallAudioEngine {
|
|||||||
return@runWorker
|
return@runWorker
|
||||||
}
|
}
|
||||||
applyTrackOutputDevice(router.currentOutputDevice)
|
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 trackCreateMs = SystemClock.elapsedRealtime() - createdAt
|
||||||
val playedAt = SystemClock.elapsedRealtime()
|
val playedAt = SystemClock.elapsedRealtime()
|
||||||
track.play()
|
track.play()
|
||||||
@@ -431,6 +440,7 @@ class CallAudioEngine {
|
|||||||
val head = CallPlaybackHead()
|
val head = CallPlaybackHead()
|
||||||
var clockFailed = false
|
var clockFailed = false
|
||||||
var logAtUs = 0L
|
var logAtUs = 0L
|
||||||
|
var lastUnderruns = 0
|
||||||
while (active) {
|
while (active) {
|
||||||
val track = audioTrack ?: return
|
val track = audioTrack ?: return
|
||||||
val n = NativeLib.callAudioPull(callId, buf)
|
val n = NativeLib.callAudioPull(callId, buf)
|
||||||
@@ -474,9 +484,14 @@ class CallAudioEngine {
|
|||||||
}
|
}
|
||||||
if (nowUs - logAtUs >= 1_000_000 && NativeLib.callAudioClock(callId, nowUs, snapshot)) {
|
if (nowUs - logAtUs >= 1_000_000 && NativeLib.callAudioClock(callId, nowUs, snapshot)) {
|
||||||
logAtUs = nowUs
|
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]} " +
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user