1039 lines
95 KiB
Plaintext
1039 lines
95 KiB
Plaintext
Аудит src/call — 26.10.2026, начат с голосовых вызовов
|
||
|
||
Текущий статус: дефект допуска второго исходящего звонка исправлен после
|
||
согласования. Штатная регрессия, четыре C++ voice-набора и полный check202/202
|
||
PASS. Разделы1–4 сохраняют историю диагностики, внедрение — в разделе5.
|
||
При продолжении подтверждён и исправлен дефект: старые TX-задачи после destroy/init
|
||
call_audio уменьшали pending новой привязки и обходили лимит8. Внедрена отдельная
|
||
эпоха владельца, постоянные регрессии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;
|
||
история/план — разделы6–7, внедрение TX — раздел8, диагностика mesh — раздел9.
|
||
Полная проверка аудио, видео и платформенных разделов не завершена.
|
||
При продолжении подтверждён и по запросу пользователя исправлен дефект
|
||
исходящей PCM-очереди call_headless: header+PCM принимаются/отбрасываются
|
||
целиком, partial send сохраняет хвост, потери имеют отдельный учёт/диагностику.
|
||
Новая постоянная регрессия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.
|
||
По следующему «ОК» внедрена передача raw PCM+mute через JNI в существующий core API.
|
||
Новая регрессия5/5, voice/desktop30/30, Android timing/trace14/14,
|
||
assembleDebug для двух ABI и check203/203 PASS,proxy9/9,burst1/1,load8/8,168с.
|
||
Диагностика/план — раздел13, внедрение/актуальная проверка — раздел14.
|
||
База проверки: f79b9261, чистое рабочее дерево после предыдущего push stream.
|
||
|
||
1. Подтверждённый дефект: call_start не проверяет обычную занятость
|
||
|
||
call_busy возвращает1 для любой сессии state!=ENDED. Входящий INVITE проверяет
|
||
этот же признак других сессий и отвечает BUSY. call_start проверяет только
|
||
radio_call_busy, затем создаёт новую call_session без проверки обычного звонка.
|
||
В результате один instance может иметь несколько незавершённых обычных сессий.
|
||
|
||
Точки кода на базе проверки:
|
||
- call.c:625–640: входящий INVITE отклоняется при другом незавершённом звонке;
|
||
- call.c:960–965: call_busy учитывает все состояния, кроме ENDED;
|
||
- call.c:1064–1137: call_start не использует call_busy, создаёт сессию/CM,
|
||
отправляет INVITE и запускает таймеры независимо от уже идущего звонка;
|
||
- chat_headless_control.c:750–763: headless-команда напрямую вызывает call_start,
|
||
без дополнительной проверки call_busy. Ограничение только в UI недостаточно.
|
||
|
||
Реестр может содержать завершённые записи для END_ACK/таймаута и новый звонок;
|
||
это штатно и не означает поддержку нескольких одновременно незавершённых.
|
||
call_audio_start_impl использует один g_call_id/codec/jitter-контур; start другого
|
||
ID освобождает прежний. Аппаратное воспроизведение/переключение в этом аудите
|
||
не запускалось, фактическая потеря звука не заявляется.
|
||
|
||
2. Изолированное воспроизведение на настоящих ядрах
|
||
|
||
/tmp/call-audit-busy.c использует существующий fixture tests/test_call.c:
|
||
два UTUN_INSTANCE на одном UASYNC, свои временные конфиги/ключи/SQLite,
|
||
настоящие CHAT/BGP/ETCP/router/CM. Сторонних узлов и пользовательской БД нет.
|
||
После штатного получения нужного состояния вызывается публичный call_start
|
||
с другим call_id для того же пира. Реестр и счётчики читаются в uasync.
|
||
Ни состояние первой сессии, ни результат call_busy вручную не подменяются.
|
||
|
||
У каждого сценария отдельный процесс. Перед вторым call_start:
|
||
call_busy=1, число незавершённых=1. Ожидается отказ без второй сессии/CM,
|
||
без изменения первой сессии и c_calls_started. Фактически:
|
||
|
||
Сценарий Первая сессия live Новый CM c_calls_started
|
||
outgoing caller OUTGOING, state1 1->2 да 1->2
|
||
ringing caller RINGING, state2 1->2 да 1->2
|
||
active обе стороны приняли, ACTIVE3 1->2 да 1->2
|
||
incoming callee RINGING, state2 1->2 да 0->1
|
||
wait_decision caller WAIT_DECISION, state5 1->2 да 1->2
|
||
|
||
Во всех пяти первая сессия/её состояние сохранены, вторая имеет OUTGOING1.
|
||
Это две незавершённые сессии, а не доказательство двух ACTIVE-разговоров:
|
||
стенд останавливается после проверки локального допуска, до обработки
|
||
второго INVITE другой стороной. Два CM handles не означают два физических ETCP.
|
||
Все5 отрицательных проверок завершаются exit1; после destroy/poll/uasync_destroy
|
||
счётчик tracked allocations возвращается0->0. Утечка этим тестом не обнаружена.
|
||
|
||
Диагностика: глобальный error, категория call=debug. В логах видны оба
|
||
call_start, ID/peer/group, отдельный CM, исходное состояние, live1->2,
|
||
счётчики и cleanup. Все логи пяти сценариев прочитаны полностью.
|
||
Пример ACTIVE: busy=1 live=1->2 original_state=3 preserved=1 second_created=1
|
||
second_state=1 second_CM=1 started=1->2 expected_live=1 result=FAIL.
|
||
|
||
Сборка через штатный Makefile/LIBUTUN/COMMON_LIBS, без ручного списка src/*.c:
|
||
make -C tests -f Makefile -f /tmp/call-audit-busy.mk /tmp/call-audit-busy
|
||
PASS: /tmp/call-audit-busy-build.log.
|
||
Логи /tmp/call-audit-busy-{outgoing,ringing,active,incoming,wait_decision}.log.
|
||
|
||
3. Предлагаемое исправление и проверки
|
||
|
||
Единая точка допуска — call_start в uasync. До создания сессии, CM, отправки
|
||
INVITE и публикации занятости проверять call_busy. Существующий звонок не
|
||
завершать и не заменять. Для нового ID при занятости использовать существующее
|
||
событие ENDED/BUSY, как при pending radio invitation, с понятным INFO-логом
|
||
нового/занятого ID, состояния и peer/group. Новые состояния/таймеры/wire не нужны.
|
||
|
||
До BUSY-события отдельно обработать уже зарегистрированный out_call_id:
|
||
повтор/коллизия не должны породить ENDED с ID действующего звонка или вставить
|
||
дубликат ключа реестра. Такой вызов безопасно отклонять с WARN без изменения
|
||
существующего звонка. Для новой попытки caller выбирает ненулевой уникальный ID.
|
||
Это требование к корректности будущего исправления; отдельная runtime-проверка
|
||
коллизии ID в текущем аудите не выполнялась.
|
||
|
||
Постоянная регрессия должна проверить:
|
||
- OUTGOING, caller/callee RINGING, ACTIVE и обе стороны WAIT_DECISION;
|
||
- второй вызов тому же/другому пиру и из другой группы/DM, защита на уровне instance;
|
||
- отсутствие второй session/CM/INVITE, новых started и изменений исходного звонка;
|
||
- BUSY ровно для нового ID, повтор существующего ID без ложного ENDED исходного;
|
||
- новый уникальный ID разрешён после ENDED, даже пока живёт старый tombstone;
|
||
- сохранение правил pending radio invitation, phone-policy и нормального start;
|
||
- исходный call/CM/аудиоприём продолжаются, cleanup без tracked leaks.
|
||
|
||
После исправления: сборка, test_call/test_call_headless, тесты голосового
|
||
стека соответствующей C++ сборки и общий check.sh. Затем продолжить аудит
|
||
call_audio/jitter/delay/timeline/tones/headless, а после них — видео.
|
||
|
||
4. Полнота аудита и текущие проверки
|
||
|
||
Полностью прочитаны call.c1230 строк, call.h134, call_priv.h43, call_proto.h111,
|
||
call_audio.h105 и voice_sources.cmake. Размеры заголовков указаны до правок.
|
||
В call_audio.c прочитана полная call_audio_start_impl141–258, остальной файл
|
||
целиком ещё не проверен. doc/chat_call_arch.md прочитан до строки320 из465.
|
||
Из теста call использован штатный fixture и просмотрены относящиеся к нему
|
||
setup/события/сценарии; полноценный аудит всех тестовых/video-функций не заявляется.
|
||
|
||
call.h, call_audio.h, call_priv.h отмечены[~]; прежняя отметка call_proto.h[~]
|
||
сохранена. Итого0[V]/4[~]/17[ ] для21 заголовка src/call. Остальные голосовые
|
||
заголовки пока[ ], видео по просьбе пользователя ещё не проверялось.
|
||
doc/chat_call_arch.md[~], doc/call_video.txt[ ] сохранены. В call.h и doc
|
||
добавлено предупреждение о подтверждённом дефекте, задача записана в tasks.md.
|
||
|
||
Штатный tests/test_call PASS:2/2,0 failed, exit0.
|
||
Лог /tmp/call-audit-baseline-test-call.log прочитан полностью; ERROR-сообщения
|
||
относятся к ожидаемым отрицательным media/video-проверкам самого теста.
|
||
Прохождение штатного набора не закрывает выявленный пробел допуска.
|
||
Новая диагностическая программа собрана PASS, пять отрицательных проверок
|
||
FAIL ожидаемо. C/C++ реализации и штатные тесты проекта не менялись.
|
||
Полная сборка/check.sh, sanitizers, отдельный C++ audio/GUI и Android runtime
|
||
в этом продолжении не запускались; прежние результаты stream сюда не переносятся.
|
||
Неизменность C-токенов всех21 call-заголовков относительно начала аудита,
|
||
отсутствие правок C/C++ реализаций/lib/tests от f79b9261, diff --check и
|
||
итоговые отметки чек-листа PASS: /tmp/call-audit-contract-check.log.
|
||
|
||
5. Исправление допуска и штатная регрессия — 26.10.2026
|
||
|
||
call_start до создания session, CM и INVITE проверяет:
|
||
- ненулевые peer/id и остальные обязательные аргументы, с ERROR контекста;
|
||
- уже зарегистрированный ID, включая ENDED: WARN без событий/изменений записи;
|
||
- любую обычную сессию state!=ENDED: ENDED/BUSY только для нового ID, INFO
|
||
с requested/busy ID, исходным ch, peer и состоянием занятого звонка;
|
||
- прежнюю pending radio invitation — по существующему правилу.
|
||
|
||
Для проверки обычной занятости используется тот же предикат, что в call_busy
|
||
и входящем INVITE. Один проход даёт также запись/состояние для точного лога;
|
||
дополнительного boolean-прохода и нового API поиска не добавлено. Всё выполняется
|
||
в одном uasync-потоке до первого изменения состояния, межпоточной гонки допуска нет.
|
||
Завершённые записи с таймерами END_ACK не блокируют новый уникальный ID.
|
||
Не изменены wire, таймеры/состояния, входящая политика и аудиопроцессинг.
|
||
|
||
Добавлен tests/test_call_admission.c и цель Autotools, включённая в общий check.
|
||
Fixture test_call используется в отдельном процессе: два реальных ядра,
|
||
CHAT/Merkle/BGP/ETCP/router/CM и DM. Для проверки рации дополнены настоящие
|
||
join/update/tree подписи участников через member_sync, без прямого изменения БД.
|
||
Тест получает все состояния штатным сигналингом, вручную их не присваивает.
|
||
Observers --wrap=conn_mgr_open/etcp_route_send считают реальные вызовы и передают
|
||
их исходным функциям; только END_ACK намеренно удерживается для проверки ENDED.
|
||
|
||
Покрыты caller OUTGOING и обе роли RINGING/ACTIVE/WAIT_DECISION (7 сочетаний):
|
||
повтор собственного ID, коллизия того же ID с другим tuple, новый ID через CHAT/DM
|
||
и другой ch/peer до проверки его доступности. Вторую существующую CHAT-группу
|
||
и третье физическое ядро тест не создаёт: проверяется общий допуск instance
|
||
до поиска группы/пира, а не маршрутизация к этим дополнительным назначениям.
|
||
|
||
Во всех отказах проверяются отсутствие новых session/CM/INVITE и started,
|
||
сохранение исходного состояния/роли/CM/очереди/таймеров/seq. BUSY приходит ровно
|
||
для нового ID; дубликат не даёт событий. Дополнительно проверены:
|
||
- нулевые peer/id без создания записей и ложных событий;
|
||
- двусторонние закодированные Opus-кадры после всех ACTIVE-отказов через реальный
|
||
router, прежний ETCP остаётся UP; это не запуск микрофона/PCM-вывода;
|
||
- повтор старого ID после ENDED без ложного события; новый звонок при ещё живом
|
||
старом tombstone, с последующим PHONE_BUSY/CONTINUE той же попытки;
|
||
- рация отклоняется при обычном звонке, обычный звонок — при pending radio invitation;
|
||
- нормальный новый звонок/decline после завершения приглашения и полная очистка.
|
||
|
||
Финальная регрессия:547 assertions,22 BUSY,18 отказов без события,
|
||
audio_RX1/1, allocations0->0, exit0. Лог /tmp/call-admission-fix-regression.log.
|
||
Сборка ./build.sh --no-gui -j4 PASS: /tmp/call-admission-fix-build.log;
|
||
после уточнения fixture целевая сборка PASS: /tmp/call-admission-regression-build.log.
|
||
Первый прогон остановился на рации: старый fixture не имел tree-подписей;
|
||
radio=info показал rc_group invalid member и UNAVAILABLE. Исправлен только
|
||
fixture, требования проверки членства в production не ослаблялись.
|
||
|
||
Первоначальный диагностический стенд пересобран с исправленной штатной libutun:
|
||
те же5 сценариев теперь PASS, exit0, live1->1, second_created0/CM0,
|
||
исходная сессия сохранена, started не меняется, allocations0->0.
|
||
Логи /tmp/call-admission-original-{outgoing,ringing,active,incoming,wait_decision}.log,
|
||
сборка /tmp/call-admission-original-build.log. Логи прочитаны полностью.
|
||
|
||
Отдельная штатная CMake-сборка /tmp/utun4-call-admission-gui:
|
||
test_call_jitter, test_call_delay, test_call_timeline и test_voice_tx4/4 PASS.
|
||
Это реальные Opus/SoundTouch/voice-компоненты с существующими тестовыми
|
||
обвязками, без аппаратного GUI/Android-звонка. Configure/build:
|
||
/tmp/call-admission-gui-configure.log, /tmp/call-admission-gui-build.log;
|
||
ctest: /tmp/call-admission-voice-tests.log (3,01с).
|
||
|
||
Полный check.sh PASS:202 passed,0 failed,0 skipped, включая новый admission,
|
||
test_call/test_call_headless и radio/radio_headless; proxy9/9,burst PASS,
|
||
load8/8,134217728 байт,полное время174с. Лог /tmp/call-admission-full-check.log.
|
||
Прогон от user в отдельном IPv6 network namespace: /tmp/call-admission-netns.log.
|
||
Настройки сети хоста сохранены. ASAN/UBSan/LSan, GUI/Android на устройстве
|
||
и реальный аппаратный звонок в этом исправлении не запускались.
|
||
Исправление285a60bf; тестcfc8d801 и уточнение fixture8eb3d102.
|
||
Полнота аудита пока прежняя:0[V]/4[~]/17[ ], архитектурный документ[~].
|
||
Итоговая проверка /tmp/call-admission-final-contract.log PASS: C-токены21/21
|
||
call-заголовков не менялись (API/ABI/wire прежние), source/lib/tests идентичны
|
||
проверенному8eb3d102, diff --check и оформление новых файлов в пределах150.
|
||
Отметки call и прежние stream18[V] сохранены. Основной namespace прежний,
|
||
all/default/lo.disable_ipv6=1. После runtime менялись только отчёты/задачи.
|
||
|
||
6. Аудиостек: устаревшие задачи уменьшают pending новой привязки — 26.10.2026
|
||
|
||
Полностью прочитаны call_audio.c652, call_jitter.cpp265, call_delay.cpp240,
|
||
call_timeline.cpp174 и call_tones.c165 (всего1496 строк), а также соответствующие
|
||
заголовки, call_clock.h и inline call_jitter_window.h. Уточнения контрактов ещё
|
||
не завершены: найдено расхождение учёта TX при повторной привязке к instance.
|
||
|
||
call_audio_init (строка110) задаёт g_inst=inst и g_tx_pending=0. У старых
|
||
posted tasks при этом нет отдельного поколения привязки. call_audio_send
|
||
(строка82) уменьшает g_tx_pending, даже когда tx->generation уже не совпадает
|
||
с g_tx_generation и отправка старого кадра запрещена. После init это уже
|
||
счётчик новых задач: он может стать0 при восьми ещё не исполненных новых кадрах.
|
||
Следующие восемь feed принимаются, хотя лимит должен остановить их.
|
||
|
||
Генерация звонка решает другую задачу: start/release меняют g_tx_generation,
|
||
поэтому старый Opus не отправляется. Она не является эпохой привязки init/destroy.
|
||
При обычной смене звонка без reinit старые задачи по-прежнему занимают pending,
|
||
и их исполнение должно уменьшать счётчик. Простое условие current перед
|
||
декрементом оставило бы эти слоты навсегда занятыми — это неверное исправление.
|
||
|
||
Детерминированное воспроизведение
|
||
|
||
/tmp/call-audit-tx-pending.cpp: минимальный UTUN_INSTANCE с настоящим UASYNC,
|
||
реальные call_audio/Opus/CallDelay/SoundTouch/tones из штатной CMake-библиотеки.
|
||
uasync и порядок posted callbacks настоящие; состояние/счётчики call_audio не
|
||
читаются и не подменяются. Нет сетевого звонка, микрофона или пользовательской БД.
|
||
Сетевую границу call_send_media заменяет наблюдатель числа кадров/ID;
|
||
chat_setting отключает compressor/AEC, get_time_tb фиксирован, чтобы исключить TTL.
|
||
|
||
reinit:
|
||
1) init/start_prepared41, принять8 PCM-кадров; их posted tasks пока не выполнять.
|
||
2) Поставить собственный uasync callback extra после этих8 задач.
|
||
3) stop/destroy/init того же instance/uasync, start_prepared42, принять8 новых кадров.
|
||
4) uasync исполняет8 старых задач: отправка запрещена, но pending новой привязки
|
||
уменьшается8->0. Штатный DEBUG-снимок показывает pending0 при восьми ожидающих новых кадрах.
|
||
5) extra пытается принять ещё8 кадров нового звонка, пока первые8 не исполнены:
|
||
принято8, ожидалось0. Одновременно ожидают исполнения16 кадров звонка42.
|
||
6) Два прохода uasync исполняют также задачи, опубликованные из extra:
|
||
sent_current16, sent_old0, allocations0->0; отрицательная проверка exit1.
|
||
|
||
Контроль fresh: extra поставлен перед8 кадрами нового звонка, старых задач нет.
|
||
pending8, extra_accepted0, sent_current8, sent_old0, allocations0->0, exit0.
|
||
Контроль same_owner: stop/start без destroy/init. Старые8 занимают лимит,
|
||
initial_new0, после их исполнения extra принимает8; sent_current8, old0,
|
||
allocations0->0, exit0. Это подтверждает необходимость различать привязку и звонок.
|
||
|
||
Сборка через готовые штатные CMake flags/utun_voice/utun/uasync/Opus,
|
||
без ручной компиляции C-реализаций; /tmp/call-audit-tx-pending.mk.
|
||
Build PASS: /tmp/call-audit-tx-pending-build.log.
|
||
Логи /tmp/call-audit-tx-pending-{fresh,same_owner,reinit}.log прочитаны целиком;
|
||
call=debug показывает pending, число posts и причины отказов. Первый вариант
|
||
стенда выполнял один poll и не исполнял задачи из extra до проверки доставки;
|
||
исправлен только стенд (два poll), после чего fresh/same_owner PASS, reinit FAIL.
|
||
|
||
Предлагаемое исправление
|
||
|
||
Использовать уже применённый в radio_audio шаблон: отдельная монотонная эпоха
|
||
привязки instance, сохранённая в каждой TX-задаче, помимо генерации звонка.
|
||
Менять её при фактическом init/destroy; повтор init того же живого владельца
|
||
оставлять идемпотентным. Обнуление pending относится к новой привязке.
|
||
В send вычислять owner по эпохе привязки и inst; уменьшать pending только
|
||
для своего owner, включая задачи старого звонка того же owner. Отправка
|
||
дополнительно требует текущего звонка/состояния/возраста. Статистика старой
|
||
привязки не должна попадать в новую. Radio-код прочитан в этих точках;
|
||
менять его или формат posted_task/uasync для этого исправления не требуется.
|
||
|
||
Предел8 относится к активной привязке; старые неотправляемые задачи могут
|
||
доживать в прежнем uasync до callback/destroy. Число физических старых+новых
|
||
аллокаций при нескольких последовательных reinit — отдельное свойство,
|
||
данный стенд не доказывает общего лимита8 для всех исторических привязок.
|
||
|
||
Постоянная проверка должна сохранить все три сценария, добавить разные
|
||
instance/uasync и повторное использование адреса instance, удаление старых
|
||
задач при uasync_destroy без callback, повтор init и обычную смену звонка.
|
||
Нельзя перенести старое уменьшение счётчика или просроченную статистику в новый owner.
|
||
Затем выполнить C++ voice-профиль и общий check; GUI/Android lifecycle проверять отдельно.
|
||
|
||
Существующий профиль call_jitter/delay/timeline/voice_tx повторён:4/4 PASS,
|
||
3,01с; /tmp/call-audit-voice-baseline.log. Он новый сценарий reinit не покрывает.
|
||
Desktop runLoop штатно снимает call_audio при остановке uasync; текущий GUI
|
||
не воспроизводился, сбой реального звонка не заявляется. Android/GUI и
|
||
ASAN/UBSan/LSan в этом продолжении не запускались. Полный check202 из раздела5
|
||
относится к прежнему исправлению допуска, а не к этому расхождению.
|
||
|
||
Аудит остановлен по AGENTS.md:10. Production C/C++ и штатные тесты не менялись;
|
||
добавлены предупреждение call_audio.h/статус doc и задача. Заголовки clock,
|
||
delay,jitter,jitter_window,timeline,tones теперь[~], без преждевременного[V].
|
||
Итог0[V]/10[~]/11[ ]; headless и видео ещё не проверялись. Архитектурный документ[~].
|
||
Проверка /tmp/call-audit-audio-contract-check.log PASS: C-токены21/21 заголовков
|
||
не менялись, production C/C++/lib/tests идентичны ab3d582a, diff --check чистый;
|
||
отметки call0/10/11 и прежние stream18[V] проверены. Исправление допуска из раздела5 сохранено.
|
||
|
||
7. Проверка корректности плана TX ownership — 26.10.2026
|
||
|
||
Вывод: отдельная эпоха привязки корректно решает выявленную ошибку. Уточнены
|
||
три обязательных условия: owner ограждает не только pending, но и отправку/
|
||
позднюю TX-статистику; старый звонок того же owner продолжает освобождать
|
||
слоты; предел8 относится к активной привязке, а не к сумме всех исторических tasks.
|
||
Это проверка плана, production C/C++ и штатные тесты проекта не менялись.
|
||
|
||
Временный минимальный прототип
|
||
|
||
/tmp/call-audio-owner-prototype.c — копия нынешнего call_audio.c с изменениями:
|
||
- static g_instance_generation и поле instance_generation в call_audio_tx;
|
||
- init: увеличить поколение только при фактической новой привязке, обнулить pending;
|
||
повтор init того же владельца сохраняет поколение/счётчик;
|
||
- destroy: инвалидировать поколение действующего владельца;
|
||
- feed: сохранить поколение привязки под тем же g_mtx, что увеличение pending;
|
||
- callback: owner=(поколение совпало && g_inst==tx->inst),
|
||
current=owner && совпало поколение звонка; decrement только owner,
|
||
запись post-time/age-drop и отправка только current с прежними active/ending/ID/TTL.
|
||
|
||
Все новые поля внутренние; wire, API/ABI заголовков, codec, PCM и таймеры не меняются.
|
||
Прототип не добавляет сброс DSP/часов/всех накопленных диагностик при init.
|
||
Изоляция статистики означает запрет позднему TX-callback дописывать post-time
|
||
и age-drop другой привязке; существующие правила start/get_stats сохраняются.
|
||
init/destroy и освобождение instance остаются сериализованным lifecycle;
|
||
эпоха не заменяет остановку producers и правила владения uasync.
|
||
|
||
Прочитаны полные process_posted_tasks, post_reserved/cancel_post и destroy:
|
||
uasync снимает текущую пачку tasks под mutex, вызывает callbacks без него,
|
||
затем u_free(task). При destroy ожидающие tasks удаляются без callback.
|
||
call_audio_tx содержит posted_task первым полем, payload в том же calloc-блоке;
|
||
добавление uint64_t не требует cleanup callback, отдельного owner-объекта,
|
||
отмены списка задач или правок uasync. Новая привязка обнуляет свой счётчик,
|
||
поэтому отсутствие callback удалённой старой задачи не блокирует новую очередь.
|
||
|
||
Прототип скомпилирован отдельно штатными CMake C-флагами voice; объект подставлен
|
||
перед libutun_voice.a без замены архивов/объектов проекта. Временные файлы:
|
||
/tmp/call-audio-owner-prototype.c/.o/.mk;
|
||
build /tmp/call-audio-owner-prototype-build.log и /tmp/call-audio-owner-plan-build.log.
|
||
diff с исходником проверен: только перечисленные ownership-поля/условия.
|
||
|
||
Сравнение на одинаковых стендах
|
||
|
||
Сценарий Исходный код Прототип Проверяемое свойство
|
||
fresh PASS PASS Лимит8 без старых задач
|
||
same_owner PASS PASS Смена звонка без reinit, старые слоты освобождаются
|
||
reinit FAIL PASS Старые callbacks не уменьшают новые8
|
||
idempotent PASS PASS Повтор init не открывает ещё8 слотов
|
||
different_instance FAIL PASS Новый pointer owner, поздний destroy старого
|
||
reused_address FAIL PASS Тот же адрес instance после переинициализации
|
||
detached_drain PASS PASS Старые задачи исполняются между destroy и init
|
||
discarded_loop PASS PASS Старый uasync уничтожен без исполнения задач
|
||
cross_loop FAIL PASS Поздние callbacks другого uasync после нового init
|
||
mixed_calls PASS PASS Четыре старые+четыре новые задачи одного owner
|
||
current_expiry PASS PASS Текущие задачи старше200мс освобождают лимит
|
||
rebind_storm FAIL PASS 16 reinit до poll, изоляция последней привязки
|
||
detached_engine FAIL PASS Encoder сохранён при detach/attach, поколение звонка то же
|
||
|
||
Итого исходный код7/13, шесть отрицательных проверок FAIL; прототип13/13 PASS.
|
||
Везде tracked allocations возвращаются к исходному значению, старый Opus
|
||
в прототипе не отправляется. cross_loop использует отдельный поток для каждого
|
||
uasync и явную синхронизацию: правило одного uasync на поток соблюдено.
|
||
Тесты наблюдают TX-границу и используют реальные Opus/uasync; полноценное
|
||
снятие audio_ops двух настоящих call_ctx, GUI и аппаратный захват не проверялись.
|
||
|
||
detached_engine дополнительно подтвердил необходимость owner в current:
|
||
исходный код отправил8 кадров прежней привязки и записал posts8 после reinit;
|
||
прототип не отправил их и не записал эту позднюю статистику. В этом сценарии
|
||
destroy лишь снимает регистрацию, engine освобождается позднее через stop.
|
||
Это не новое правило управления устройствами и не предложение сохранять
|
||
аппаратный звонок между перезапусками ядра.
|
||
|
||
Сводка /tmp/call-audio-owner-plan-results.log проверяет все результаты,
|
||
баланс аллокаций и отсутствие неожиданных ERROR стендов. Подробные логи:
|
||
/tmp/call-audio-owner-prototype-{fresh,same_owner,reinit}.log,
|
||
/tmp/call-audio-owner-plan-{baseline,prototype}-<scenario>.log.
|
||
Расширенный стенд /tmp/call-audio-owner-plan-check.cpp; состояния production
|
||
напрямую не читаются/не подменяются, все переключения — через public API.
|
||
|
||
Штатные C++ test_call_jitter/delay/timeline/voice_tx перелинкованы с тем же
|
||
временным объектом:4/4 PASS, exit0. Их собственные CHECK/REQUIRE активны
|
||
независимо от NDEBUG. Build /tmp/call-audio-owner-voice-build.log;
|
||
логи /tmp/call-audio-owner-test_call_{jitter,delay,timeline}.log и
|
||
/tmp/call-audio-owner-test_voice_tx.log. WARN/ERROR отрицательных проверок
|
||
clock/формата и AEC в этих тестах ожидаемы; runtime assertions пройдены.
|
||
|
||
Уточнённый план внедрения
|
||
|
||
1) Перенести только проверенные ownership-поля/условия в call_audio.c,
|
||
добавить редкие логи init/destroy с эпохой и TRACE решения старой задачи.
|
||
2) Добавить постоянные регрессии в штатную C++ voice-сборку: исходные три
|
||
сценария, повтор init, смена pointer/адреса/ua, удаление без callback,
|
||
mixed/current-expiry и изоляция отправки/поздней статистики.
|
||
3) Сохранить external lifecycle: один активный аудиоконтур, остановка producers
|
||
перед освобождением, init/destroy не гоняются между собой. Изменять
|
||
audio_ops/приём/сброс AEC и DSP в рамках этого исправления не требуется.
|
||
4) Сборка, C++ voice-профиль и полный check.sh; аппаратный GUI/Android lifecycle
|
||
остаётся отдельной проверкой. Новый полный check/санитайзеры здесь не запускались.
|
||
|
||
Отдельный общий предел8 физических аллокаций всех старых привязок план не обещает:
|
||
rebind_storm удерживает128 старых задач до poll/destroy и только8 действующих.
|
||
После drain всё освобождено. Это осознанная граница исправления учёта активного owner.
|
||
После внедрения нужен штатный regression, временные13/13 не заменяют его.
|
||
|
||
8. Внедрение TX ownership и постоянные регрессии — 26.10.2026
|
||
|
||
Проверенный план перенесён в call_audio.c: отдельная g_instance_generation,
|
||
снимок эпохи в embedded task, owner по эпохе+inst. Только owner уменьшает pending;
|
||
current=owner&&call-generation ограждает отправку и поздние post-time/age-drop.
|
||
Старый звонок того же owner освобождает слоты. init того же inst идемпотентен,
|
||
фактический init/destroy инвалидирует прежние задачи. uasync не менялся.
|
||
Заголовок описывает эти границы; C-токены API идентичны исходному заголовку.
|
||
Codec/DSP, приём, wire и внешние правила остановки producers не менялись.
|
||
Логи call=info показывают inst/epoch/pending при init/destroy, call=trace —
|
||
обе эпохи задачи/текущего звонка и решение owner; underflow логируется ERROR.
|
||
Основное исправление821b71e8; постоянные тестыda5817cf, уточнение проверок07aa7aa2.
|
||
|
||
Штатный tools/chatgui/tests/test_call_audio_tx.cpp зарегистрирован в CMake
|
||
для UNIX без Apple: GNU --wrap на сетевую границу, настройки и монотонный TB.
|
||
Каждый из13 сценариев — отдельный CTest-процесс с timeout15с; реальные
|
||
Opus/CallDelay/SoundTouch/uasync, без приватных полей/подмены состояния движка.
|
||
Инстансы минимальные: не создаются настоящие call_ctx/сеть/микрофон.
|
||
cross_loop использует отдельный поток для каждого uasync, явные promise/join;
|
||
init/destroy и захват сериализованы, одновременно работает один аудиодвижок.
|
||
REQUIRE вызывает abort независимо от NDEBUG; Release действительно проверяет условия.
|
||
|
||
Проверки: fresh,same_owner,reinit,idempotent,different_instance,reused_address,
|
||
detached_drain,discarded_loop,cross_loop,mixed_calls,current_expiry,
|
||
rebind_storm,detached_engine. Всегда sent_new8/sent_old0 и allocations0->0.
|
||
После drain принимаются ровно8 новых задач, девятая отклоняется; финальный
|
||
хвост освобождается через uasync_destroy без callbacks. mixed_calls проверяет
|
||
4 старые+4 новые задачи, затем освобождённые4 слота; old-call не блокирует очередь.
|
||
detached_engine сохраняет encoder/call generation при перепривязке: старые
|
||
свежие и просроченные tasks не отправляются и не добавляют post-time/age-drop.
|
||
current_expiry, напротив, проверяет posts8/age-drop8 действующей привязки.
|
||
|
||
Нативная сборка ./build.sh --no-gui -j4 PASS: /tmp/call-audio-tx-build.log.
|
||
CMake configure/build PASS: /tmp/call-audio-tx-voice-{configure,build}.log.
|
||
Профиль13 новых+4 прежних голосовых тестов17/17 PASS,3,45с:
|
||
/tmp/call-audio-tx-voice-tests.log. После уточнения проверок повторены13/13,
|
||
0,42с: /tmp/call-audio-tx-regression-{build,tests}.log.
|
||
Все13 логов прочитаны/проверены: /tmp/call-audio-tx-<scenario>.log;
|
||
сводка /tmp/call-audio-tx-diagnostics-review.log проверяет все строки,
|
||
owner/эпохи/pending, отсутствие ERROR и неожиданных WARN, баланс аллокаций.
|
||
rebind_storm:128 старых callbacks сохраняют pending8 активной привязки.
|
||
same_owner:8 старых callbacks с owner1 уменьшают pending7..0; mixed —7..4.
|
||
|
||
До пересборки сохранён прежний libutun_voice.a, новый test object отдельно
|
||
перелинкован с ним в /tmp/call-audio-tx-baseline; архивы проекта не подменялись.
|
||
Те же13 сценариев:7 PASS,6 ожидаемых FAIL (reinit,different_instance,
|
||
reused_address,cross_loop,rebind_storm,detached_engine). Первые пять принимают
|
||
лишние8 задач, detached_engine отправляет8 старых кадров. Это проверяемые
|
||
последствия, не только сравнение новых диагностических строк.
|
||
Логи /tmp/call-audio-tx-baseline-{build,results,<scenario>}.log.
|
||
|
||
Первый полный check:201 passed/1 failed/0 skipped из202. Единственный FAIL —
|
||
test_radio_mesh: все кадры/FIN/порядок получены, но dupC=0. Изменённый call_audio
|
||
не линкуется в этот бинарник (nm); radio/uasync не менялись. Одиночный запуск
|
||
того же бинарника с UTUN_TEST_DEBUG=1 PASS, dup drop seq0..5, итоговый C dup6.
|
||
Условие завершения P_WAIT_RX1 не ждёт дубликатов; это наблюдение по коду,
|
||
точная причина нестабильности не установлена. Создана отдельная открытая задача.
|
||
Логи /tmp/call-audio-tx-full-check-first.log,
|
||
/tmp/call-audio-tx-radio-mesh-{first,debug}.log. Повторный полный прогон также
|
||
дал201 passed/1 failed/0 skipped из202 с тем же test_radio_mesh/dupC=0;
|
||
/tmp/call-audio-tx-full-check.log, /tmp/call-audio-tx-netns-repeat.log,
|
||
/tmp/call-audio-tx-radio-mesh-repeat.log. Зелёный полный check не заявляется.
|
||
Повторы общего check остановлены; причина отдельного отказа требует своего аудита.
|
||
|
||
Так как check.sh при этом не дошёл до proxy-интеграций, они выполнены отдельно
|
||
в таком же изолированном IPv6 namespace: check-proxy9/9 PASS, check-burst1/1
|
||
PASS (8388608 байт), check-load8/8 PASS (134217728 байт); общий exit0.
|
||
Одиночный test_radio_mesh с диагностикой в этом namespace также PASS:
|
||
окружение само по себе отказ не воспроизвело, причина не объявляется установленной.
|
||
Логи /tmp/call-audio-tx-integrations.log, /tmp/call-audio-tx-check-{proxy,burst,load}.log,
|
||
/tmp/call-audio-tx-radio-mesh-netns.log. Сетевые настройки хоста не менялись.
|
||
|
||
Полный check запускается в отдельном sudo unshare --net: IPv6/lo включён
|
||
только там, запуск от user; IPv6 хоста остаётся отключён. Аппаратный GUI/Android,
|
||
ASAN/UBSan/LSan не запускались. Аудит headless/платформ/видео ещё не завершён;
|
||
call остаётся0[V]/10[~]/11[ ], архитектурный документ[~]. Push не выполнялся.
|
||
|
||
9. Проверка test_radio_mesh: место дедупликации зависит от порядка прихода — 26.10.2026
|
||
|
||
Вывод: dupC>0 не является инвариантом diamond. Дедупликация успешно срабатывает
|
||
на другом узле, а тест выдаёт ложный FAIL. Подтверждено наблюдением настоящих
|
||
raw ETCP отправок/приёма до radio_recv; production radio/uasync не менялись.
|
||
|
||
В tests/test_radio_mesh.c добавлены диагностические снимки фаз/узлов/рёбер,
|
||
а в Unix-сборке --wrap=etcp_bind/etcp_send. Наблюдатели вызывают реальные функции,
|
||
не меняют payload, результат, порядок или владение entry. DEBUG_CATEGORY_DEBUG
|
||
явно включена на INFO; события показывают self/peer/src/group/stream/seq/FIN/hops.
|
||
Счётчики TX означают переданные etcp_send задачи, RX — вход в raw callback.
|
||
Возврат к радио-обработчику и дедупликация остаются настоящими.
|
||
|
||
Опция UTUN_TEST_MESH_TAIL оставляет прежний loop работающим ещё500мс после
|
||
исходного P_DONE. Снимок dupC до хвоста сохраняется для итогового вердикта,
|
||
поэтому диагностический хвост не превращает старый FAIL в PASS. Обычный режим
|
||
сохраняет прежнее чтение счётчика после выхода из uasync_mainloop.
|
||
Таймер хвоста исполняется в работающем loop, без sleep/повторного запуска
|
||
остановленного mainloop; флаг stop автоматически не сбрасывается.
|
||
|
||
Нативная сборка ./build.sh --no-gui -j4 и цель make -C tests test_radio_mesh PASS:
|
||
/tmp/radio-mesh-audit-build.log, /tmp/radio-mesh-audit-target-build.log.
|
||
Запущен один обычный тест и16 с хвостом, по8 параллельно. Итог16 PASS/1 FAIL.
|
||
Сводка /tmp/radio-mesh-audit-runs.log, подробные ...-run-00.log..16.log.
|
||
Проверка всех логов /tmp/radio-mesh-audit-evidence.log сверяет кадры/FIN/seq,
|
||
порядок raw RX, счётчики рёбер и отсутствие runtime ERROR/неожиданных WARN.
|
||
|
||
Полный разбор воспроизведённого FAIL: /tmp/radio-mesh-audit-run-01.log.
|
||
1) В14:54:49.182 A отправляет seq0..5 обоим B и D; seq5 — FIN.
|
||
2) D принимает их и пересылает C. В14:54:49.183 C пересылает их B.
|
||
3) B принимает все6 от C в14:54:49.183607..183631, впервые видит ключи
|
||
(src=A,stream=64106,seq0..5), доставляет локально5 кадров и FIN.
|
||
4) Прямые A→B приходят лишь в14:54:49.185416..185444. radio_seen_dup
|
||
возвращает1, radio_recv увеличивает dupB и выходит ДО дальнейшего flood.
|
||
Поэтому B→C TX0, C←B RX0, dupB6 и dupC0.
|
||
5) В P_DONE все B/D/C имеют frames5/FIN1/seq_ok1. Через500мс показатели
|
||
остаются теми же: это не доказанный случай раннего завершения ожидания C.
|
||
|
||
Код согласован с логом: radio_seen_dup ведёт общий для узла кэш src/stream/seq,
|
||
независимо от соседа/хопов. Первый путь выигрывает; последующий дубликат
|
||
отбрасывается до radio_flood. Оба A→B и A→D существовали и передали пакет;
|
||
второй путь до C не обязан завершиться, если пути уже сошлись раньше на B.
|
||
Нормальный запуск run00, напротив: B получает A раньше, отправляет C;
|
||
C получает обе копии и имеет dupC6. Порядок UDP/dispatch не задан контрактом.
|
||
|
||
Предлагаемое исправление теста: ждать полную доставку/FIN и проверять
|
||
дедупликацию на допустимом узле B/C/D; закрепить оба порядка прихода.
|
||
Не добавлять фиксированную задержку и не ждать только dupC: хвост показал,
|
||
что C вообще не получит вторую копию в корректном варианте обхода.
|
||
Критерий и production пока не исправлялись; задача остаётся открытой.
|
||
|
||
Новый общий check был запущен с диагностическим тестом, но по запросу
|
||
пользователя pull/rebase корректно прерван SIGTERM runner до изменения дерева.
|
||
Процессы тестов остановлены; /tmp/radio-mesh-audit-full-check.log и ...-netns.log
|
||
содержат interrupted143, поэтому полным результатом не считаются.
|
||
В этом общем прогоне сам test_radio_mesh прошёл; tests/logs/test_radio_mesh.log.
|
||
Санитайзеры и аппаратный GUI/Android в этой проверке не запускались.
|
||
|
||
git pull --rebase успешно выполнен на origin/master aea8ca72: перенесены все23
|
||
локальных коммита. Конфликты были только в doc/tasks.md; записи feedback/member
|
||
сохранены побайтно, старые call-задачи обновлены до их итоговых состояний.
|
||
Исходники локальных изменений сохранены; tests/Makefile.am содержит также новые
|
||
upstream member_purge sources. Рабочее дерево после rebase чистое.
|
||
Сам radio.c и диагностический тест побайтно совпадают с проверенными до rebase;
|
||
новая объединённая версия целиком ещё не собиралась/не тестировалась.
|
||
Исторические ID коммитов разделов1–8 относятся к состоянию до rebase;
|
||
диагностика после него: c2908a6c/d943c0dd/21af2e0f/ff394c7a. Push не выполнялся.
|
||
|
||
10. Исправление критерия mesh и проверка объединённой версии — 26.10.2026
|
||
|
||
По запросу пользователя исправлен tests/test_radio_mesh.c; production radio,
|
||
uasync и протокол не менялись. Коммиты1bc27aa2/bf4d14c4.
|
||
Теперь каждый burst требует ровно5 кадров по порядку и один корректный FIN
|
||
на B/D/C, отсутствие локального эха A и суммарно не менее6 удалённых дублей
|
||
на B/D/C. Счётчики берутся относительно начала burst; прежний сценарий
|
||
не может дать ложное подтверждение следующему. Дополнительно проверяются
|
||
src/stream, FIN после всех кадров, повтор FIN и data после FIN.
|
||
|
||
Unix-wrapper считает отправки и входы в raw RX: завершение разрешено только
|
||
после совпадения TX[i][j] и RX[j][i] для всех рёбер и отсутствия удержанных
|
||
задач. Радио-forward из принятого пакета уже завершён к снимку на следующем
|
||
таймере. Поэтому отложенная копия тоже должна пройти настоящий radio_recv;
|
||
успех не объявляется между первыми данными и ещё ожидающим дубликатом.
|
||
|
||
Три последовательных burst на тех же4 настоящих ядрах и одном UASYNC:
|
||
0) обычный порядок доставки;
|
||
1) gate держит6 прямых A→B entry до получения B всех5 кадров+FIN через C;
|
||
затем передаёт entry реальному etcp_send. Обязательны dupB6/dupC0,
|
||
отсутствие B→C TX и C←B RX;
|
||
2) симметричный gate для A→D: обязательны dupD6/dupC0 и отсутствие D→C TX.
|
||
Payload, stream/seq и настоящий кэш radio_recv не подменяются. Возвращая0,
|
||
gate принимает владение entry; при release передаёт его real ETCP, при
|
||
незавершённом сценарии освобождает оставшийся хвост до destroy инстансов.
|
||
В исходящем пути после real etcp_send entry больше не читается.
|
||
|
||
Диагностический хвост UTUN_TEST_MESH_TAIL удалён; фиксированных задержек
|
||
для получения нужного порядка нет. Обычный clock/timer остаётся прежним.
|
||
Пакетные сообщения переведены в TRACE, INFO сохраняет фазы/итоги/gate/cleanup.
|
||
DEBUG_CATEGORY_DEBUG явно включена на INFO; UTUN_TEST_DEBUG включает её TRACE.
|
||
На платформе без Unix-wrapper исполняется обычный burst с общим критерием
|
||
дедупликации. Сборка/запуск этой ветки проверены на Linux с -UUTUN_TEST_MESH_OBSERVE;
|
||
это не проверка Windows или FreeBSD.
|
||
|
||
После rebase origin/master aea8ca72 выполнена нативная сборка
|
||
./build.sh --no-gui -j4 PASS и make -C tests test_radio_mesh PASS:
|
||
/tmp/radio-mesh-fix-build.log, /tmp/radio-mesh-fix-target-build.log.
|
||
Первый тест PASS, все3 сценария, tracked allocations0->0:
|
||
/tmp/radio-mesh-fix-test.log. Ещё16 процессов, по8 параллельно, каждый
|
||
с3 сценариями:16/16 PASS, всегда dupB6/dupD0/dupC0 в сценарии1 и
|
||
dupB0/dupD6/dupC0 в сценарии2, allocations0->0. Это48 дополнительных burst;
|
||
/tmp/radio-mesh-fix-repeat-00.log..15.log и ...-repeat-results.log.
|
||
|
||
Проверена чувствительность условий на временных копиях вне проекта:
|
||
- возврат старого условия dupC>0 даёт ожидаемый FAIL в сценарии1 после
|
||
настоящей обходной доставки на B; cleanup allocations0->0;
|
||
- искусственная лишняя локальная доставка даёт frames6 и ожидаемый FAIL
|
||
по FIN/числу/порядку; cleanup allocations0->0;
|
||
- ветка без Unix-wrapper компилируется и проходит обычный burst.
|
||
Ни production, ни архивы/объекты проекта для этого не заменялись.
|
||
Сборка использует переменные штатного tests/Makefile через временный overlay;
|
||
/tmp/radio-mesh-fix-negative.mk, ...-negative-build.log, ...-negative-results.log,
|
||
...-old-criterion.log, ...-extra-delivery.log, ...-no-wrap.log.
|
||
|
||
CMake-сборка всех голосовых целей после rebase PASS; CTest17/17 PASS,3,46с:
|
||
call_jitter/delay/timeline/voice_tx4 и call_audio_tx13.
|
||
Логи /tmp/radio-mesh-fix-voice-{configure,build,tests}.log.
|
||
|
||
Полный ./check.sh:202 passed,0 failed,0 skipped (202 total),exit0,176с.
|
||
Включены call_admission,radio_mesh,member_adapter/feedback и остальные тесты
|
||
объединённой версии; в общем mesh все3 сценария PASS,allocations0->0.
|
||
Интеграции того же запуска: proxy9/9,burst1/1 (8388608 байт),load8/8
|
||
(134217728 байт), обе стороны rc0. Логи /tmp/radio-mesh-fix-full-check.log,
|
||
/tmp/radio-mesh-fix-netns.log и tests/logs/test_radio_mesh.log.
|
||
Все проверки прошли после единственного текущего изменения исходников теста;
|
||
дальнейшие правки — только документация. Повтор общего набора не требуется.
|
||
Запуск из sudo unshare --net от user, IPv6/lo включены только там;
|
||
IPv6 хоста all/default/lo остаётся отключён. Санитайзеры, аппаратный GUI,
|
||
Android, Windows и FreeBSD в этом исправлении не запускались.
|
||
Аудит остальных контрактов call остаётся частичным; отметки0[V]/10[~]/11[ ] сохранены.
|
||
|
||
11. Headless PCM: разрыв фрейма при переполнении очереди — 26.10.2026
|
||
|
||
База продолжения03b05094, master совпадал с origin/master. Полностью прочитаны
|
||
call_headless.c/.h и относящиеся к TX/RX/cleanup функции test_call_headless;
|
||
запуск/остановка listener в chat_service_start/stop, media callback в call.c.
|
||
Сервис имеет собственные enc/dec на instance, отдельные от call_audio DSP.
|
||
Все его операции выполняются в uasync; stop снимает callback и освобождает
|
||
listener/клиентов/таймер/кодеки до call_destroy. Максимум16 клиентов,
|
||
единственный callback потребителя Opus; темп/джиттер готового PCM — у клиента.
|
||
Заголовки уточняют владение/потоки и существующий порядок байтов; API не меняется.
|
||
Полный аудит GUI/Android и всех ветвей headless ещё не завершён.
|
||
|
||
Подтверждённое расхождение
|
||
|
||
ca_write_frame формирует header11 байт и дважды вызывает ca_send_raw:
|
||
сначала header, затем PCM. При уже непустой очереди ca_send_raw проверяет
|
||
место отдельно для каждого фрагмента; если len>space, молча возвращается.
|
||
Поэтому возможен принятый header с отброшенным телом. После возобновления
|
||
записи клиент получает неполный фрейм, а очередной header попадает в его PCM.
|
||
Это нарушение границ протокола, а не только потеря одного20мс аудиокадра.
|
||
Штатный тест ищет один готовый PCM-фрейм и эту границу очереди не проверяет.
|
||
|
||
Локальная компонентная диагностика
|
||
|
||
/tmp/call-headless-audit-queue.c включает временную копию call_headless.c.
|
||
Единственное изменение этой копии — WARN перед существующим silent drop
|
||
с fd/call/fragment/queued/offset/space. Diff прочитан: логика исходной функции
|
||
не меняется. Файлы проекта и их архивы/объекты не подменяются.
|
||
Сборка через штатные test_call_headless_CFLAGS/LDADD в overlay Makefile:
|
||
/tmp/call-headless-audit-queue.mk; build ...-queue-build.log PASS.
|
||
|
||
Стенд создаёт один настоящий UASYNC и loopback TCP listener с port0 через
|
||
call_headless_init. Обычный клиент подключается и передаёт HELLO. В том же
|
||
потоке ca_media_recv получает реальные20мс Opus-пакеты от настоящего encoder;
|
||
decoder преобразует их в PCM. Отдельного настоящего call_ctx/звонка/роутера
|
||
в этом компонентном тесте нет: вызывается именно headless media callback.
|
||
Поля очереди/счётчики читаются для наблюдения, но вручную не заполняются.
|
||
--wrap=send задаёт EAGAIN только исходящему fd этого клиента; после снятия
|
||
backpressure настоящий TCP передаёт накопленные байты, строгий последовательный
|
||
парсер проверяет границы без сканирования/поиска следующего похожего header.
|
||
|
||
Контроль control:8 кадров, затем ещё один после drain. Получено8/9 полных
|
||
фреймов, remaining0, aligned1, allocations0->0,exit0.
|
||
Pressure:40 обычных вызовов callback при blocked send. Первые33 помещаются
|
||
целиком, queued63723. Следующий header увеличивает queued до63734, но
|
||
PCM1920 не помещается в оставшиеся1802 байта и отбрасывается. Таких orphan
|
||
header остаётся7, всего77 байт. Очередь63800 содержит33 полных фрейма и хвост77.
|
||
После drain/следующего настоящего кадра парсер встречает неверный header;
|
||
границы не восстановились. dec41/dec_drop0 в штатном destroy дополнительно
|
||
показывает отсутствие диагностики потери в исходном коде.
|
||
Pressure завершается ожидаемым exit1; allocations0->0. Логи control/pressure
|
||
прочитаны полностью: /tmp/call-headless-audit-queue-{control,pressure}.log.
|
||
|
||
Штатный tests/test_call_headless повторён:1/1 PASS,exit0;
|
||
/tmp/call-headless-audit-baseline.log. Он проверяет настоящий звонок двух ядер
|
||
через JSON control/TCP PCM, но только один media frame и не покрывает overflow.
|
||
Прежние202/202 и C++17/17 из раздела10 не являются тестом этого нового условия.
|
||
|
||
План исправления
|
||
|
||
1) Собрать полный ограниченный PCM-фрейм header+body и передавать его
|
||
ca_send_raw одним вызовом. При нехватке места принимать/отбрасывать
|
||
целый кадр; отдельно логировать/учитывать отбрасывание. Wire/API не меняются.
|
||
2) Сохранить корректную обработку partial send: уже переданный prefix
|
||
продолжается сохранённым tail; новый кадр не вклинивается в него.
|
||
Компактизация использованного места — отдельное улучшение, для целостности
|
||
не обязательна; рефакторинг всего сервиса на tcp_io не требуется.
|
||
3) Добавить постоянную регрессию на normal/EAGAIN/short-write, точную границу
|
||
очереди и следующий кадр после drain. Удерживать исходный e2e и cleanup-проверки.
|
||
4) Сборка/целевые проверки и общий check после внедрения. Аналогичный шаблон
|
||
rh_write_frame/rh_send_raw замечен при сравнении; в этом аудите radio overflow
|
||
отдельно не воспроизводился, полный radio-аудит не заявляется.
|
||
|
||
По AGENTS.md:10 реализация не исправляется без следующего согласованного шага.
|
||
Добавлены предупреждение/контракт header, заметка архитектуры и открытая задача;
|
||
call_headless.h теперь[~]. Call0[V]/11[~]/10[ ], архитектурный документ[~],
|
||
видео[ ] сохранены. Новый полный build/check, sanitizers, аппаратный GUI/Android,
|
||
Windows/FreeBSD здесь не запускались. Исправления предыдущих разделов сохранены.
|
||
|
||
12. Headless PCM: целый кадр в ограниченной очереди — 26.10.2026
|
||
|
||
Пользователь «исправляй» разрешил внедрение плана раздела11. Базаae9d3f01;
|
||
коммиты913ae531/7c4e6f99/0ad80f39. Изменены только call_headless.c/.h,
|
||
tests/test_call_headless_queue.c и его цель в tests/Makefile.am; остальные
|
||
изменения этого шага относятся к документации/чек-листу. Wire и публичный API
|
||
сохранены, реализация radio_headless и общий call_audio DSP не менялись.
|
||
|
||
Реализация
|
||
|
||
ca_write_frame собирает header11+PCM до единственного вызова ca_send_raw.
|
||
Буфер ограничен CA_PCM_FRAME_BYTES1920+CALL_AUDIO_HDR_SIZE11, находится
|
||
на стеке; oversize и ненулевой len с NULL отклоняются с ERROR до memcpy.
|
||
При ожидающем tail очередь проверяет место для целого нового кадра:
|
||
если его недостаточно, ни header, ни PCM не дописываются. Успешный partial
|
||
send сохраняет только неотправленный tail; следующие кадры стоят после него.
|
||
Нет новых динамических выделений, таймеров или дополнительных владельцев.
|
||
|
||
Переполнение увеличивает pcm_dropped клиента и c_pcm_dropped сервиса;
|
||
decode-drop остаётся отдельным показателем. WARN показывает call_id/fd,
|
||
размер кадра, send_len/offset/capacity и оба счётчика. Первый drop логируется
|
||
сразу, следующие WARN ограничены одним в секунду (10000 timebase units).
|
||
При закрытии клиента и destroy сервиса INFO выводят полные итоги pcm_drop,
|
||
включая потери между WARN. Использованный prefix буфера до полного drain
|
||
не компактизируется: это прежняя политика ёмкости, целостность не нарушается.
|
||
|
||
Постоянная регрессия
|
||
|
||
tests/test_call_headless_queue.c включён в check_PROGRAMS для !OS_WINDOWS;
|
||
нужен GNU --wrap=send. Стенд включает production .c, как другие компонентные
|
||
тесты проекта. Один настоящий uasync на поток, свежий loopback TCP listener
|
||
с port0/клиент/HELLO и настоящие Opus encoder/decoder для каждого сценария.
|
||
Минимальный instance не создаёт call_ctx/роутер: media callback вызывается
|
||
напрямую в том же потоке. Полноценный звонок двух ядер сохраняет штатный e2e.
|
||
Поля очереди только наблюдаются, она заполняется обычными media callbacks.
|
||
Wrapper задаёт EAGAIN/short write только серверному fd; успешный prefix
|
||
действительно передаётся через TCP. Независимый reference decoder обрабатывает
|
||
все Opus-пакеты, включая отброшенные для TCP, сохраняя такое же состояние.
|
||
Сравнивается весь ожидаемый wire header+PCM байт в байт, без поиска похожего
|
||
следующего header. CHECK действует и при NDEBUG.
|
||
|
||
11 сценариев PASS:
|
||
- normal:8 кадров без backpressure, затем следующий после drain;
|
||
- frame_boundary:34 попытки,33 принятых,1 целый drop;
|
||
- overflow:40 попыток,33 принятых,7 целых drops;
|
||
- partial_header:первые5 байт переданы TCP, далее EAGAIN;40/33/7;
|
||
- partial_pcm:первые113 байт переданы TCP, далее EAGAIN;40/33/7;
|
||
- exact_fit:первые118 байт переданы,34 кадра заполняют очередь ровно65536,
|
||
потерь нет; exact_overflow:35-я попытка отбрасывается целиком;
|
||
- chunked_write:prefix5, затем drain порциями не более127 байт;
|
||
- offset_overflow:после33 queued кадров отправлены118 байт, новая попытка
|
||
отбрасывается без изменения send_len/offset; оставшийся поток корректен;
|
||
- destroy_pending:8 queued кадров освобождаются без drain;
|
||
- invalid_output:oversize1921 и NULL+1920 дают ровно2 ожидаемых ERROR,
|
||
не изменяют очередь/переданные байты; следующий допустимый кадр проходит.
|
||
После каждого обычного drain проверен следующий кадр с непрерывным состоянием
|
||
Opus. В каждом сценарии проверяются точные PCM-счётчики закрытия/destroy,
|
||
наличие WARN, баланс uasync socket/timer и allocations0->0 после cleanup.
|
||
Лог /tmp/call-headless-fix-queue.log прочитан полностью:11/11 PASS;
|
||
6 первых overflow-WARN и2 намеренных ERROR, неожиданных ERROR нет.
|
||
|
||
Чувствительность теста проверена без замены production/архивов/объектов:
|
||
временная копия регрессии включает старый instrumented .c из раздела11.
|
||
Normal PASS, на frame_boundary старый код оставляет header, queued63734,
|
||
отбрасывает PCM1920 при space1802. Проверка ожидаемой длины сразу FAIL,
|
||
ожидаемый returncode=-6 (CHECK/abort), core dump отключён для этого запуска.
|
||
До abort первый fixture освобождён с allocations0->0; cleanup второго
|
||
прерван и его баланс этим негативным запуском не заявляется.
|
||
/tmp/call-headless-fix-old-test.c/.mk, ...-old-build.log, ...-old-test.log.
|
||
|
||
Проверка окончательного исходного кода
|
||
|
||
./build.sh --no-gui -j4 PASS; после финальной правки0ad80f39 целевая
|
||
make-сборка PASS, новая регрессия11/11 и штатный test_call_headless1/1 PASS.
|
||
Логи /tmp/call-headless-fix-{build,target-build,queue,e2e}.log.
|
||
Штатный e2e использует JSON control, два настоящих ядра и TCP PCM;
|
||
он дополняет компонентную проверку очереди и остаётся однокадровым.
|
||
|
||
Полный ./check.sh:203 passed,0 failed,0 skipped (203 total),exit0,188с.
|
||
Новая цель входит в этот запуск; proxy9/9,burst1/1 (8388608 байт),
|
||
load8/8 (134217728 байт), обе стороны rc0. Логи
|
||
/tmp/call-headless-fix-full-check.log, ...-netns.log,
|
||
tests/logs/test_call_headless_queue.log и test_call_headless.log.
|
||
Запуск в отдельном sudo unshare --net от user; IPv6/lo включены только там.
|
||
Настройки хоста all/default/lo disable_ipv6=1 сохранены и проверены.
|
||
После этих проверок менялись только документация/чек-лист.
|
||
|
||
Исправление фрейминга закрыто. Отдельная задача по похожему шаблону
|
||
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
|
||
этим продолжением не выполнялись. Новый дефект не исправлен; задача открыта.
|
||
|
||
14. Android mute после AEC/AGC: внедрение и проверка — 26.10.2026
|
||
|
||
Следующее «ОК» разрешило план раздела13. База03be46c3; production-коммиты
|
||
a4435edb/4fcb2ab3, постоянная регрессия74375e23. Общий call_audio DSP,
|
||
wire, desktop prepared path и другие Android I/O-механизмы не менялись.
|
||
|
||
Реализация
|
||
|
||
В CallAudioEngine.captureLoop убран silence/out: на завершённый20мс кадр
|
||
берётся один snapshot frameMuted, в NativeLib передаются исходный buf,
|
||
этот флаг и прежний capture_pts_us. Флаг проходит через nativeCallAudioFeed
|
||
и jboolean JNI в C bridge; bridge вызывает call_audio_feed_pcm_muted.
|
||
Core AEC и AGC продолжают обрабатывать настоящий микрофон, а mute зануляет
|
||
только PCM непосредственно перед Opus. Обновлены все production callers,
|
||
объявление C bridge и комментарии API; совместимость со старой JNI-сигнатурой
|
||
не добавляется. JVM descriptor публичного и native feed теперь (J[SZJ)Z.
|
||
|
||
Нового глобального состояния mute, владельцев, блокировок, таймеров или
|
||
DSP-режимов нет. Метрики capture latency включают mutedFrames за интервал;
|
||
feed-отказ логирует call_id, snapshot mute и PTS. Эти кадры означают попытки
|
||
захвата/подачи, не подтверждённую доставку. Частота сводки остаётся раз в секунду.
|
||
Прежние INFO setMuted on/off сохраняются; логов на каждый успешный feed нет.
|
||
|
||
Постоянная регрессия реального bridge
|
||
|
||
tools/chatgui/tests/test_android_call_mute.cpp и цель CMake собирают настоящий
|
||
host android_jni_bridge.c с function/data sections и gc-sections. Нужен GNU
|
||
--wrap, поэтому цель в существующей ветке UNIX AND NOT APPLE. Проверяется
|
||
C feed bridge; JNI-маршалинг/AudioRecord/AudioTrack на устройстве не исполняются.
|
||
|
||
Настоящие call_audio/Opus/AEC/AGC/jitter/CallDelay/uasync, один uasync на поток.
|
||
Искусственный удалённый Opus проходит on_media/pull и становится настоящим
|
||
core AEC reference. Микрофон=render/2, начальный FIFO заполняется публичным
|
||
callback. Синтетический get_time_tb шагает20мс, delay1 задаётся публичным API;
|
||
network sink и настройки заменены наблюдателями. Wrapper AEC/AGC/encode
|
||
только сравнивает/читает PCM и вызывает настоящую функцию с прежними аргументами.
|
||
Приватные поля/кодеки/фильтры вручную не заполняются.
|
||
|
||
После300 кадров прогрева проверяются по20 before/muted/after, всего360 TX.
|
||
Перед каждым AEC подтверждается исходный raw PCM; AGC получает точный выход
|
||
AEC (либо raw при AEC off), его enabled соответствует настройке. Encoder
|
||
получает точный выход AGC до/после mute и строго нули во время mute.
|
||
Проверяются неизменность массива caller, PTS каждого posted callback,
|
||
capture epoch1/codec delay, пустая posted queue, баланс socket/timer и
|
||
u allocations0->0. CHECK действует при NDEBUG, PCM-несовпадения накапливаются
|
||
до полного cleanup, затем возвращается FAIL.
|
||
|
||
Пять CTest-сценариев PASS:
|
||
- четыре AEC0/1 × AGC0/1: в каждой muted-фазе Opus nonzero0,peak0,RMS0,
|
||
raw остаётся ненулевым; before/after Opus ненулевой и побайтно равен
|
||
наблюдаемому выходу DSP. AEC/AGC продолжают вызываться во время mute;
|
||
- legacy-контроль передаёт тишину с muted0, воспроизводя прежний Kotlin.
|
||
На320-м кадре после прогрева/before-фазы AEC снова создаёт19192/19200
|
||
ненулевых отсчётов,peak7676,RMS1591,958; контроль ожидает именно этот сигнал.
|
||
После unmute RMS528,486 против9,151 у правильного mute при AEC1/AGC0;
|
||
это искусственный компонентный сигнал, не акустический замер устройства.
|
||
Все5 cleanup:sent360,PTS_epoch1,allocations0->0; mismatch0 у правильного пути.
|
||
Лог новых сценариев полностью прочитан: /tmp/call-android-mute-fix-voice-details.log.
|
||
|
||
Чувствительность к потере флага подтверждена отдельно: неизменённый объект
|
||
постоянного теста с прежним телом bridge из03be46c3. Только сигнатура адаптирована
|
||
под новый muted-аргумент, который игнорируется; тело по-прежнему вызывает
|
||
call_audio_feed_pcm. Текущие production source/объекты/архивы не заменяются.
|
||
Сценарий AEC1/AGC0 даёт muted opus_nonzero18450,mismatches18450,
|
||
before/after PASS, затем полноценный cleanup allocations0->0 и ожидаемый exit1.
|
||
Это проверка утраченного флага в bridge; отдельный legacy-контроль выше
|
||
проверяет сам прежний порядок раннего зануления Kotlin.
|
||
/tmp/call-android-mute-fix-{old-bridge.c,negative-build.py,negative-build.log,
|
||
negative-result.log,negative.log}; отрицательный лог прочитан полностью.
|
||
|
||
Сборка и общий профиль
|
||
|
||
./build.sh --no-gui -j4 PASS. CMake configure/build PASS; профиль30/30 PASS,
|
||
20,67с: прежние25 voice/desktop тестов и5 новых mute-сценариев. Логи
|
||
/tmp/call-android-mute-fix-{native-build,voice-configure,voice-build,voice-tests,voice-details}.log.
|
||
Предупреждения/ERROR существующих I/O-тестов относятся к их намеренным
|
||
fault/watchdog проверкам; у5 новых положительных сценариев ERROR/WARN нет.
|
||
|
||
Android ./gradlew assembleDebug --max-workers=4 PASS: реальные NDK-сборки
|
||
arm64-v8a и x86_64, Kotlin/JNI, APK содержит оба libutun.so.
|
||
Первый запуск1м51с; финальный повтор после форматирования строки диагностики3с.
|
||
Это сборка APK без установки. javap подтвердил оба feed-descriptor (J[SZJ)Z.
|
||
Gradle :app:testDebugUnitTest для CallAudioTimingTest8 и CallAudioTraceTest6:
|
||
14/14,failures0,errors0,skipped0,BUILD SUCCESSFUL7с. Это тесты часов/trace,
|
||
не исполнение Android captureLoop или native-библиотеки на телефоне.
|
||
Логи /tmp/call-android-mute-fix-{android-build,android-final-build,android-unit}.log;
|
||
JUnit XML в tools/chatgui-android/app/build/test-results/testDebugUnitTest/.
|
||
|
||
Полный ./check.sh:203 passed,0 failed,0 skipped (203 total),exit0,168с.
|
||
Новая C++ voice-регрессия относится к CMake-профилю30, в C-only check203 не входит.
|
||
В общем наборе call/headless/Android replies и остальные тесты PASS;
|
||
proxy9/9,burst1/1 (8388608 байт),load8/8 (134217728 байт), обе стороны rc0.
|
||
/tmp/call-android-mute-fix-{full-check,netns}.log. Весь общий вывод прочитан.
|
||
IPv6/lo включены только в отдельном sudo unshare --net; check выполнялся от user.
|
||
Настройки хоста all/default/lo disable_ipv6=1 сохранены и проверены.
|
||
|
||
Исправление mute закрыто, задача и контракты обновлены. После проверок
|
||
менялись только документация/внешний чек-лист. Общий голосовой/платформенный
|
||
аудит остаётся частичным:call0[V]/11[~]/10[ ],desktop8[~],архитектура[~],видео[ ].
|
||
Новые sanitizer, аппаратный Android/desktop, Windows/FreeBSD проверки не
|
||
запускались. Реальная акустика/переключение mute на устройстве требуют отдельного
|
||
контроля; непосредственное последействие lossy Opus-декодера этим тестом не измеряется.
|
||
Новые коммиты локальные, push в этом шаге не выполнялся.
|