Reduce Linux voice capture latency and verify device timing

This commit is contained in:
evgeny committed 2026-10-15 17:26:09 +02:00
1 parent 3276204196
commit 7477da42a5
11 files changed
+342 -38

No files matched your search

+96 -1
View File
@@ -382,7 +382,7 @@ LeakSanitizer; LeakSanitizer запускается вне песочницы. M
ThreadSanitizer. Изолированный интеграционный запуск:
```sh
python3 tools/chatgui/tests/run_pulse_recovery.py
dbus-run-session -- python3 tools/chatgui/tests/run_pulse_recovery.py
```
Итоговый прогон 2026-10-02: сборка `vibechat` успешна, CTest — 11/11,
@@ -407,6 +407,101 @@ async-тесты исключены из дальнейших запусков.
AEC в длительном реальном звонке этими проверками не подтверждены. Reference
охватывает звук этого приложения на том же выходе, согласно выбранному контракту.
## Период Linux/PulseAudio и сравнительный замер
`AudioDevice::requestLowLatency()` задаёт запрос 10 мс для capture звонка/рации.
Он применяется при каждом открытии, включая recovery и fallback.
Остальные backends сохраняют прежние параметры.
Общий и отдельный voice output сохраняют прежний период: запрос 10 мс
на проверенной системе не снизил измеренную задержку воспроизведения.
Кадры AEC/кодека остаются 20 мс; `noFixedSizedCallback` позволяет обслуживать
фактические порции backend без дополнительного накопления в miniaudio.
Это запрос задержки, а не гарантия размера callback. PulseAudio согласует
`tlength`, `fragsize`, `minreq`, `maxlength`, `prebuf`; PipeWire также ограничивает
quantum и минимальные порции. `AudioDiag` при init выводит внутренний PCM-формат
и согласованные параметры в миллисекундах (`-1` — значение по умолчанию).
Пересчёт использует внутренние rate/channels/format, а не формат приложения.
Секундные clock-отчёты содержат `latency_us`/`backend_us` и `block_us` в виде
`min/avg/max`. В задержку включены только измеренные timing snapshots;
`estimated_blocks`/`measured` позволяют отличить их отсутствие от нулевой задержки.
Для времени исполнения callback включать `call=trace`, для состояния AEC —
`aec=debug`. Логи агрегируются вне аппаратного callback.
Ручной режим существующего стенда использует настоящие устройства и голосовые
workers, не сохраняет микрофонный PCM и не отправляет его по сети. Выход звонка
без удалённого собеседника обслуживается тишиной; акустическое подавление эха
этот режим не измеряет. Аргументы: capture frames, playback frames, секунды,
необязательный режим `call` (по умолчанию), `radio` или `output`, затем путь лога.
Ноль frames оставляет стандартный период backend; override применяется только
в тестовом executable, до настоящего `ma_device_init`.
```sh
cmake --build tools/chatgui/build --target test_audio_recovery -j4
tools/chatgui/build/test_audio_recovery --latency 960 0 60 > /tmp/audio-baseline.log 2>&1
tools/chatgui/build/test_audio_recovery --latency 480 0 60 > /tmp/audio-capture10.log 2>&1
tools/chatgui/build/test_audio_recovery --latency 960 480 60 > /tmp/audio-playback10.log 2>&1
tools/chatgui/build/test_audio_recovery --latency 480 480 60 > /tmp/audio-both10.log 2>&1
```
Запуски последовательные: несколько потоков могут менять общий quantum сервера.
Сравнивать одинаковые маршруты и нагрузку, отдельно от старта/остановки и после
стабилизации адаптивного PCM-резерва. Проверять backend latency, возраст первого
сэмпла перед готовым TX-кадром, очередь после mix, starvation, holes, потери PCM,
AEC reference и событий диагностики. Затем повторить baseline и проверить
выбранный вариант под нагрузкой. Меньший возраст PCM в capture callback сам по
себе не доказывает такой же выигрыш до кодирования: worker собирает кадры 20 мс.
`test_voice_audio_io` дополнительно проверяет порции по 256 samples: порядок PCM,
временные метки, границы mute/PTT и позицию частичного playback. AEC проверяется
при разных порциях capture/reference (256/480 и 480/256) с противофазным stereo
reference. Очереди по-прежнему ограничены числом событий; уменьшение периода
не увеличивает их ёмкость в миллисекундах.
Сравнение 2026-10-15 на встроенном HDA, PipeWire 1.6.2, 48 кГц. Каждый вариант
работал 60 секунд; таблица содержит средние по callback/кадрам после 45 секунд
прогрева (PCM-резерв к этому моменту уменьшился до одного блока).
| Запрос capture / playback | Capture backend, мс | Первый сэмпл готового TX-кадра, мс | Playback backend, мс | PCM после mix, мс |
| --- | ---: | ---: | ---: | ---: |
| 20 мс / default 25 мс | 30,69 | 34,53 | 16,32 | 18,67 |
| 10 мс / default 25 мс | 16,02 | 29,79 | 16,15 | 18,67 |
| 20 мс / 10 мс | 31,51 | 34,97 | 18,09 | 24,00 |
| 10 мс / 10 мс | 16,13 | 29,66 | 18,46 | 24,00 |
Изменение capture даёт около 14,7 мс на границе backend и около 4,7 мс перед
передачей полного кадра в голосовой стек. Для playback сервер согласовал
`tlength=16ms`, `minreq=5.333ms` при запросе 10 мс; callback уменьшился с 512
до 256 samples, но выигрыша по задержке не получилось. Поэтому в рабочем коде
изменён только capture. Значения в столбцах не следует складывать как независимые
участки общей задержки: capture backend входит в возраст TX-кадра, а очередь после
mix снята уже после извлечения текущего выходного блока из очереди.
В этих четырёх прогретых окнах не было потерь capture/reference PCM, выходного
starvation, backend underrun/overflow или потери событий диагностики.
Отдельные отсутствующие AEC references были и в baseline (4 кадра за окно),
и в остальных вариантах (4–5). Это существующее ограничение привязки reference,
а не проверка акустического качества. Первоначальный output starvation и
временное увеличение PCM-резерва учитываются отдельно от стабильного окна.
Повтор baseline дал 30,76 мс capture backend и 34,99 мс возраста TX-кадра.
Двухминутный прогон выбранного capture 10 мс под нагрузкой четырёх CPU-процессов
дал после прогрева 15,81 и 28,55 мс соответственно; PCM после mix — 18,67 мс.
Потерь PCM, backend underrun/overflow и потери событий диагностики в этом окне
не было. За 75 секунд окна зарегистрировано 25 отсутствующих AEC references.
Отдельный минутный прогон рации: capture backend в среднем 15,48 мс, без потерь PCM.
Сборка `vibechat` и 6 аудиотестов прошли. Изолированный интеграционный тест
подтвердил объединение выходных маршрутов и восстановление capture/playback после
перезапуска тестового PulseAudio. Запускать весь стенд через `dbus-run-session`,
чтобы имя его PulseAudio не конфликтовало с сервером пользовательской сессии.
В fixture перед `radio_audio_init` создаётся `radio_instance`, освобождаемый после
`radio_audio_destroy`; это исключает ложную ошибку регистрации mute callback.
Полный `check.sh` вне sandbox: 156/156, 9/9 proxy-интеграций, burst и load — PASS.
В sandbox сетевые тесты не проходят из-за запрета сокетов; этот запуск не является
проверкой сетевых регрессий.
## Референсы
Первичные исходники и документация просмотрены 2026-10-01; ссылки на master могут