410 lines
48 KiB
Markdown
410 lines
48 KiB
Markdown
# Жизненный цикл ядра и сервисов
|
||
|
||
CLI выполняет daemon fork до создания `UASYNC`, SQLite, сокетов и фоновых потоков.
|
||
Сервисы и их workers создаются уже в дочернем процессе. Родитель ждёт подтверждения
|
||
успешной инициализации и записи PID-файла; ошибка старта возвращается вызывающему.
|
||
`test_daemon_worker` проверяет настоящий запуск без `-f`, наличие дискового потока,
|
||
штатный SIGTERM и ненулевой статус launcher при ошибке PID-файла.
|
||
|
||
Все операции выполняются в потоке единственного `UASYNC` экземпляра, вне callbacks
|
||
останавливаемых модулей. GUI передаёт команды в этот поток.
|
||
|
||
```c
|
||
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config);
|
||
if (!inst || utun_core_start(inst) < 0) /* обработать ошибку */;
|
||
if (chat_service_start(inst) < 0) /* обработать ошибку */;
|
||
if (utun_service_start(inst) < 0) /* обработать ошибку */;
|
||
|
||
chat_service_stop(inst); /* UTUN продолжает работать */
|
||
chat_service_start(inst); /* загружает сохранённые каналы */
|
||
utun_service_stop(inst); /* чат продолжает работать */
|
||
utun_instance_destroy(inst);
|
||
```
|
||
|
||
`utun_instance_init()` — сокращение для запуска ядра, UTUN и чата, если чат включён
|
||
в конфигурации. Desktop GUI запускает ядро, UTUN и чат явно; TUN создаётся при
|
||
`[global] tun_enabled=yes`. Без TUN и NAT запуск чата не требует прав администратора.
|
||
Desktop подтверждает start только после готовности всех сервисов и uasync poll;
|
||
ошибка TUN откатывает запуск и возвращается в GUI. Android запускает ядро и чат без UTUN.
|
||
Повторный start работающего сервиса и повторный stop допустимы. Ошибка старта
|
||
сервиса откатывает созданные им ресурсы. `utun_instance_stop()` останавливает цикл
|
||
приложения; освобождение ядра выполняет `utun_instance_destroy()`.
|
||
|
||
Для TUN в desktop `vibechat.cfg` задаются те же параметры, что и у CLI:
|
||
|
||
```ini
|
||
[global]
|
||
tun_enabled=yes
|
||
tun_ifname=vibechat
|
||
tun_ip=10.42.0.1
|
||
|
||
[routing]
|
||
my_subnet=10.42.0.1/32
|
||
route_subnet=10.42.0.0/24
|
||
```
|
||
|
||
Адреса и маршруты выбираются под свою сеть; изменения применяются после перезапуска.
|
||
На Windows нужна `wintun.dll` рядом с `vibechat.exe` и запуск с правами администратора.
|
||
CMake копирует DLL и лицензии из `lib/`; Windows-упаковка явно включает DLL, проверяет
|
||
её архитектуру x64 и зависимости, поскольку `LoadLibrary` не отражается в импортах EXE.
|
||
На Linux необходимы доступ к `/dev/net/tun` и права на настройку интерфейса и маршрутов.
|
||
|
||
Успех запуска требует успешной инициализации всех обязательных ресурсов: заданных
|
||
listen-сокетов, мониторинга, TUN и всех `route_subnet` при включённом TUN. Частичный
|
||
набор сокетов или маршрутов — ошибка запуска. `utun_started` устанавливается только
|
||
после завершения всех шагов. Системные маршруты хранятся отдельным списком успешно
|
||
созданных записей; stop и откат удаляют только этот список, в обратном порядке.
|
||
Маршрут, на добавлении которого ядро вернуло ошибку, не становится собственностью
|
||
UTUN и при откате не удаляется. Ошибка одного удаления не прекращает очистку остальных.
|
||
|
||
`auto_sockets=android` выбирает внешнее управление сетью: frontend регистрирует
|
||
ConnectivityManager до запроса запуска и передаёт конфигурацию сокетов через JNI.
|
||
Оба нативных монитора ОС в этом режиме не создаются. Рабочие listen-сокеты по-прежнему
|
||
должны открыться полностью. Android подтверждает запуск по `SERVICE_STARTED`, а не
|
||
по успешному созданию потока; после события передаётся свежий снимок адресов.
|
||
|
||
| Владелец | Ресурсы |
|
||
|---|---|
|
||
| Ядро | Идентичность узла, реестр узлов и групп, SQLite, транспортные сокеты, NCD, маршрутизатор, общая репликация, время, мониторинг |
|
||
| UTUN | Группа UTUN, запросы соединений из конфигурации, DATA binding, TUN, системные маршруты, NAT transport |
|
||
| Чат | CHAT-группы, запросы подключения и join, экземпляры синхронизации каналов, chat API, медиа, личные сообщения, звонки и headless API |
|
||
| Группа | Сессии пиров, её маршруты и логические каналы, планировщик подключения и recovery |
|
||
| Сессия пира группы | NCD handle с момента CONNECTING, согласованные эпохи, состояние обмена таблицей, очередь отправки |
|
||
|
||
Proxy настраивается при создании ядра. Instance владеет общими UDP/ICMP-контекстами;
|
||
TCP client/server владеют только своими bindings, listeners и TCP-потоками.
|
||
Общие контексты нужны для exit либо клиентского TUN; SOCKS/HTTP без TUN их не требуют.
|
||
Ошибка обязательного listener, TUN mapping или регистрации откатывает создание instance.
|
||
Недоступность raw ICMP по правам или поддержке отключает только ICMP exit с WARN;
|
||
клиентские ICMP REPLY и TCP/UDP продолжают работать.
|
||
`utun_instance_destroy()` закрывает proxy и обрабатывает отложенные free до остановки
|
||
UTUN/CHAT-групп и транспорта. Waiters/таймеры снимаются до освобождения их владельцев.
|
||
Остановка отдельного TCP proxy-модуля не уничтожает общие UDP/ICMP-контексты.
|
||
|
||
SQLite открывается ядром один раз: `db_path/chats.db`, либо `:memory:`, если путь
|
||
не задан. Остановка чата сохраняет каналы и сообщения в этой БД. В варианте
|
||
`:memory:` данные сохраняются только до уничтожения ядра.
|
||
|
||
Работающий общий транспорт сохраняется, пока он нужен хотя бы одному из пиров. Закрытие запроса одной группы
|
||
не разрывает соединение, нужное другой группе. При остановке группы отменяются
|
||
её планировщики, запросы, таймеры и ожидания очередей; отправляется LEAVE,
|
||
закрываются логические каналы маршрутизатора и удаляется её ожидающий транзит
|
||
на общих соединениях. Транзит других групп сохраняется. Отложенный LEAVE может временно
|
||
удерживать транспорт самостоятельно, не обращаясь к уже удалённой группе.
|
||
|
||
ETCP удаляет транспорт после 30 секунд непрерывного DOWN только при отсутствии локальных NCD handles.
|
||
Первоначальное ожидание UP тоже учитывает владение. Повторный DOWN не продлевает
|
||
таймаут; восстановление UP отменяет его. Если срок истёк при наличии handles, транспорт
|
||
сохраняется, а исходный deadline остаётся. Освобождение последнего handle во время DOWN
|
||
оставляет транспорт до этого deadline либо запускает удаление в следующем callback,
|
||
если срок уже истёк. Внешние ETCP refs задерживают только освобождение памяти.
|
||
Явный force-close и отмена неудачной попытки последнего владельца закрывают сразу.
|
||
|
||
Входящий UP без локальных handles сохраняется: он может быть нужен пиру. CLOSE/KEEP_ALIVE
|
||
привязан при запуске ядра, поэтому мягкое закрытие работает и на чисто входящем узле.
|
||
На CLOSE при наличии handles отвечаем KEEP_ALIVE; без них закрываем транспорт.
|
||
KEEP_ALIVE полностью отменяет FIN_WAIT. Ожидание CLOSE обычно занимает 5 секунд,
|
||
для спящего пира увеличивается до его RX deadline. Сам факт DOWN во время FIN_WAIT
|
||
завершает ранее запрошенное мягкое закрытие.
|
||
|
||
Контроль RX независим от отправки keepalive. Объявленный сон не отключает проверку
|
||
жизни: срок от последнего RX равен максимуму согласованного keepalive timeout и
|
||
объявленного остатка ACTIVE+SLEEP с запасом normal timeout. Фазовые таймеры и наши
|
||
отправки срок жизни пира не продлевают. Пробуждение сразу сокращает deadline до normal timeout.
|
||
После истечения последнего линка транспорт
|
||
становится DOWN, затем может удаляться через 30 секунд, когда локальных NCD handles нет. `test_etcp_server_lifecycle`
|
||
проверяет сроки, несколько путей, восстановление, владение, сон и CLOSE/KEEP_ALIVE
|
||
с управляемыми часами и контролем освобождения памяти/таймеров.
|
||
|
||
Остановка чата сначала прекращает внешние команды и фоновые задания, затем
|
||
закрывает медиа и сервисы, синхронизацию и группы, после чего освобождает chat core.
|
||
`media_async_destroy()` дожидается завершения native workers и вызывает каждый
|
||
ожидающий callback с `MEDIA_ASYNC_CANCELLED`, пока его контекст ещё существует.
|
||
Worker резервирует completion до запуска и передаёт его через `uasync_post_reserved`;
|
||
периодического polling нет. Destroy выполняется вне callbacks uasync, после join
|
||
отменяет pending post и завершает done ровно один раз. Uasync живёт до конца этой очистки.
|
||
Подготовленные блоки отменённых задач не регистрируются; учёт назначений подготовки
|
||
сохраняется для последующей очистки. Это синхронное ожидание: длительное
|
||
транскодирование или распознавание речи может задержать stop; принудительного
|
||
прерывания этих библиотек нет.
|
||
|
||
Владелец принимает не более 16 задач, включая уже завершённые workers до доставки
|
||
completion. Отказ запуска/неверные аргументы дают синхронный done с -1, остановка
|
||
владельца — с `MEDIA_ASYNC_CANCELLED`; при NULL done только логируется ошибка.
|
||
Примитивы `media_async.h` синхронные: отмена задачи не откатывает выполненные ими
|
||
действия. `ma_copy_file()` публикует копию внутри worker после проверенных записи и
|
||
закрытия temp; вызывающий отвечает за очистку своего результата при отмене.
|
||
Подробные файловые контракты, 64-битные размеры и ошибки RNG описаны в `media_async.h`.
|
||
|
||
Групповой crypto worker хранит один `file_io_temp` прямо в задаче вложения.
|
||
Общий валидатор MIME не выполняет I/O: send вызывает его в uasync до создания
|
||
задачи/ключа, файловый codec повторяет проверку в worker до открытия файлов.
|
||
`encrypted_file_write/read` только готовят закрытый durable temp; worker публикует
|
||
его одним rename, completion подтверждает результат в БД/UI. Отмена до completion
|
||
не публикует сообщение; cleanup учитывает реально созданные/заменённые файлы.
|
||
Ошибка синхронизации каталога после rename означает установленное имя без подтверждённой
|
||
durability; отложенный discard уже не удаляет результат. Общей транзакции ФС/БД нет.
|
||
До запуска workers и после их завершения recovery очищает собственные stale temp
|
||
в `media/decrypted`; общий file_io-реестр сохраняет активные temp всех instance.
|
||
|
||
`media_delivery_init()` вызывается после подготовки chat core и media_index, пока
|
||
общая БД и группы живы; подписка на роли обязательна. Репликация хранит отдельный
|
||
контекст и CM handle для каждой пары «CHAT-группа–пир». DOWN отменяет текущий RPC,
|
||
сохраняя handle для восстановления CM; TIMEOUT закрывает его перед повторным открытием.
|
||
Отдельный ETCP DOWN не означает потерю всех путей CM. До free контекста отменяются
|
||
его waiter, pump, watchdog и собственный handle; destroy отменяет также сверку ролей.
|
||
Обычный stop сохраняет каталог, его эпоху и RX-курсоры. Потеря локальной роли удаляет
|
||
чужие объявления вместе с RX-курсорами только этой группы одной транзакцией;
|
||
удаление канала удаляет также собственные объявления. Страница репликации и её
|
||
RX-курсор фиксируются одним самостоятельным commit; вложенная внешняя транзакция
|
||
не считается сохранённым прогрессом. Wire-контракт находится в `media_delivery_proto.h`.
|
||
|
||
Перед файловой подготовкой назначение записывается в `media_preparations`. Успешный
|
||
`media_index_commit()` регистрирует весь файл и снимает эту запись в одном savepoint;
|
||
после ошибки/отмены путь остаётся известен, даже если worker уже опубликовал копию.
|
||
Это гарантия записи БД; файловые изменения и внешний SQL COMMIT ей не охватываются.
|
||
При обычной ошибке откатывается только регистрация; SQLite может сама отменить всю
|
||
транзакцию при тяжёлой ошибке. Общая база берётся из `ui_state.media_base` и повторно
|
||
используется после stop/start. Загрузчик не меняет существующую базу.
|
||
|
||
Входящая загрузка и исходящая подготовка резервируют нормализованный путь в общем
|
||
media_delivery до начала записи. Отмена снимает загрузку с индекса, закрывает CM handles
|
||
и таймеры, освобождает резервацию; выполняющийся обработчик удерживает сам контекст.
|
||
Поэтому отмена и повторный start из progress callback допустимы, а done старой загрузки
|
||
вызывается после возврата из progress. Каждая попытка блока имеет отдельные UUID и temp:
|
||
поздние пакеты старого держателя/попытки не изменяют новый файл.
|
||
|
||
Producer и relay-выдачи независимо удерживают файл блока. После отмены producer
|
||
непроверенный источник завершается с ошибкой; проверенный может обслужить оставшихся
|
||
читателей. Последний владелец удаляет temp. При удалении канала отменяются и producer,
|
||
и все его выдачи; перед освобождением выдачи отменяются её waiter, pump и таймер.
|
||
DONE выдачи следует только после проверки блока и принятия всех его данных router.
|
||
|
||
После проверки всех блоков загрузчик собирает отдельный temp, проверяет операции
|
||
чтения/записи/close, полный размер и SHA-256, затем атомарно заменяет имя назначения.
|
||
Ошибка до публикации сохраняет прежний файл. Затем выполняется регистрация в SQLite:
|
||
при её ошибке опубликованный файл остаётся учтён в media_preparations для очистки.
|
||
Проверка и сборка блоков пока синхронны в uasync; контракт workers относится к исходящей подготовке.
|
||
|
||
Локальное удаление канала ждёт его исходящие video/register задачи. Последний
|
||
completion продолжает удаление; новые исходящие медиа канала в это время отклоняются,
|
||
подготовленные результаты ожидающих отправок не публикуются как новые сообщения.
|
||
После завершения задач отменяются загрузки/раздача и удаляются учтённые файлы, включая
|
||
назначения неуспешных подготовок. Общие файлы других каналов сохраняются. Очистка файлов
|
||
выполняется вне внешней транзакции: SQL-откат не восстанавливает удалённый файл.
|
||
При ошибке очистки записи оставшихся файлов сохраняются для повторного вызова;
|
||
удаление метаданных канала и неполная файловая очистка диагностируются отдельно.
|
||
Низкоуровневый `media_index_delete_channel()` сам workers не останавливает.
|
||
Точные ограничения путей и контракт повторной регистрации находятся в `media_index.h`.
|
||
|
||
Подготовка файлов в `src/video/video.h` выполняет remux подходящих H.264/AAC либо
|
||
перекодирование в MP4 H.264/YUV420P и AAC. Worker пишет уникальный соседний временный
|
||
файл, завершает декодеры/ресемплер/FIFO/кодеры, проверяет trailer, закрытие и метаданные.
|
||
Конечное имя заменяется только успешным completion в uasync. При отмене, включая
|
||
отмену уже подготовленного результата до доставки completion, временный файл удаляется,
|
||
старое назначение сохраняется. Совпадающие исходник/назначение и файловые алиасы
|
||
отклоняются; исходный файл должен оставаться неизменным до done. В процессе занята
|
||
одна задача до completion; атомарный захват исключает гонку между экземплярами ядра.
|
||
Перед done занятость освобождается, поэтому callback может запустить следующую задачу.
|
||
JPEG использует те же правила файлов, но извлекается синхронно в отдельном frontend worker.
|
||
Probe также синхронен и не обещает ограниченного времени чтения.
|
||
|
||
Live media хранится в RS-кольце с общим индексом PTS; каждый локальный handle владеет
|
||
отдельным cursor. Пауза player не останавливает сетевой ingest и не закрепляет старые
|
||
части против вытеснения. После FIN publisher может снять своё владение, сохранив
|
||
завершённый источник для оставшихся локальных readers. Последний close освобождает
|
||
источник, индекс и кольцо. Новая публикация отсоединяет readers прежней эпохи.
|
||
Android preview собственного эфира не создаёт reader: Camera2 пишет прямо в TextureView
|
||
и encoder Surface. UI attach/detach меняет лишь camera session; audio/encoder/origin сохраняются.
|
||
UI, ожидающие команды camera handler и каждая camera session удерживают preview target отдельно.
|
||
TextureView возвращает false при destroy и снимает ссылку UI; Surface/SurfaceTexture освобождаются
|
||
после закрытия последней session. CameraDevice.onClosed снимает также оставшиеся ссылки своих
|
||
sessions, если Android не прислал отдельные session.onClosed. Повторные callbacks безопасны.
|
||
Capture stop запрещает attach и снимает своё владение target; camera HandlerThread завершается
|
||
после camera close и освобождения outputs всех созданных sessions.
|
||
Отказ preview output возвращает encoder-only session и не завершает сам эфир.
|
||
При stop/destroy отменяются delivery и media-index callbacks до освобождения store;
|
||
destroy отсоединённого cursor не обращается к store. Frontend прекращает audio callback
|
||
и worker до освобождения своего DSP; borrowed handle/RS pointers между потоками не передаются.
|
||
|
||
Чтение video cursor запрашивает следующий кадр по готовности decoder, независимо от
|
||
PTS показа; audio cursor сохраняет временной lookahead. Viewers готовят ограниченный батч
|
||
на запрос: отдельно задаются число видеокадров, audio lookahead и число всех записей.
|
||
Desktop и Android принимают до 16 записей и обрабатывают треки по их готовности.
|
||
VIDEO_END/AUDIO_END следуют за последними пакетами своего трека, не ожидая другого трека;
|
||
общий END завершает вход, а окно ждёт опустошения обоих выходов. GAP/CLOSED отменяют старый батч.
|
||
Получение батча погашает запрос, включая повторные обращения до pull; скрытого чтения следующего батча нет.
|
||
Callback готовности только будит desktop worker и не вызывает API ядра/bridge. Stop снимает callback
|
||
под mutex viewer до освобождения его контекста; detach будит ожидающие workers до уничтожения manager.
|
||
На Android signal принадлежит audio worker и живёт до stream_stop, join video worker и close mixer input;
|
||
ядро только уведомляет его. Surface освобождается после join video worker.
|
||
В wait не удерживаются mutex bridge/ядра. Расход PCM будит тот же signal, уведомления сливаются без потери пробуждения.
|
||
GUI и audio callback будят worker при движении часов/освобождении места; сна между готовыми пакетами нет.
|
||
Каждый reader имеет отдельные audio/control и video workers. Audio worker читает батчи и готовит
|
||
до 250ms PCM через Opus/SoundTouch, не удерживая блокировку consumer. Запас пополняется при наличии
|
||
данных без обязательного prefill. SPSC передаёт PCM вместе с отображением исходных PTS; mixer/audio
|
||
callback только копирует и смешивает. Короткая блокировка защищает output map/clock и lifetime,
|
||
в ней нет декодирования, time-stretch или MediaCodec/FFmpeg. EOF продолжает prepare до опустошения DSP.
|
||
Android video worker получает ограниченный mailbox (два кадра) и держит один рабочий input и один готовый output MediaCodec; свободный
|
||
input buffer получает лишь для уже готовых данных и сразу возвращает codec.
|
||
Первый decoded frame разрешает движение clock/audio. Будущий output ждёт PTS и
|
||
приостанавливает новые video requests, сохраняя независимую обработку audio/control.
|
||
Если первый доступный звук позже KEY, cursor отсчитывает стартовый audio lookahead от
|
||
первого аудиопакета, сохраняя это основание неизменным. Opus/SoundTouch получает PCM до
|
||
запуска audio clock; неподвижные часы не разрешают читать дальше этого ограниченного окна.
|
||
PTS поданных кадров служат диагностике/проверке запаздывания, а не лимиту заполнения codec.
|
||
Смена revision заменяет только video decoder и его диагностические измерения;
|
||
GAP/EXPIRED начинают новую точку KEY. Нехватка сети сохраняет decoder references.
|
||
Reset увеличивает video generation; результаты прежнего поколения не публикуются. Video worker
|
||
после EOF остаётся доступен для reset до явного stop. Audio clock после старта не ждёт видео:
|
||
просроченные изображения декодируются для сохранения references, но не показываются.
|
||
Desktop FFmpeg держит один рабочий AVPacket и два готовых изображения; EAGAIN оставляет
|
||
packet до следующего прохода. FIN подаёт EOS и дренирует выход до decoder EOF;
|
||
конец GUI требует также показа оставшихся изображений и завершения audio tail.
|
||
Отсутствие прогресса decoder при наличии работы имеет отдельную диагностику и срок 10s.
|
||
|
||
ACK-история принадлежит ядру и обновляется только при первом найденном подтверждении
|
||
пакета. REINIT/close соединения не стирает уже подтверждённые за последнюю секунду байты.
|
||
Общий `stream_publisher` Android/desktop владеет отдельным 100ms таймером TX-снимков;
|
||
close/detach отменяют его в uasync до уничтожения instance. Bridges копируют снимок
|
||
под mutex, frontend не обращается к живым очередям транспорта. Android экран подписан
|
||
на отдельный StateFlow статистики; desktop обновляет статистику отдельно от сырого превью.
|
||
Publisher не регистрирует stream event callback и не подменяет callback viewer.
|
||
Bridge удерживает внешний mutex на время producer/open/stop и destroy общего publisher;
|
||
actor не вызывает frontend callbacks под своим mutex. Reserved post, media timer,
|
||
stats timer и stream handle освобождаются до контекста publisher.
|
||
|
||
Desktop `MainWindow` владеет `StreamPublishEngine`, а окно управления лишь отображает
|
||
его состояние. Закрытие окна сохраняет эфир, новый запуск запрещён до конца FIN drain.
|
||
Qt capture callback сохраняет последний `QVideoFrame` для preview и ограниченно ставит кадр
|
||
в очередь отдельного долгоживущего image worker. Он конвертирует `QVideoFrame` в `QImage`;
|
||
video worker получает готовый image и не создаёт Qt Multimedia TLS, которое могло задержать его exit на Windows.
|
||
Video/audio workers выполняют encode/DSP вне GUI и hardware callbacks. Отдельные поколения источника и encoder позволяют менять
|
||
видеокодек/bitrate/rotation без повторного выбора экрана в портале. Ошибки Qt capture
|
||
обрабатываются через queued callback, чтобы не уничтожать backend из собственного callback.
|
||
Stop запрещает producers, закрывает Qt capture и подписку на аудиозахват. Отдельный join worker
|
||
дожидается полного выхода video/audio threads, включая native TLS Qt; GUI продолжает обрабатывать события.
|
||
После join отправляется FIN с drain 2s; закрытие последнего микрофона может продолжаться параллельно.
|
||
Microphone lease и owner сохраняются до завершения обоих путей. Новый start ждёт также FIN drain.
|
||
При завершении приложения engine уничтожается до SoundManager и ядра; detach в uasync
|
||
закрывает оставшийся publisher. Destructor ждёт workers в локальном event loop без пользовательского ввода,
|
||
чтобы thread cleanup мог обратиться к GUI; start и внешние сигналы engine в это время запрещены.
|
||
Workers никогда не держат borrowed instance/handle.
|
||
Microphone lease публикации исключает call/recorder; рация согласует вход через `AudioCapture`.
|
||
Одинаковый выбранный вход имеет одну аппаратную сессию и профиль трансляции. Разные входы независимы.
|
||
Подключение/отключение рации не останавливает устройство трансляции: атомарный слот и счётчик readers
|
||
дожидаются только callback удаляемого потребителя. Последняя подписка закрывает устройство.
|
||
Если первой работала рация, публикация заменяет профиль захвата своим; после остановки публикации
|
||
оставшаяся рация продолжает тот же захват. Recovery останавливает callbacks, workers TX и один раз
|
||
открывает общий вход; RX рации сохраняется. Каждый потребитель обрабатывает собственную копию PCM.
|
||
На Android `MediaAudioController` владеет общим выбором устройства, AudioManager и одним аппаратным reader.
|
||
Маршрутизация использует `CallAudioRouter`; AudioTrack рации и микшера просмотра регистрируются у одного владельца.
|
||
`MicrophoneCapture` раздаёт независимые ограниченные очереди постоянного PCM 48k stereo; моно апмиксуется.
|
||
После подключения публикации встроенный микрофон использует CAMCORDER, сохраняемый до последней подписки.
|
||
Смена профиля заменяет только аппаратный AudioRecord: камера, видеокодер и source_epoch продолжают работать.
|
||
Общая сетка по capture timestamps имеет запас 200ms; пропуски заполняются тишиной, старый PCM не переносится вперёд.
|
||
Stop публикации удаляет её подписку до join Opus worker и FIN, сохраняя захват для оставшейся рации.
|
||
PTT и PCM рации проходят через одну очередь. Последняя подписка завершает общий reader;
|
||
контроллер закрывает аппаратный вход, сохраняя выходы. Перед Telecom все владельцы снимаются,
|
||
а `MediaAudioController.awaitIdle()` дожидается освобождения входа и глобального маршрута.
|
||
Ограничения фонового захвата рации, её разрешения и foreground-сервис продолжают действовать.
|
||
Fatal ошибка audio worker передаётся в GUI с session ID и завершает всю публикацию;
|
||
откат видеоконфигурации применяется только к video/capture. Поздний отказ старого publisher
|
||
не завершает новую сессию. Причина ошибки сохраняется в окне после окончания drain.
|
||
|
||
Desktop-звонок и рация проверяют поступление корректного PCM, включая первый callback.
|
||
Секунда без данных отключает TX и фиксирует понятную ошибку в GUI; цифровая тишина является данными.
|
||
RX продолжает работать, рация снимает PTT, автоматического переоткрытия неисправного входа нет.
|
||
Выбор другого микрофона или новая сессия снимают ошибку. Неисправное устройство закрывается
|
||
в отдельном потоке; stop не присоединяет уже запущенный close worker. Новый захват ждёт его завершения.
|
||
В звонке microphone lease удерживается до конца закрытия даже после stop. Общий AudioCapture
|
||
рации сначала отсоединяет callback потребителя; последний владелец освобождает backend в worker.
|
||
Сброс контекста и уничтожение владельца дожидаются worker до освобождения miniaudio-контекста.
|
||
Штатное закрытие исправного устройства сохраняет синхронный порядок.
|
||
|
||
`AudioDevice` подстраивает буфер по подтверждённым backend XRUN: WASAPI DATA_DISCONTINUITY
|
||
на входе, ALSA EPIPE, PulseAudio capture hole/overflow и playback underflow. Notification
|
||
только увеличивает атомарный счётчик, решение и лог выполняются в GUI recovery timer.
|
||
Исходные параметры, включая Linux capture period=10мс, сохраняются до повторных сбоев.
|
||
Первая секунда после start исключена; нужны ошибки в двух опросах за 5с. Период увеличивается
|
||
на 5мс, не чаще раза в 5с, до общей запрошенной ёмкости 100мс с учётом числа периодов.
|
||
Фактический размер согласует backend; исходный или уже достигнутый больший размер не уменьшается.
|
||
При достижении предела повторные сбои логируются без новых перезапусков. Подобранный запрос
|
||
сохраняется при recovery, сбрасывается при смене устройства/сессии; стабильный звук не вызывает reopen.
|
||
Подстройка микрофона закрывает устройство асинхронно, останавливая TX worker и сохраняя RX.
|
||
Общая capture-сессия отсоединяет callbacks до close и заново подготавливает потребителей после init.
|
||
Новая подписка во время close откладывается, stop удаляет её без ожидания backend.
|
||
Первое переоткрытие после подстройки не использует backoff; при ошибке открытия действует обычный retry.
|
||
|
||
В desktop-видеозвонке Windows/Linux камера живёт в отдельном процессе того же
|
||
`vibechat --call-camera-worker`. Служебный вход выполняется до конфига, БД и
|
||
single-instance lock. Перечисление устройств, Qt/V4L2, преобразование и освобождение
|
||
аппаратных кадров происходят только там. GUI получает собственный `QImage`; TX не
|
||
обращается к драйверу. Смена codec не перезапускает захват и не ждёт следующего GOP.
|
||
|
||
Пиксели одного кадра передаются через mapping (Windows — pagefile-backed объект ОС,
|
||
Linux — временный файл), метаданные/команды — через локальный сокет.
|
||
Worker записывает mapping до FRAME; следующий write разрешён
|
||
только после ACK, который GUI посылает после копирования в свою память. Общего mutex
|
||
нет. В worker ожидает только последний raw-кадр; поколения отсекают старые результаты.
|
||
Preview обновляется по готовности, TX получает condition-variable wake. PTS сохраняет
|
||
время захвата; кадры старше 250мс отбрасываются до encode и перед send. Отброшенный
|
||
encoded frame требует следующего KEY, поскольку encoder уже продвинул reference chain.
|
||
|
||
Отключение камеры и удаление окна не ждут worker. Независимый Session сохраняет
|
||
QProcess и mapping до finished, затем удаляет себя после отсоединения владельца.
|
||
Зависший stop получает kill через 1.5с, отсутствие прогресса — через 5с; таймеры
|
||
живут в родительском процессе. При выходе приложения kill отправляется без wait;
|
||
GUI не уничтожает живой QProcess. При отказе новый процесс запускается лишь по
|
||
явной команде пользователя и после finished прежнего. Лог категории call показывает
|
||
последнюю стадию enumerate/start/convert/release/stop и возраст кадра.
|
||
`test_call_camera_capture` проверяет настоящую IPC-цепочку с подставным устройством:
|
||
блокировки start/map/stop, поздние кадры после смены поколения, задержку 720p,
|
||
повтор после отказа, отсутствие рестарта камеры при смене codec, удаление окна,
|
||
выход приложения и независимое освобождение mapping в каждом процессе.
|
||
|
||
Статус транспорта UP подтверждает восстановление физического пути. READY сессии
|
||
группы означает согласованный JOIN/ACCEPT и завершённый обмен её таблицей; именно
|
||
его ждёт recovery. REINIT меняет эпохи обмена, сохраняя рабочие маршруты. Эпохи
|
||
защищают от сообщений старой сессии и не задают срок жизни маршрута.
|
||
|
||
Проверки: `test_services` проверяет независимый stop/start, общий NCD в CONNECTING,
|
||
сохранение идентичности/сокетов/БД, повторную загрузку каналов, отмену workers,
|
||
заблокированного медиа-потока, транзита и ожиданий заменённой очереди. `test_chat_join_e2e`
|
||
проверяет join и распространение membership между узлами без UTUN-сервиса.
|
||
|
||
В Android звонковой сессией владеет `CallController` уровня приложения; события
|
||
ядра и команды Telecom обрабатываются один раз в main thread, без повторного
|
||
проигрывания событий при создании ViewModel. UI получает `StateFlow` и передаёт
|
||
пользовательские команды. `CallService` владеет `CallAudioEngine` и частичным
|
||
wake lock: перед исходящим вызовом или принятием входящего запускается foreground
|
||
режим `phoneCall|microphone`, затем сигналинг и аудио. Завершение сначала
|
||
останавливает I/O и дожидается аудиопотоков, затем освобождает native-аудио,
|
||
wake lock и сервис. Перед `NativeLib.stop/restart/destroy` выполняется тот же
|
||
порядок; новый звонок разрешается после `SERVICE_STARTED` нового ядра.
|
||
|
||
Активность ядра задаётся явно: видимая Activity или незавершённый звонок удерживают
|
||
ACTIVE и выключают Android standby. Состояние не истекает по таймеру. После
|
||
завершения звонка в фоне возвращается standby; экран может гаснуть во время
|
||
разговора независимо от частичного wake lock сервиса.
|
||
|
||
Если работают рация, публикация или просмотр трансляции, исходящий Android-звонок
|
||
требует подтверждения их остановки; входящий показывает предупреждение до принятия.
|
||
После подтверждения/принятия `CallController` выключает участие в рации, завершает
|
||
публикацию и просмотр, асинхронно ждёт освобождения их аудио и только затем запускает
|
||
сигналинг и звонковое аудио. Foreground-сервис резервируется сразу из видимой Activity.
|
||
Отмена во время освобождения не запускает устаревший звонок.
|
||
До окончания звонка повторное включение рации/трансляции заблокировано.
|
||
После закрытия звонкового аудио и Telecom Connection ранее включённая рация
|
||
возвращается в прежний канал; удержания PTT сброшены. Удалённый канал и остановленное
|
||
ядро не восстанавливаются. Публикация и просмотр трансляции автоматически не возобновляются.
|
||
|
||
`utun_set_client_activity()` в сборке с `UTUN_HAVE_STANDBY` применяет фактический
|
||
режим duty-cycle до рассылки событий активности; JNI не переключает его отдельно.
|
||
Повторная команда не перезапускает фазу и анонсы, но первый фоновый запуск включает
|
||
standby даже при начальном `client_activity=STANDBY`. Keepalive подписан на режим
|
||
и фазы самого standby; новые UDP/TCP-линки сразу анонсируют текущую фазу после
|
||
handshake. Пробуждение по таймеру и внешней сети сначала сообщает ACTIVE-окно
|
||
пирам, затем запускает ожидающие задачи. При выходе сначала восстанавливаются
|
||
линки; deinit отменяет ожидающие задачи и очищает подписки старого ядра.
|