Сохрани накопленные исправления аудио, async и диагностики
This commit is contained in:
1 parent
b1e3193899
commit
bdcf8b5634
59 files changed
+4611
-1441
No files matched your search
@@ -0,0 +1,354 @@
|
||||
# Linux: диагностика и восстановление аудио
|
||||
|
||||
Проверка 2026-10-01: desktop chatgui, miniaudio/PulseAudio, общий голосовой стек,
|
||||
звонки, рация и запись сообщений. Исправления обработки PCM, владения устройствами
|
||||
и автоматическое восстановление реализованы. Ниже сохранены исходные результаты
|
||||
аудита; раздел «Реализация» описывает действующие контракты и проверки.
|
||||
|
||||
## Вывод
|
||||
|
||||
До исправлений тракт был некорректен при сбоях. Самое существенное: воспроизведение
|
||||
звонка зависит от наличия микрофонных кадров в duplex-ring. Его опустошение
|
||||
останавливает вызов пользовательского callback, оставляя хвост выходного буфера
|
||||
незаполненным. Восстановление jitter-буфера не исправляет остановившееся устройство.
|
||||
Отдельного управляющего механизма восстановления устройств не было.
|
||||
|
||||
В реализации восстановлены три гарантии: каждый запрос output полностью заполнен; обработка
|
||||
одного callback всегда ограничена; остановка/пересоздание устройства выполняются
|
||||
вне аудиопотока и не уничтожают сетевую сессию звонка.
|
||||
|
||||
## Что установлено по работающим клиентам
|
||||
|
||||
В локальном старом логе звонка `132e4a9698f47bdf` с Samsung SM_A525F наблюдались
|
||||
паузы callback примерно 0,5–2,6 секунды при зарегистрированной обработке 0–2 мс.
|
||||
Входящий буфер desktop достигал 15 секунд, росли потери, включалось ускорение 160%.
|
||||
На Samsung захват продолжал давать около 50 кадров/с, playback регистрировал
|
||||
underrun и тоны разрыва. RTT порядка 1–4 мс не объясняет эти паузы сам по себе.
|
||||
|
||||
Это признаки проблемы обслуживания локального аудио, но ещё не доказательство
|
||||
конкретной причины зависания PulseAudio. Старое измерение исключало время
|
||||
синхронных DEBUG-вызовов между концом обработки и обновлением timestamp.
|
||||
Новая диагностика охватывает весь callback, включая эти логи.
|
||||
Работающий GUI не перезапускался, поэтому новый инструмент ещё не записал этот
|
||||
сбой на реальном звонке. Лог Samsung сохранён в `/tmp/sm-audio-current.log`.
|
||||
|
||||
## Найденные дефекты и ограничения до исправлений
|
||||
|
||||
| Место | Поведение и последствия | Необходимая доработка |
|
||||
| --- | --- | --- |
|
||||
| `lib/miniaudio.h`, `ma_device__handle_duplex_callback_playback()` | При пустом capture-ring `acquire_read` возвращает success с нулём кадров. Выполняется `break`, output остаётся частично незаполненным, call callback не вызывается. PulseAudio затем отправляет весь mapped buffer. | Обеспечить полный output и продолжение playback при отсутствии capture. Нельзя делать commit_read для синтетических кадров. |
|
||||
| `ma_device_on_write__pulse()` | Успешная запись с нулём обработанных кадров не уменьшает остаток цикла. Возможно бесконечное вращение с блокированием того же PA mainloop, включая capture. | Немедленно выходить при отсутствии продвижения, возвращать управление mainloop. Логировать состояние и запрос, не пытаться записывать до успеха в этом callback. |
|
||||
| `ma_device_on_read__pulse()` | PA hole отбрасывается без подачи соответствующего количества нулевых кадров. Из capture timeline исчезает интервал, duplex-ring может опустеть. | Сохранять длительность hole тишиной либо явно обозначать discontinuity независимому capture-тракту. |
|
||||
| `lib/speex_aec.c`, `speex_aec_feed_playback()` | Обрабатывает только остаток одного 960-sample кадра, отбрасывая хвост входного блока. Контракт разрешает произвольный count. | Циклом потреблять весь count, складывать все полные кадры, сохранять остаток. |
|
||||
| `CallAudioEngine::setPlaybackDeviceIndex()` | После неудачного пересоздания `m_active` остаётся true при отсутствии устройства; `start` того же звонка возвращает успех. | Разделить желание продолжать звонок и работоспособность устройства. Ошибка запуска переводит устройство в recovering. |
|
||||
| `RadioAudioEngine::teardownDevice()` | Если playback init успешен, а start нет, uninit пропускается: он зависит от флага started. Объект удаляется вместе с незавершёнными ресурсами. | Разделить initialized и started; каждый успешный init обязательно завершать uninit. |
|
||||
| `MainWindow::~MainWindow()` / `SoundManager::shutdown()` | Общий context освобождается вместе с engine, но звонок и рация здесь явно не останавливаются. Их singleton-объекты могут оставлять живые устройства. `m_context` также не обнуляется. | Сначала остановить все зависимые устройства и дождаться callbacks, затем освобождать общий context. |
|
||||
| `AudioRecorder` | Callback выбирает глобальный `g_recorder`, игнорируя `pUserData`. Второй recorder, в том числе тест микрофона, заменяет получателя. Флаги/уровень/вектор waveform используются из GUI и аудиопотока без согласованной синхронизации. | Использовать `pUserData`; передавать уровни атомарно, waveform — безопасным snapshot; буфер записи должен иметь однозначного владельца. |
|
||||
|
||||
До исправлений первые два дефекта воспроизводились стендом без аппаратуры:
|
||||
duplex оставлял sentinel в output при starvation, а PA-write без продвижения
|
||||
требовал внешней остановки. AEC из 1200 + 720 samples сохранял один кадр вместо
|
||||
двух. Теперь эти проверки требуют полного output, самостоятельного возврата
|
||||
из callback и двух сохранённых AEC-кадров; результаты приведены ниже.
|
||||
|
||||
Capture-AEC также обрабатывает не более одного кадра за вызов. В звонке его
|
||||
вызывают ровно с 960 samples, поэтому текущий call feed этот случай не нарушает.
|
||||
При расширении API нельзя объявлять произвольный count без согласованного размера
|
||||
выходного буфера. `speex_aec_reset()` уже существует: новый API для сброса самого
|
||||
AEC не нужен, но нужен согласованный сброс всего локального voice-тракта.
|
||||
|
||||
## Чистота реализации
|
||||
|
||||
Этот раздел фиксирует исходный аудит; изменения владения и ограничение работы
|
||||
callback описаны в разделе «Реализация».
|
||||
|
||||
Нормальная структура уже есть: GUI владеет устройствами, сервисный слой кодирует
|
||||
Opus и обрабатывает jitter, сетевой поток доставляет пакеты. Нет необходимости
|
||||
переписывать весь стек или заводить новый backend.
|
||||
|
||||
Однако границы владения не завершены: context фактически принадлежит engine
|
||||
звуковых уведомлений, а используется независимыми устройствами звонка/рации.
|
||||
Флаги active/started/initialized местами смешивают три разных состояния.
|
||||
Выбор input применяется к recorder, звонок использует default input, рация —
|
||||
default устройства. Output звонка и SoundManager тоже выбираются отдельно.
|
||||
Индекс перечисления не является устойчивой идентичностью устройства после hotplug.
|
||||
В radio capture fallback `useDefault` фактически повторяет ту же попытку:
|
||||
`pDeviceID` и так NULL в обеих ветках. Callbacks рации запускаются раньше
|
||||
подготовки capture-буфера и установки VAD; порядок запуска нужно привести к
|
||||
«сначала состояние и буферы, затем устройства», чтобы исключить гонки при старте.
|
||||
|
||||
Callback не является строго realtime-safe. Capture удерживает `g_mtx` во время
|
||||
AEC/AGC/Opus; отправка выделяет память и вызывает `uasync_post`. Playback вызывает
|
||||
SoundTouch и код с возможным расширением контейнеров, mutex и синхронными логами.
|
||||
Приём пакетов тоже может держать mutex при работе с jitter и логировании.
|
||||
Плохие Opus-кадры могут последовательно выбираться из большой очереди в одном
|
||||
pull: нужен предел работы на callback. Новая диагностика покажет, где это
|
||||
действительно нарушает deadline. Перенос всего DSP в ещё один worker заранее
|
||||
увеличит число очередей и точек синхронизации без установленной необходимости.
|
||||
|
||||
AEC получает reference только звонка. Звуки отдельного SoundManager в него не
|
||||
попадают; delay по умолчанию фиксирован, не вычисляется по фактической задержке
|
||||
устройств. Это ограничение качества эхоподавления, а не установленная причина
|
||||
текущего зависания.
|
||||
|
||||
Jitter умеет повторно накопить резерв после сетевого underrun, но диапазон tempo
|
||||
ограничен 1,0–1,6. Ускорение убирает избыток, а замедления для компенсации более
|
||||
медленных часов отправителя нет. При устойчивом отрицательном дрейфе резерв будет
|
||||
снова опустошаться. Это отдельная задача длительных звонков: после восстановления
|
||||
устройств проверить наклон глубины буфера и при необходимости добавить небольшую
|
||||
компенсацию дрейфа в обе стороны, не смешивая её с быстрым catch-up после паузы.
|
||||
|
||||
Принудительные 480 frames задают период 10 мс при 48 кГц. Собственный PA default
|
||||
miniaudio — 25 мс с комментарием о проблемах PipeWire при периодах меньше ~20 мс.
|
||||
Период backend и Opus-кадр 20 мс должны оставаться независимыми. Увеличивать
|
||||
период до исправления AEC нельзя: 1200-sample блоки уже воспроизводят потерю хвоста.
|
||||
После исправления разумно сначала проверить default backend и измерить задержку.
|
||||
|
||||
## Предлагаемое восстановление без большого усложнения
|
||||
|
||||
### 1. Сначала восстановить инварианты обработки и владения
|
||||
|
||||
Исправить полный output, zero-progress, AEC accumulation и uninit при неудачном
|
||||
start. Перед освобождением context явно остановить recorder, звонок, рацию и
|
||||
прочие его устройства. До остановки отменять recovery timer; после join можно
|
||||
сбрасывать voice-ресурсы. Проверять возвраты feed/start/init и логировать причину.
|
||||
|
||||
Самый небольшой вариант — оставить один duplex-device: при нехватке capture
|
||||
подать в call callback нулевой input, полностью сформировать playback и не
|
||||
продвигать реальный capture-ring за синтетические samples. Это чинит непосредственный
|
||||
дефект, но сохраняет общий жизненный цикл input/output и синтетический TX по
|
||||
playback-clock.
|
||||
|
||||
Для устойчивости к исчезновению микрофона предпочтительнее два устройства
|
||||
capture/playback внутри существующего `CallAudioEngine`, как уже сделано в рации.
|
||||
Capture только накапливает 960 samples и вызывает feed; playback независимо
|
||||
вызывает pull и всегда заполняет output. Это два handle с одним владельцем,
|
||||
без нового DSP-worker или дополнительной PCM-очереди. Потребуется уточнить
|
||||
потоковый контракт voice-слоя: один capture-writer и один playback-writer,
|
||||
согласованный доступ к AEC, остановка обоих до release. Текущий `g_mtx` уже
|
||||
защищает AEC, но остальные общие поля нужно проверить перед разделением.
|
||||
|
||||
### 2. Разделить краткий underrun и неисправность устройства
|
||||
|
||||
| Событие | Действие |
|
||||
| --- | --- |
|
||||
| Нет сетевого PCM, callback продолжает приходить | Тишина/существующий gap tone; jitter автоматически накапливает данные. Устройство не пересоздавать. |
|
||||
| Один backend underrun, новые кадры принимаются | Подать следующий полностью заполненный буфер; собрать счётчик. Не сбрасывать AEC и не запускать restart на каждый xrun. |
|
||||
| Hole/переполнение capture | Учесть потерянную длительность; краткую потерю пережить, длительную отметить discontinuity для AEC. |
|
||||
| Поток failed/stopped или нет продвижения кадров длительное время | Перейти в recovering и пересоздать затронутое устройство вне callback. |
|
||||
| Suspend/resume ОС | Учитывать suspend как отдельную причину, восстановление проверять после resume; не запускать частый restart-loop во время сна. |
|
||||
| Потерян общий backend/context | Однократно остановить всех его пользователей, восстановить владельца, затем требуемые устройства. |
|
||||
|
||||
Один owner timer и состояния `Stopped / Running / Recovering` достаточны.
|
||||
`wanted` хранит желание пользователя продолжать; generation меняется при stop,
|
||||
новом звонке и смене устройства. Каждая отложенная попытка проверяет generation
|
||||
и wanted — законченный звонок никогда не возрождается.
|
||||
|
||||
Callbacks только обновляют атомарные счётчики успешно обработанных кадров и
|
||||
признак backend failure. Watchdog проверяет фактический прогресс отдельно по
|
||||
направлениям. Тишина в PCM не означает остановку устройства. Reporter диагностической
|
||||
очереди не должен становиться watchdog: его события могут теряться.
|
||||
|
||||
Начальные значения для проверки: timer 200 мс, подозрение при отсутствии прогресса
|
||||
500 мс после startup grace; повторные попытки через 250/500/1000 мс с потолком
|
||||
2 секунды. Это предлагаемые настройки, не доказанные универсальные пределы.
|
||||
Suspend и фактический согласованный backend period должны учитываться отдельно.
|
||||
Нельзя обещать bounded recovery, пока init/stop/uninit сами могут зависнуть:
|
||||
сначала устранить spin-loop и проверить время backend-операций. Если они реально
|
||||
блокируют GUI, вынести только управление устройствами в один owner-thread,
|
||||
сохранив тот же автомат состояний.
|
||||
|
||||
На пересоздании сбросить capture-accumulator, AEC delay/filter и локальный PCM/
|
||||
stretch, сохранив call ID и сетевую сессию. После длительной аппаратной паузы
|
||||
устаревшую речь нужно явно отбросить до актуального резерва jitter, иначе
|
||||
15 секунд накопленного звука при скорости 160% разгружаются примерно 25 секунд.
|
||||
Этот сброс применять только при аппаратной discontinuity; обычный сетевой jitter
|
||||
не должен автоматически приводить к отбрасыванию очереди.
|
||||
|
||||
Устройство хранить по backend ID либо как default, не по индексу. При его
|
||||
исчезновении попробовать default и явно сообщить о смене; выбранное устройство
|
||||
можно вернуть при появлении. Нужны ошибки с ID/именем/направлением и результатом
|
||||
попытки. Для PA-ошибок следующий уровень диагностики — stream/context state и
|
||||
`pa_context_errno`, потому что один `-1` от API не объясняет причину.
|
||||
|
||||
### 3. Проверить сценарии, а не только успешный старт
|
||||
|
||||
После реализации добавить проверки: capture starvation 20/500 мс и возврат;
|
||||
hole; AEC блоки, пересекающие границу кадра; zero writable; ошибка записи;
|
||||
исчезновение выбранного устройства и появление default; suspend/resume;
|
||||
перезапуск звукового сервера; stop во время backoff; закрытие GUI при активном
|
||||
звонке/рации. Проверять полный output, bounded очереди и работу callback,
|
||||
возобновление прогресса, отсутствие callbacks после release и отсутствие
|
||||
повторного старта после пользовательского stop.
|
||||
|
||||
## Как читать добавленную диагностику
|
||||
|
||||
В GUI-категориях включить `call=debug` либо задать отдельную строку в INI:
|
||||
|
||||
```ini
|
||||
[debug]
|
||||
call=debug
|
||||
```
|
||||
|
||||
Обычные сводки идут на DEBUG, аномалии — WARN/ERROR; miniaudio info перенесён
|
||||
на DEBUG, подробный backend log — TRACE. Все устройства используют категорию
|
||||
call, включая recorder, radio и sound-engine; role и ID различают их в строках.
|
||||
Не включать глобальный trace для всего проекта при измерении аудио.
|
||||
|
||||
Reporter раз в секунду печатает stage, tid, callbacks/s, requested frames/s,
|
||||
максимальный интервал между входами, полное wall-time обработки и thread CPU-time,
|
||||
незавершённый вход и время с последнего входа. `work_max` и `cpu_max` — отдельные
|
||||
максимумы за окно, они не обязательно относятся к одному вызову.
|
||||
|
||||
| Stage | Что измеряется |
|
||||
| --- | --- |
|
||||
| loop | Один `pa_mainloop_iterate`, включая ожидание и вложенные callbacks. Долгое время само по себе не доказывает тяжёлый DSP. |
|
||||
| read / write | Полный backend read/write callback. Frames/s здесь — запрошенные, не гарантированно обработанные кадры. |
|
||||
| map | `writable_size` и `begin_write`; пустой/ошибочный выход тоже закрывает измерение. |
|
||||
| submit | Вызов `pa_stream_write`. |
|
||||
| duplex | Перенос capture-ring в duplex-output; detail содержит глубину capture-ring. |
|
||||
| callback | Полный miniaudio data callback, включая код приложения и его синхронные логи. |
|
||||
| feed / pull | Capture DSP/encode/send и playback jitter/stretch/tone в звонке. |
|
||||
|
||||
`unfilled_frames` — незаполненный output, `hole_frames` — потеря capture timeline,
|
||||
`capture_drop_frames` — переполнение capture-ring, `no_progress` — PA-write без
|
||||
продвижения. `backend_underruns` — отдельный callback самого PA-stream; это не
|
||||
то же самое, что пустой сетевой jitter. `backend playback started/resumed`
|
||||
показывает фактическое начало/возобновление playback. Notification содержит
|
||||
числовые miniaudio type/state.
|
||||
|
||||
Первое событие каждого типа за окно имеет tid и CLOCK_MONOTONIC timestamp;
|
||||
повторения суммируются. Для задержавшихся потоков reporter читает `/proc` wchan
|
||||
и накопленные schedstat CPU/ready-wait/slices. Сравнивать соседние snapshots:
|
||||
сами totals не являются длительностью текущей паузы. CPU-time намного меньше
|
||||
wall-time означает ожидание/вытеснение; wchan помогает отличить poll от mutex/
|
||||
I/O wait. Это снимок в момент отчёта, не исторический стек зависшего вызова.
|
||||
|
||||
Producer не выделяет память, не ждёт mutex reporter и не пишет диагностические
|
||||
логи: ограниченная MPSC-очередь 8192 события, не более четырёх CAS-попыток.
|
||||
Reporter не разыменовывает ma_device/pa_stream. Переполнение логируется как
|
||||
`diagnostic events lost`; такое окно нельзя считать полным. Инструментация
|
||||
добавляет получение часов/TID и атомарные операции, поэтому overhead ненулевой.
|
||||
Частые события call jitter/AEC/TX теперь публикуются счётчиками и выводятся из
|
||||
uasync вне аудиомьютекса. Часть существующих логов рации остаётся синхронной;
|
||||
тракт не объявляется строго realtime-safe.
|
||||
После close устройство удаляется из reporter: для устройств короче секунды
|
||||
доступны lifecycle и первые аномалии, но не полная секундная сводка.
|
||||
|
||||
Новый binary собран в `tools/chatgui/build/vibechat`; для нового измерения нужен
|
||||
его обычный перезапуск. Изменение файла binary не обновляет уже запущенный процесс.
|
||||
|
||||
## Реализация
|
||||
|
||||
`AudioDevice` — небольшой владелец одного miniaudio device в GUI-потоке.
|
||||
Успешный init всегда имеет парный uninit, включая ошибку start. Callback и его
|
||||
данные готовятся до start. Close останавливает/join-ит worker перед очисткой
|
||||
callback. Прогресс — атомарный счётчик завершённых кадров, независимый от reporter.
|
||||
Каждый владелец проверяет свои устройства Qt-таймером 200 мс: stopped либо 1 с
|
||||
без продвижения требуют пересоздания. Suspend обновляет grace; единичный xrun
|
||||
не запускает restart. Backoff: 250/500/1000/2000 мс, сброс после прогресса.
|
||||
|
||||
Capture и playback звонка теперь независимы. Период выбирает backend; аппаратный
|
||||
чанк не обязан совпадать с 20-мс Opus-кадром. Рация заполняет большой output
|
||||
несколькими pulls по максимум 20 мс. AEC сохраняет весь render, включая хвосты
|
||||
1200/720 samples, а capture принимает только полный кадр. При аппаратном recovery
|
||||
после join сбрасываются capture accumulator, AEC и локальный jitter/stretch;
|
||||
из encoded backlog сохраняется только свежий резерв. Call ID и группа сохраняются.
|
||||
|
||||
`SoundManager` отдельно владеет общим контекстом и устройством вывода.
|
||||
`ma_engine` работает без своего устройства: mixer, звуки и PCM cursor переживают
|
||||
recovery. При failed context синхронный сигнал сначала закрывает всех клиентов,
|
||||
затем context освобождается. После повторного init клиенты открываются по desired
|
||||
state. Stop выключает recovery; таймер завершения звонка проверяет generation.
|
||||
MainWindow останавливает звонок/рацию/recorder до SoundManager и сетевого ядра.
|
||||
Регистрация desktop voice ops выполняется в потоке ядра.
|
||||
|
||||
PulseAudio control-waits ограничены 1 с на операцию, mainloop iterates неблокирующие.
|
||||
При timeout pending operation отменяется до возврата из функции, чтобы callback
|
||||
не получил просроченный stack pointer. Partial init закрывает PA context/mainloop
|
||||
и события miniaudio. Device worker распознаёт failed context/stream; лог содержит
|
||||
состояния и PA errno. Capture hole подаётся как тишина той же длительности;
|
||||
zero writable/zero converter progress возвращают управление. Duplex output заранее
|
||||
полностью инициализируется, starvation не выключает playback callback.
|
||||
|
||||
TX ограничен восемью задачами до помещения в uasync, возраст — максимум 200 мс.
|
||||
Generation исключает передачу кадров старого звонка/захвата. Ещё не обработанные
|
||||
старые задачи учитываются и при повторном start, поэтому очередь ограничена
|
||||
между поколениями. Task и аргументы имеют одну аллокацию; destroy очереди освобождает
|
||||
их и для media, и для PTT control. Bad Opus в звонке учитывается в бюджете decode
|
||||
на pull. Ошибки encode/очереди/выделения памяти/возраста различаются в сводке TX.
|
||||
|
||||
Устройства сохраняются как backend ID вместо индекса. При init/start failure
|
||||
явного выбора пробуется default, причина логируется. Выбранный ID сохраняется;
|
||||
вернуться к нему можно повторным выбором или при следующем recovery, автоматический
|
||||
опрос hotplug не добавлен. Recorder использует pUserData своего устройства,
|
||||
компрессор настраивается до записи, UI получает атомарный уровень и длительность
|
||||
по записанным кадрам. Настройки компрессора в ходе записи применяются к следующей.
|
||||
|
||||
## Проверки реализации
|
||||
|
||||
`cmake --build tools/chatgui/build -j4` — успешно.
|
||||
|
||||
```sh
|
||||
ctest --test-dir tools/chatgui/build --output-on-failure \
|
||||
-R 'test_(linux_audio|audio_diagnostics|audio_recovery|call_jitter|radio_audio|radio_jitter|voice_file)$'
|
||||
```
|
||||
|
||||
7/7 успешно. Проверены целостность MPSC, bounded overflow/restart reporter,
|
||||
duplex starvation, PA zero-progress, capture hole, deadline/cancel, сохранение
|
||||
AEC render, bad Opus budget, partial start/init, две записи одновременно,
|
||||
восстановление только playback, потеря общего context и повторные отказы init,
|
||||
stop во время backoff, старый end timer, сохранение PCM cursor, selected→default,
|
||||
TX limit между поколениями и освобождение pending PTT при destroy.
|
||||
|
||||
Изолированная Debug-сборка в `/tmp/utun-audio-asan`: четыре теста
|
||||
`audio_recovery/linux_audio/call_jitter/radio_audio` проходят с ASAN/UBSAN и
|
||||
LeakSanitizer. Запуск вне песочницы нужен LeakSanitizer для проверки потоков.
|
||||
|
||||
Реальный перезапуск сервера:
|
||||
|
||||
```sh
|
||||
python3 tools/chatgui/tests/run_pulse_recovery.py
|
||||
```
|
||||
|
||||
Тест создаёт собственные PipeWire, PulseAudio и WirePlumber в `/tmp`, отключает
|
||||
аппаратные monitors, использует виртуальный sink/monitor source. После остановки
|
||||
PulseAudio проверяет освобождение контекста; после запуска — новый прогресс
|
||||
capture и прежнего PCM sound. Проверка прошла: capture 325→575 мс, playback
|
||||
16384→29184 frames. Системный звуковой сервер не перезапускается.
|
||||
|
||||
Полный `check.sh` до исключения async-тестов: 119 passed, 0 failed, 1 skipped;
|
||||
proxy/burst/load интеграции прошли. По последующему указанию пользователя async
|
||||
тесты исключены из дальнейших запусков. `./gradlew --offline assembleDebug` —
|
||||
успешно, APK не установлен на телефон.
|
||||
|
||||
Физический USB/Bluetooth hotplug, сон ОС и качество длительного реального звонка
|
||||
этими проверками не подтверждены. AEC reference отдельных уведомлений и компенсация
|
||||
дрейфа часов звонка остаются ограничениями исходного DSP. Граница 1 с относится
|
||||
к отдельной PA-операции: последовательность init/uninit нескольких устройств
|
||||
может задержать GUI дольше. Дополнительный owner-thread/DSP-worker не добавлен.
|
||||
|
||||
## Референсы
|
||||
|
||||
Первичные исходники и документация просмотрены 2026-10-01; ссылки на master могут
|
||||
изменяться. Используем принципы, а не копируем целиком архитектуру библиотеки.
|
||||
|
||||
- [PipeWire audio-src-ring.c](https://docs.pipewire.org/audio-src-ring_8c-example.html):
|
||||
callback читает доступные кадры, заполняет остаток тишиной и возвращает полный
|
||||
буфер; пополнение инициирует через событие управляющего потока. Хороший минимальный
|
||||
пример поведения при пустой PCM-очереди.
|
||||
- [PulseAudio stream API](https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/stream.h):
|
||||
отдельные underflow/overflow/started/suspended/moved callbacks; peek различает
|
||||
пустую очередь и hole с ненулевой длиной. Нужно разделять эти состояния в логах.
|
||||
- [PulseAudio pacat.c](https://github.com/pulseaudio/pulseaudio/blob/master/src/utils/pacat.c):
|
||||
underflow callback сообщает событие без пересоздания stream. При failed context
|
||||
утилита завершает работу — это пример диагностики, не готовая self-healing система.
|
||||
- [Mozilla cubeb PulseAudio backend](https://github.com/mozilla/cubeb/blob/master/src/cubeb_pulse.c):
|
||||
явные context/stream states, пересоздание ошибочного context при инициализации,
|
||||
согласованное отключение callbacks и доступ под mainloop lock; minimum latency
|
||||
25 мс. Это полезный референс владения и границ управления, а не основание
|
||||
автоматически заимствовать всю библиотеку.
|
||||
- [miniaudio](https://github.com/mackron/miniaudio/blob/master/miniaudio.h):
|
||||
init/start/stop/uninit запрещены внутри data callback из-за deadlock. Просмотренный
|
||||
upstream тоже содержит break при пустом duplex-ring: обновление библиотеки само
|
||||
по себе не решает этот найденный дефект.
|
||||
Reference in new issue
Block a user