diff --git a/doc/chat_call_arch.md b/doc/chat_call_arch.md index 810b23a1..db51f28a 100644 --- a/doc/chat_call_arch.md +++ b/doc/chat_call_arch.md @@ -129,6 +129,13 @@ capture/play потоки независимо создают и запуска Ошибка любого направления завершает звонок. Общий `stop()` отменяет подготовку, разблокирует I/O и ждёт оба потока до освобождения Android-устройств и native-аудиостека. Отдельные locks защищают подготовку/остановку каждого устройства. Замер AEC планируется через 300мс после готовности обоих направлений и не задерживает воспроизведение. +При наличии Telecom Connection режимом и выходом звонка управляет только Telecom. +ACTIVE запрашивается до создания аудиоустройств; AudioManager/прямой preferred device не дублируют выбор. +На API 34+ маршрут запрашивается через доступный CallEndpoint, на старых — через Connection.setAudioRoute. +Подтверждённый маршрут приходит из callback системы, отдельно от желания пользователя; ошибка запроса логируется. +Если Connection ещё нет, используется AudioManager. Поздняя регистрация передаёт управление Telecom, +снимая заявку приложения на режим и прямые устройства. Рация сохраняет отдельный AudioManager-путь. +Реальное время запуска/переключения зависит от Android и требует проверки на устройстве. Маршрутизатор использует один снимок входных/выходных устройств; обновляет его при старте, событиях устройств/BT-профиля и явной переоценке. По завершении снимок освобождается. INFO-логи Android показывают длительности настройки режима, регистрации callback, BT-профиля, выбора маршрута, @@ -146,7 +153,18 @@ INFO-логи Android показывают длительности настро Это очередь AudioTrack, а не полная сквозная задержка и не сетевой jitter reserve. Ранее на Samsung startFrames=1920 был принят, но без ограничения рабочего буфера written-played оставался 120–160мс; одного стартового порога недостаточно. -Фактически принятый размер после изменения требует проверки на устройстве, включая смену маршрута. +Замер Samsung подтвердил постоянную очередь 40мс. При прежнем параллельном управлении маршрутом +AudioTrack пересоздавался, очередь временно достигала 160мс, а ожидание свободного места — 328–336мс. +При старте AudioTrack.play занимал 1080мс. Новое владение маршрутом нужно проверить тем же сценарием. + +На desktop `AudioDiag: capture clock` при call=debug выводит секундную сводку исходных backend-блоков: +число блоков/сэмплов, накопленную последовательность, диапазон ошибки относительно конца предыдущего блока, +latency PulseAudio, число скачков >2мс, hole-сэмплы и блоки без достоверного измерения. +Первый скачок за секунду содержит обе latency/поправки конвертера, expected/actual PTS и монотонное время. +Backend публикует только числовой снимок в ограниченное кольцо; форматирование выполняет reporter. +Это наблюдение, а не коррекция: пороги 2мс в call_audio и 20мс в VoiceAudioIo пока сохранены. +Счётчик sequence описывает полученные backend-сэмплы; отсутствие потерь очереди само по себе +не доказывает непрерывность физического захвата. Проверять вместе с hole/overflow и route-событиями. Диагностика Android при `call=debug`: playback сохраняет один числовой снимок на блок (20мс) в кольце 512 элементов, без форматирования/вывода. Отдельный reporter раз в 200мс забирает записи; diff --git a/tools/chatgui/tests/test_audio_diagnostics.cpp b/tools/chatgui/tests/test_audio_diagnostics.cpp index 753e96fe..2fe32823 100644 --- a/tools/chatgui/tests/test_audio_diagnostics.cpp +++ b/tools/chatgui/tests/test_audio_diagnostics.cpp @@ -19,6 +19,26 @@ int main() { assert(take(e)); assert(e.value == int64_t(i) && e.detail == 7); } assert(!take(e)); lost = 0; + // Граница секундного отчёта не обрывает карту времени; hole отделён от скачка оценки latency. + std::map devices; + audio_capture_timing timing{1000000, 100000, 0, 480, 48000, 1, 0}; + audio_diag_capture_clock(nullptr, &timing); + timing.first_us = 1014000; timing.latency_us = 96000; + audio_diag_capture_clock(nullptr, &timing); + timing.first_us = 1024000; timing.missing = 1; + audio_diag_capture_clock(nullptr, &timing); + while (take(e)) consume(e, devices); + auto& capture = devices[0]; + assert(capture.captureSequence == 1440 && capture.captureFrames == 1440 && capture.captureBlocks == 3); + assert(capture.captureJumps == 1 && capture.captureMaxDelta == 4000 && capture.captureMinDelta == 0); + assert(capture.captureMissing == 480 && capture.captureEstimated == 0); + assert(capture.captureMinLatency == 96000 && capture.captureMaxLatency == 100000); + report(devices, now(CLOCK_MONOTONIC), 1.0); + timing.first_us = 1034000; timing.missing = 0; timing.measured = 0; + audio_diag_capture_clock(nullptr, &timing); + assert(take(e)); consume(e, devices); + assert(capture.captureJumps == 0 && capture.captureMissing == 0 && capture.captureEstimated == 1); + assert(capture.captureSequence == 1920 && capture.captureBlocks == 1 && capture.captureFrames == 480); std::vector workers; for (int p = 0; p < producers; ++p) workers.emplace_back([p, &done]() { for (int i = 0; i < perProducer; ++i)