From b25070acbd554956df966223004e26dd05063a34 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 12:54:44 +0200 Subject: [PATCH] Document shared live publisher and desktop capture lifecycle --- doc/service_lifecycle.md | 24 ++++++++++++++--- doc/stream.txt | 57 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index 261791a5..5b2125eb 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -106,9 +106,27 @@ destroy отсоединённого cursor не обращается к store. ACK-история принадлежит ядру и обновляется только при первом найденном подтверждении пакета. REINIT/close соединения не стирает уже подтверждённые за последнюю секунду байты. -Android publisher владеет отдельным 100ms таймером TX-снимков; close/detach отменяют -его в uasync до уничтожения instance. JNI копирует снимок под mutex без доступа к -живым очередям транспорта из Kotlin. Экран подписан на отдельный StateFlow статистики. +Общий `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 хранит только последний кадр, video/audio workers выполняют encode/DSP +вне GUI и hardware callbacks. Отдельные поколения источника и encoder позволяют менять +видеокодек/bitrate/rotation без повторного выбора экрана в портале. Ошибки Qt capture +обрабатываются через queued callback, чтобы не уничтожать backend из собственного callback. +Сначала stop запрещает producers, закрывает Qt capture и audio device, затем join workers, +освобождает microphone lease и завершает публикацию через FIN с drain 2s. +При завершении приложения engine уничтожается до SoundManager и ядра; detach в uasync +закрывает оставшийся publisher. Workers никогда не держат borrowed instance/handle. +Microphone lease публикации эксклюзивен; прежние call/radio/recorder leases совместные. +Восстановление устройства сохраняет владение и не разрешает другой capture до stop/failure. Статус транспорта UP подтверждает восстановление физического пути. READY сессии группы означает согласованный JOIN/ACCEPT и завершённый обмен её таблицей; именно diff --git a/doc/stream.txt b/doc/stream.txt index b542f6ea..17d75304 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -803,7 +803,9 @@ Android: трансляции; оба канала используют общий gain, сохраняющий стереобаланс. Изменения настроек рации применяются на следующем PCM-кадре, без перезапуска микрофона, Opus или source_epoch; compressor не добавляет задержку и не меняет capture PTS. - Camera/codec callbacks — HandlerThread, Opus encoder — audio capture thread, decoder/AudioTrack — viewer worker. -- Native bridge держит pthread mutex, ограниченные очереди и один pending post либо timer. +- Native bridge использует общий stream_publisher для TX и собственный actor для viewer. + stream_publisher держит pthread mutex, ограниченные очереди и один pending post либо media timer; + статистика обслуживается отдельным таймером 100ms. Тот же модуль используется desktop. attach/detach выполняются в uasync вне callbacks. detach отменяет post/timer, закрывает handles, очищает media, снимает наблюдение каталога и запрещает producers до уничтожения instance/ua. UI не сохраняет borrowed inst/ua. Idle без pending data не будит мобильное ядро. @@ -880,8 +882,51 @@ C-тесты проверяют новую revision, потерю первого Это наблюдение относится к данной прошивке; metadata Camera2 сами по себе не подтверждают рабочую скорость. Desktop: -- Значок трансляций расположен в шапке MessageList. Источники берутся из того же каталога ядра; +- Значок трансляций расположен в шапке MessageList группового канала даже без источников; + меню содержит запуск/управление собственной публикацией и выбор удалённого источника. + Собственный источник открывает управление; микрофон не воспроизводится локально. + Источники берутся из того же каталога ядра; ответы предыдущего канала не меняют текущую шапку. Личные беседы снимают наблюдение. +- MainWindow владеет одним StreamPublishEngine. Окно StreamPublishWindow можно закрыть и открыть + снова без завершения эфира. Qt >=6.6 Multimedia/MultimediaWidgets и FFmpeg нужны для публикации; + без них сохраняются остальные функции чата. CMake сохраняет признак capture в отдельном marker, + deployment/Windows installer включают Multimedia runtime, FFmpeg plugin и транзитивные DLL. +- Видео: QCamera, QScreenCapture или QWindowCapture передают последний QVideoFrame в QVideoSink. + Под Wayland/XWayland экран выбирается через системный портал. Открытие портала не ограничивается + таймаутом подтверждения KEY до появления первого сырого кадра. + Callback только заменяет последний кадр под mutex, Qt-очередь кадров не растёт. + Сырое превью обновляется отдельно до 30Hz; encode/conversion и сетевой publisher его не задерживают. + Для камеры сохраняется относительная шкала backend PTS с привязкой к монотонному origin; + при отсутствии PTS используется время захвата callback. Это оценка, а не аппаратный timestamp сенсора. + Неподвижный экран кодируется повторно с новым монотонным PTS; последний raw кадр допустим и после + смены видеоконфигурации на backend, который уведомляет только об изменении изображения. +- Отдельный worker преобразует кадры через swscale и кодирует без B-кадров, GOP=1s. + Предлагаются H.264/H.265/VP9/AV1/H.266 в этом порядке, если реальная проба encoder успешна. + NVENC/QSV/AMF/VAAPI проверяются перед программными реализациями. Проба требует два принудительных + независимых KEY, затем encoder открывается заново, без reference frames и PTS пробной сессии. + Encoder, не выполняющий повторный запрос KEY, для live не используется. VAAPI/QSV async_depth=1. + H.264/H.265/H.266 нормализуются в Annex B; каждый KEY содержит decoder configuration. +- Настройки: разрешение/FPS, видеокодек, Авто/пресеты bitrate, три кнопки 0/90/270 с красной верхней + гранью и подсветкой активной кнопки. libx264 меняет bitrate в текущем encoder; остальные реализации + заменяют только video encoder. Смена codec/режима/rotation подтверждается новой revision с KEY. + Capture и encoder имеют отдельные поколения: смена codec/bitrate/rotation не переоткрывает источник + и не вызывает повторный выбор в портале. Origin, source_epoch, аудио и frame IDs сохраняются. + Ошибка или отсутствие KEY в течение 8s вызывает одну попытку rollback всей последней подтверждённой + конфигурации; повторная ошибка завершает эфир. Late callbacks отсеиваются по поколению источника. +- Микрофон выбирается до запуска. miniaudio захватывает 48kHz stereo; при mono вход дублируется + с явным указанием Mono в UI. Отдельный worker использует Opus AUDIO/128kbps/20ms и собственный + audio_compressor с текущими настройками рации (200ms lookback, нулевой lookahead, общий stereo gain). + Callback копирует PCM в предвыделенную очередь; DSP/Opus в нём нет. Hardware PTS используется при + наличии, иначе шкала строится по количеству отсчётов. Очередь/возраст ограничены; при разрыве + сбрасываются только неполный PCM-пакет и история компрессора. Mute передаёт тишину. + Публикация эксклюзивно занимает микрофон: звонок/рация/запись и публикация запрещают друг другу новый + capture. Прежнее совместное владение микрофоном голосовыми функциями сохраняется. +- transport/stream_bridge владеет независимым stream_publisher; shared C actor выполняет uAV2/RS, + admission, KEY recovery, FIN и TX snapshot в uasync. Очереди/pressure/drain совпадают с Android. + UI показывает video queue, суммарный inflight, send buffer и ACK_1s каждые 100ms. + Stop закрывает источники и audio callback, join обоих workers, затем FIN и ограниченный drain 2s. + Удаление engine происходит до SoundManager и ядра; detach отменяет posts/timers и закрывает publisher + в uasync. Протокол uAV2 и wire v5 при добавлении desktop publisher не меняются. - Один StreamPlayerWindow на процесс. Он содержит имя источника, состояние подключения, громкость, полноэкранный режим и закрытие. Выбор нового источника закрывает предыдущего reader. - transport/stream_bridge владеет одним viewer, готовит до двух media records и ограничивает RX 4MiB/16 записей. @@ -908,9 +953,15 @@ Opus/громкость на настоящем null audio backend, непрер late/pressure drops, число и байты кадров, PTS границ очереди относительно часов и audio callback timing. test_stream_service также проверяет общий каталог двух независимых источников одного канала, шестисекундную паузу readers при живом сетевом приёме без restart и полное локальное/удалённое дочитывание FIN. +test_stream_publish проверяет реальные encoder/decoder H.264/H.265/VP9/AV1, обновление libx264 bitrate +с прежним decoder, общую публикацию/RS, смену revision, pressure/KEY recovery, FIN и concurrent detach +с балансом tracked allocations. Дополнительный Xvfb/private D-Bus сценарий проверяет реальный экран/окно, +стереомикрофон с двумя разными тонами, сырое превью, закрытие окна управления, неподвижный источник, +смену codec/bitrate/rotation и rollback без смены audio/source_epoch. Статистика отправки на экране публикации: -- Отдельный uasync timer publisher каждые 100ms делает согласованный TX snapshot и передаёт его через JNI. +- Отдельный uasync timer общего stream_publisher каждые 100ms делает согласованный TX snapshot. + Android получает его через JNI, desktop — через mutex bridge; UI не читает живые очереди транспорта. Kotlin обновляет отдельный StateFlow, без пересоздания preview. После close/detach timer отменяется. - video_queue_bytes — оставшиеся байты с вычитанием offset частично упакованного головного кадра. audio_queue_bytes — целые ожидающие Opus-пакеты. stream_pending_bytes — RS-части всех выдач текущей эпохи,