From 03be46c3390369d762f4eec2993743b37982b79b Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 26 Oct 2026 16:34:23 +0100 Subject: [PATCH] Record desktop voice checks and reproduce Android mute signal after AEC --- doc/call_audit_findings.txt | 138 +++++++++++++++++++++++++++++++++++- doc/chat_call_arch.md | 3 +- doc/tasks.md | 12 ++++ 3 files changed, 151 insertions(+), 2 deletions(-) diff --git a/doc/call_audit_findings.txt b/doc/call_audit_findings.txt index 22b7c26d..c6ccfee5 100644 --- a/doc/call_audit_findings.txt +++ b/doc/call_audit_findings.txt @@ -8,7 +8,7 @@ call_audio уменьшали pending новой привязки и обход эпоха владельца, постоянные регрессии13/13 и голосовой профиль4/4 PASS. Ложный отказ test_radio_mesh (dupC=0) также исправлен: место дедупликации зависит от порядка прихода. После rebase сборки и voice17/17 PASS, -check202/202 PASS,proxy9/9,burst1/1,load8/8,176с. Актуальный результат — раздел10; +check202/202 PASS,proxy9/9,burst1/1,load8/8,176с. Результат этого этапа — раздел10; история/план — разделы6–7, внедрение TX — раздел8, диагностика mesh — раздел9. Полная проверка аудио, видео и платформенных разделов не завершена. При продолжении подтверждён и по запросу пользователя исправлен дефект @@ -17,6 +17,11 @@ check202/202 PASS,proxy9/9,burst1/1,load8/8,176с. Актуальный резу Новая постоянная регрессия11/11 и штатный headless e2e1/1 PASS; native build и check203/203 PASS,proxy9/9,burst1/1,load8/8,188с. Диагностика/план — раздел11, внедрение/актуальная проверка — раздел12. +По запросу «пуш и продолжай» опубликованы6 коммитов до54a7522d. При продолжении +desktop-профиль25/25 PASS; подтверждён дефект Android mute до AEC: +занулённый Kotlin-вход превращается в ненулевой PCM перед Opus. +Компонентные контроли PASS, текущий mute ожидаемо FAIL,allocations0->0. +Реализация не менялась, аудит остановлен по AGENTS.md:10; план/доказательства — раздел13. База проверки: f79b9261, чистое рабочее дерево после предыдущего push stream. 1. Подтверждённый дефект: call_start не проверяет обычную занятость @@ -793,3 +798,134 @@ radio_headless остаётся на диагностику, runtime-дефек Аудит остальных call-контрактов/платформ остаётся частичным:call0[V]/11[~]/10[ ], call_headless.h[~], архитектура[~],видео[ ]. Новые sanitizer/аппаратные GUI, Android, Windows и FreeBSD проверки этим шагом не выполнялись. Push не выполнялся. + +13. Desktop I/O и Android mute до AEC — 26.10.2026 + +Публикация завершённого исправления + +По запросу «пуш и продолжай» git push origin master PASS: +03b05094..54a7522d, все6 локальных коммитов отправлены. git ls-remote подтвердил +refs/heads/master=54a7522dd93934bd3f082990550e971339bdb4f7; +на момент push HEAD==origin/master, дерево чистое. Прежний полный check203/203 +и headless11/11 из раздела12 относятся к уже опубликованной реализации. + +Продолжение desktop-аудита + +Полностью прочитаны CallAudioEngine.cpp/.h167+76 строк, VoiceAudioIo.cpp/.h525+76, +AudioDevice.cpp/.h225+68, AudioEventQueue, AudioCaptureLease, VoiceCaptureClock, +VoicePlaybackBuffer; относящиеся к start/end/stop handlers MainWindow и lifecycle +utun_node, attach/detach/render/recovery SoundManager. Остальные SoundManager +функции и все потребители общего I/O этим этапом целиком не проверены. +Повторно сверены call_audio feed/pull, call_jitter/reset/window, CallTimeline +и tones; их прежние частичные отметки не закрываются преждевременно. + +Capture callback копирует PCM в ограниченную MPSC-очередь, capture worker +выполняет AEC и сохраняет границы mute. Независимый RX worker получает PCM; +SoundManager смешивает все источники того же выхода и возвращает итоговое +stereo как reference. Stop: закрыть/отменить capture, join capture worker, +detach output, join RX, release core. Async uninit сохраняет callback/backend +и microphone lease до завершения закрытия; отсутствие PCM не останавливает RX. +start подтверждает подготовленную сессию, recovery устройств может продолжаться. + +Штатный CMake configure/build PASS, профиль25/25 PASS,20,89с: +- прежние call_jitter/delay/timeline/voice_tx и13 call_audio_tx —17 тестов; +- audio_event_queue,voice_audio_io,audio_timing,soundtouch_clock, + audio_recovery,microphone_watchdog,audio_buffer_tuning,audio_diagnostics —8. +Очередь проверена на80000 упорядоченных сообщениях; VoiceAudioIo проверяет +настоящие workers/stereo AEC с заменой сервисной PCM-границы наблюдателями. +Recovery/watchdog используют настоящий miniaudio null backend, Qt timers, +core voice и введённые отказы/задержки, без сетевого звонка/микрофона. +ERROR/WARN новых I/O-тестов соответствуют намеренным init/start/context/ +PCM/watchdog faults, старвированию выхода при recovery и недостающему reference. +В recovery есть WARN о паузе uasync: fixture специально не выполняет poll +во время работы виртуальных устройств. Все25 завершились Passed. +Логи /tmp/call-desktop-audit-{configure,build,tests,last-test}.log; +вывод новых I/O-тестов прочитан полностью. Предупреждение компилятора из +существующего miniaudio.h не относится к новым исходникам этого аудита. +Это не проверка реального PulseAudio/ALSA, Windows/FreeBSD или акустики устройства. + +Подтверждённый Android-дефект + +В CallAudioEngine.kt прочитаны start/stop/runWorker/reportFailure и полный +captureLoop (строки1–454), а также syncAecForRoute/measureAecDelay и хвост +796–856. Kotlin playback/recovery середины файла и остальной Android lifecycle +целиком ещё не проверены. Сверена полная цепочка feed в NativeLib.kt, +nativeCallAudioFeed и utun_bridge_call_audio_feed, core call_audio_feed_impl. + +captureLoop (строка414) делает out=if(muted) silence else buf, затем +NativeLib.callAudioFeed(callId,out,ptsUs). В NativeLib/JNI/C bridge флага mute +нет: bridge вызывает call_audio_feed_pcm. Core получает нулевой raw-вход, +после чего выполняет AEC, затем AGC и Opus. AEC с прогретым фильтром/ненулевым +reference может создать ненулевой результат даже из этого нулевого входа. +syncAecForRoute включает core AEC на SPEAKER; разрешённый delay1..20 кадров. +Таким образом раннее зануление не гарантирует нулевого PCM перед Opus. +Кроме того AEC/AGC лишаются настоящего микрофонного входа во время mute; +деградация на реальном устройстве отдельно не измерялась. + +Core уже имеет call_audio_feed_pcm_muted: настоящий PCM проходит AEC/AGC, +muted применяется непосредственно перед encode. Desktop prepared path +применяет свою маску после AGC и этого дефекта не имеет. + +Компонентная диагностика без замены production + +/tmp/call-android-mute-audit.cpp и ...-build.py используют текущие CMake +libutun_voice/libutun/libuasync/Opus. Настоящий android_jni_bridge.c собран +отдельным host-объектом с function/data sections и gc-sections, как штатный +test_android_replies. __ANDROID__ выключен: реально выполняется C bridge, +JNI-маршалинг/Kotlin/AudioRecord/AudioTrack на устройстве не запускаются. +Проектные исходники, архивы и объекты не заменяются. + +Один настоящий uasync/минимальный instance, call_audio_init/start, реальные +remote Opus encode → on_media → jitter/CallDelay → pull PCM → core AEC reference. +Синтетический микрофон = половина фактически полученного render; прогрев300 +кадров, затем20 наблюдаемых. Через публичный API выбран AEC delay1; AGC +отключён настройкой для изоляции AEC. Начальный FIFO заполнен обычными +on_media, приватные поля/фильтр не подменяются. Синтетический get_time_tb +продвигается на20мс/кадр; настоящий uasync исполняет TX после каждого feed. +Сетевая граница call_send_media заменена счётчиком; соединение двух ядер +не создаётся. Wrapper AEC/encode только читает PCM до/после настоящих функций +и сохраняет их результат. Звук не записывается, используются искусственные данные. + +Результаты (в20 кадрах всего19200 отсчётов): +- unmuted: bridge получает настоящий микрофон; AEC in19193/out18445 ненулевых, + Opus input18445,peak46,RMS10,500; control PASS,exit0; +- no_aec: текущий ранний mute, AEC отключён; Opus input0,peak0,RMS0; + control PASS,exit0; +- legacy: текущий ранний mute, AEC включён; AEC in0/out19191, + Opus input19191,peak7515,RMS1602,348. Проверка нулевого входа Opus ожидаемо + FAIL,exit1; наблюдение завершилось с полноценным cleanup; +- final_mute: существующий core API получает настоящий микрофон+muted1; + AEC in19193/out18445, но Opus input0,peak0,RMS0; control PASS,exit0. +Во всех4 запусках warm render RMS3878,247; все320 TX callbacks исполнены, +pending0 при destroy, u allocations0->0. Сырые входы AEC в unmuted/final_mute +совпадают; ошибочный legacy имеет именно нулевой вход и ненулевой выход. +Обычное последействие Opus-декодера здесь не измеряется: дефект доказан +до lossy encode. Прослушивание/утечка речи микрофона на устройстве не заявляются. +Логи /tmp/call-android-mute-audit-{build,results,unmuted,no_aec,legacy,final_mute}.log +прочитаны полностью. Build PASS; предупреждение snprintf относится к +существующей неиспользуемой здесь функции формирования списка каналов bridge. + +План исправления для согласования + +1) Убрать silence/out подмену в captureLoop: передавать настоящий buf и один + snapshot muted на готовый20мс кадр, сохранив capture PTS и порядок PCM. +2) Добавить флаг в NativeLib.callAudioFeed/nativeCallAudioFeed, JNI и объявление/ + реализацию C bridge. Bridge вызывает существующий call_audio_feed_pcm_muted. + Сверены все production callers: один Kotlin captureLoop и один JNI вызов. + Не нужны новый глобальный mute, новый режим DSP или изменение wire. +3) Закрепить настоящий bridge→AEC/AGC→Opus постоянной регрессией: unmuted, + текущий ранний mute как негативный контроль, итоговый mute, AEC on/off, + AGC on/off, следующий unmute и cleanup. Наблюдать точный PCM перед encode; + raw-вход фильтра должен оставаться исходным, muted-вход Opus строго нулевым. +4) Собрать native/voice-профиль и Android assembleDebug для проверки общей + JNI/Kotlin сигнатуры; затем check. Реальное переключение mute на громкой + связи и AEC после unmute — отдельный аппаратный контроль. + +По AGENTS.md:10 новый логический дефект требует остановки перед исправлением. +Изменены только комментарии call_audio.h и документация/внешний чек-лист. +Уточнены актуальные event IDs, состав C/C++ сборки, отдельный headless DSP, +desktop stop-порядок и reset FIFO; runtime/API/протокол/штатные тесты не менялись. +Call0[V]/11[~]/10[ ],chat_call_arch.md[~],видео[ ] сохранены;8 затронутых +desktop headers отмечены[~]. Общий голосовой/платформенный аудит ещё частичный. +Новый fullcheck/sanitizers/Gradle/аппаратный Android/desktop/Windows/FreeBSD +этим продолжением не выполнялись. Новый дефект не исправлен; задача открыта. diff --git a/doc/chat_call_arch.md b/doc/chat_call_arch.md index 2efab6d0..1ceb5346 100644 --- a/doc/chat_call_arch.md +++ b/doc/chat_call_arch.md @@ -483,7 +483,8 @@ AEC/AGC обрабатывают микрофон, перед Opus примен Capture callback только копирует PCM; capture worker выполняет stereo-reference AEC и передаёт prepared PCM с маской mute в core AGC/Opus. RX worker независимо вызывает `call_audio_pull_pcm`. `SoundManager` смешивает голос с остальными источниками на общем выходе и возвращает тот же итоговый - stereo PCM как AEC-reference. Stop закрывает capture, останавливает workers, отсоединяет выход и освобождает core. + stereo PCM как AEC-reference. Stop закрывает capture, ждёт capture worker, отсоединяет выход, + ждёт RX worker и освобождает core. При неисправном capture async close сохраняет объекты и lease до завершения uninit; RX остаётся активным. - **Android:** JNI `nativeCall*` + Kotlin `AudioRecord`/`AudioTrack`. **Переключение источника:** `AudioManager` (API31+ `setCommunicationDevice` + diff --git a/doc/tasks.md b/doc/tasks.md index a8756724..be9447d9 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -4,6 +4,18 @@ ## Текущие задачи +[ ] **Android call mute: передавать настоящий PCM и флаг mute через JNI до core** — подтверждено 26.10.2026. + CallAudioEngine.captureLoop заменяет микрофон тишиной до nativeCallAudioFeed; bridge вызывает feed_pcm. + Включённый AEC после этого снова создаёт ненулевой сигнал перед Opus. Стенд с настоящими bridge/Opus/AEC, + прогревом300 кадров и20 muted кадрами:19191/19200 ненулевых отсчётов, peak7515,RMS1602,348; ожидаемый FAIL. + AEC отключён:0; существующий call_audio_feed_pcm_muted с настоящим микрофоном:0; unmuted-контроль PASS. + Во всех4 запусках320 TX callbacks, allocations0->0. AGC отключён для изоляции причины; Android runtime не запускался. + План: убрать раннее зануление Kotlin, передавать snapshot mute через NativeLib/JNI/C bridge в feed_pcm_muted. + Не менять wire, общий DSP или desktop prepared path; добавить постоянную регрессию raw/final mute с AEC/AGC. + Проверить сигнатуры assembleDebug и native/voice-профиль; аппаратный контроль громкой связи отдельно. + До согласования реализация не меняется по AGENTS.md:10. Подробности: [call_audit_findings.txt](call_audit_findings.txt),раздел13; + /tmp/call-android-mute-audit-{build,results,unmuted,no_aec,legacy,final_mute}.log. + [+] **call_headless: целостность PCM-кадра при переполнении TCP-очереди** — исправлено 26.10.2026. Header/PCM собираются до отправки; очередь принимает целый кадр или отбрасывает его целиком. Частичная запись сохраняет хвост перед следующими кадрами. Потери учитываются отдельно для клиента/сервиса;