Document multiview ownership, independent clocks and validation

This commit is contained in:
evgeny committed 2026-10-20 17:14:08 +02:00
1 parent b3ebc09b9b
commit 03f7d4341d
2 files changed
+63 -18

No files matched your search

+50 -18
View File
@@ -794,7 +794,7 @@ Desktop и Android открывают просмотр через stream_media_o
Ретрансляторы не разбирают uAV2 и не пересоздают подписи источника.
Android:
- Один foreground publisher на процесс, один viewer. Каждая операция имеет новый session ID.
- Один foreground publisher на процесс; каждый viewer имеет независимый session ID.
- StreamService владеет камерой, подпиской на микрофон и PARTIAL_WAKE_LOCK; пересоздание Activity не завершает источник.
Сервис camera|microphone запускается из видимого приложения после runtime-разрешений; без микрофона — camera.
- Camera2 TEMPLATE_RECORD пишет в Surface encoder. Кодеки перечисляются в порядке H.264/H.265/VP9/AV1/H.266;
@@ -816,8 +816,9 @@ Android:
lookback=200ms, lookahead=0, release=rise*5, target=0.25. История и gain принадлежат только audio worker
трансляции; оба канала используют общий gain, сохраняющий стереобаланс. Изменения настроек рации применяются на следующем PCM-кадре, без перезапуска микрофона,
Opus или source_epoch; compressor не добавляет задержку и не меняет capture PTS.
- Camera/codec callbacks — HandlerThread, Opus encoder — audio capture thread, decoder/AudioTrack — viewer worker.
- Native bridge использует общий stream_publisher для TX и собственный actor для viewer.
- Camera/codec callbacks — HandlerThread, Opus encoder — audio capture thread, decoder — отдельный viewer worker,
общий AudioTrack — worker StreamAudioMixer. Перекладка плиток сохраняет TextureView/decoder.
- Native bridge использует общий stream_publisher для TX и stream_viewers для RX.
stream_publisher держит pthread mutex, ограниченные очереди и один pending post либо media timer;
статистика обслуживается отдельным таймером 100ms. Тот же модуль используется desktop.
attach/detach выполняются в uasync вне callbacks. detach отменяет post/timer, закрывает handles,
@@ -827,7 +828,8 @@ Android:
неполную пачку объединяет с аудио/видео до 20ms; продолжение работы через timer 5ms.
При video pressure отбрасываются целые ожидающие кадры, P-кадры запрещены до нового KEY.
Кодировщик получает отказ admission и запрашивает KEY с ограничением частоты 500ms.
- RX<=4MiB/256 records, bridge готовит один ответ на незавершённый запрос worker.
- RX: stream_viewers держит отдельный handle/cursor, demand, status и один owned ответ на session ID.
Общей RX FIFO нет: остановка/отставание одного reader не задерживает остальных.
Получение ответа и сетевые AVAILABLE не запускают автоматическое чтение следующего кадра.
Запас остаётся в RS-кольце. want_video запрашивает следующий полный видеокадр независимо от часов показа;
audio_ahead ограничивает только звук относительно PTS потребителя. Управление/звук обслуживаются независимо от video pressure.
@@ -842,13 +844,15 @@ Android:
- Фиксированного prebuffer нет. Начало clock/audio output разрешено после первого декодированного кадра.
AudioTrack задаёт время показа видео по реально услышанному PCM;
без аудио используются монотонные часы с остановкой на границе готовых кадров.
Android StreamAudioOutput принадлежит viewer worker и хранит один недописанный PCM (до 480 stereo frames).
Android StreamAudioOutput принадлежит StreamAudioMixer и хранит один недописанный общий PCM (до 480 stereo frames).
Одна WRITE_NON_BLOCKING за проход: частичный/нулевой результат сохраняет остаток, не задерживая video pump.
Выдано native = принято устройством + остаток; часы используют playbackHeadPosition без экстраполяции timestamp.
Шкала каждого StreamMixedPlayback начинается на своём output frame; playbackHeadPosition переводится
в число реально услышанных кадров этого input. PTS разных источников не сравниваются.
Заполнение поддерживается до фактического bufferSizeInFrames устройства; API>=31 задаёт start threshold=1.
При EOF недописанный PCM передаётся полностью; короткий хвост дополняется не более одного start threshold тишины.
Эта тишина не входит в native PTS; завершение ждёт воспроизведения всего принятого PCM, не окончания тишины.
Пауза сохраняет остаток; GAP освобождает output вместе с native timeline. Новый output начинает счётчики с нуля.
При EOF недописанный PCM передаётся полностью; mixer выдаёт тишину после последнего кадра источника
до его физического воспроизведения. END одного input не закрывает AudioTrack остальных.
Hold/starvation одного источника добавляет silence span только в его native timeline, не двигая его PTS.
GAP создаёт новый input/origin, сохраняя общее устройство и часы остальных трансляций.
Watchdog 2s проверяет отсутствие движения hardware head только при достаточном заполнении либо заблокированной записи;
намеренная пауза и ожидание данных до порога запуска не считаются зависанием. Ошибка содержит состояние и счётчики устройства.
Защитный guard минутного контроллера учитывает audio device lead и наблюдённое опережение decoder
@@ -866,7 +870,8 @@ Android:
GraphicsLayer преобразует только видео, не Surface/decoder/reader и не управление; жест продлевает таймер скрытия управления.
Во время подключения/ошибки управление видно. Смена видимости не меняет геометрию TextureView и не пересоздаёт reader.
Выход восстанавливает прежнюю requestedOrientation и видимость системных панелей; ON_STOP освобождает viewer.
Собственный источник не открывает viewer/cursor, decoder, AudioTrack и playback controller.
При одиночном выборе собственного источника используется прямой preview без reader/decoder.
В режиме всех трансляций собственный источник, если активен, также имеет обычную плитку.
Preview ориентируется по сенсору камеры и текущему положению экрана; ручной поворот трансляции его не вращает.
TextureView уже применяет ориентацию сенсора. UI Matrix компенсирует только -displayDegrees,
размеры view учитывают relative rotation; повторно поворачивать на SENSOR_ORIENTATION нельзя.
@@ -889,15 +894,18 @@ Android:
Камера при закрытии preview продолжает работать; publisher от UI не зависит.
Видимый player удерживает экран включённым. Preview ограничен оставшимся местом, кнопки управления не перекрывает.
- Source list общий для Android/desktop: stream_sources_json в uasync использует stream_member_load
и stream_member_online. stream_watch_sources наблюдает один выбранный канал; group=0 снимает наблюдение.
и stream_member_online. stream_watch_sources(inst, group, owner) наблюдает канал от имени владельца;
group=0 снимает только его наблюдение. Owner=0 — выбранный чат; desktop-сетка имеет собственного owner.
Начальный snapshot и изменения после Merkle/BGP передаются через CHAT_EVT_STREAM_SOURCES=58:
group_id:8BE, valid:1, JSON с id(decimal string), epoch(hex string), self, name.
События объединяются отложенным callback, одинаковый список повторно не отправляется. Таймера опроса нет.
valid=0 сохраняет предыдущий список UI и показывает недоступность; это не пустой успешный снимок.
Обнаружение не создаёт подписку, media-буфер и не удерживает ядро в ACTIVE.
- В шапке канала значок виден при наличии источников; один источник открывается сразу,
несколько выбираются из списка по имени/ID. Пункт меню «Трансляции» также открывает управление публикацией.
- В шапке канала значок доступен и при пустом списке; меню содержит «Смотреть все активные трансляции»
и выбор отдельного источника по имени/ID. Пункт «Трансляции» также открывает управление публикацией.
Выбранная эпоха проверяется actor до подписки и по фактически открытому handle. Перезапуск источника
требует нового выбора. Ошибка viewer kind=6 передаёт stream_close_reason в flags.
требует нового выбора в одиночном режиме; сетка заменяет только плитку с новой эпохой.
Ошибка viewer kind=6 передаёт stream_close_reason в flags.
- Ручная ориентация доступна до запуска и во время своей трансляции: три компактные кнопки 0/90/270 градусов относительно
обычного положения устройства. Поворот не зависит от текущего положения экрана; SENSOR_ORIENTATION и направление
камеры учитываются при расчёте video rotation. Камера/режим/ориентация хранятся и подтверждаются одной конфигурацией.
@@ -1016,9 +1024,9 @@ Desktop:
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, готовит один ответ на запрос и ограничивает RX 4MiB/16 записей.
- Одиночный StreamPlayerWindow либо StreamGridWindow с отдельным player на источник.
Player содержит имя, состояние и громкость; fullscreen/close в сетке принадлежат общему окну.
- transport/stream_bridge владеет stream_viewers: независимый cursor и один ближайший ответ на reader.
Attach/detach выполняются в uasync; команды и копирование медиаданных защищены mutex.
Post отменяется до destroy ядра; GUI/decoder не хранят заимствованные instance/handle pointers.
- stream_media_cursor собирает ближайший access unit выбранного кодека и выдаёт Opus 48kHz mono/stereo/20ms из RS-кольца.
@@ -1027,7 +1035,7 @@ Desktop:
Worker держит один рабочий AVPacket и не более двух готовых изображений. Следующий video request не ограничен часами показа.
FFmpeg output обслуживается перед input; EAGAIN сохраняет рабочий packet и означает backpressure, не GAP.
Дальние кадры и звук не копируются из кольца. SoundTouch и устройство держат только рабочую порцию PCM.
Кадры сохраняются до достижения их PTS аудиочасами; при задержке GUI общий clock останавливается.
Кадры сохраняются до достижения их PTS аудиочасами; задержка показа удерживает clock только этого player.
Видео декодируется без задержки frame-threading. При GAP сбрасываются decoder refs и timeline до KEY.
- Первый decoded frame разрешает движение clock/audio, защитный guard учитывает наблюдённый decoder lead.
FIN отправляет decoder EOS; выход дренируется теми же ограниченными проходами до EOF, GUI продолжает показ хвоста.
@@ -1040,6 +1048,30 @@ Desktop:
Закрытие окна снимает только reader, собственная публикация сохраняется.
Удаление канала сообщает STREAM_CLOSE_GROUP; остановка ядра снимает media и catalogue owners.
- Без FFmpeg окно сообщает, что декодирование недоступно. Нужна desktop-сборка с HAVE_FFMPEG.
Просмотр всех активных трансляций (desktop и Android):
- Добавление/удаление определяется каталогом текущего канала. Порядок существующих плиток стабилен;
добавление, удаление, смена эпохи и ошибки одного player не пересоздают остальные.
Пропавший источник дочитывает FIN/готовый хвост: дедлайн равен остатку media + 2s, не более 30s.
После удаления последней плитки окно остаётся открытым и ждёт новые источники.
- Desktop: квадратная сетка ceil(sqrt(count)), в том числе 2x2, 3x3, 4x4; при >16 расширяется дальше.
Android выбирает строки/колонки по максимальной площади вписанного видео, без принуждения к квадрату.
Плоский keyed Layout сохраняет Surface при перекладке. ON_STOP/закрытие освобождает readers;
Surface/SurfaceTexture освобождаются только после выхода decoder worker.
Перед Android-звонком новые viewers заблокированы; подготовка ждёт workers и закрытия общего AudioTrack вне UI-потока.
- PCM всех источников смешивается в один stereo 48kHz output: SoundManager на desktop,
StreamAudioMixer/AudioTrack на Android. Opus, SoundTouch, темп, PTS и A/V clock у каждого свои.
Общий limiter имеет быстрый attack и release 100ms; тихая новая плитка не уменьшает громкость остальных.
Дополнительного накопительного буфера перед микшером нет; размеры RS-колец не менялись.
- Ошибка отдельного decoder повторяется с backoff 0.5..10s; смена эпохи заменяет только этот reader.
Закрытие канала/сервиса и удаление участника не вызывают бесконечного retry.
Android пробует подходящие декодеры по очереди, включая software при исчерпании аппаратных;
непригодный codec/format остаётся с явной ошибкой в своей плитке. Реальная вместимость зависит от устройства.
- Проверки: test_stream_view — независимые readers/resync/stop, владельцы каталога, геометрия до 16 плиток,
сохранение player при перекладке, новая эпоха, пустое ожидание, два одновременно играющих FFmpeg/Opus player,
сумма/limiter общего выхода и независимое отключение input. test_android_stream — независимые native readers.
Kotlin StreamAudioMixTest/StreamGridLayoutTest и StreamAudioOutputTest проверяют отдельные origin,
частичные записи, сумму PCM и limiter, портретную/альбомную раскладку и вместимость до 40 источников.
Проверки: test_stream_view — настоящее ядро, старые эпохи/cancel, задержанное чтение без роста RX, H.264 с поворотом и сменой revision без новой эпохи,
Opus/громкость на настоящем null audio backend, непрерывные 30fps при физической audio latency 200ms,
закрытие канала и полный баланс tracked allocations. Раз в секунду desktop viewer логирует decode/show FPS,
+13
View File
@@ -4,6 +4,19 @@
## Текущие задачи
[+] **Просмотр всех активных трансляций: desktop, затем Android** — 20.10.2026:
Пункт в меню канала открывает автоматически обновляемую сетку; пустое окно ждёт новые источники.
Desktop: 2x2/3x3/4x4 с расширением по числу плиток; Android: раскладка по доступной площади экрана.
Общий stream_viewers хранит независимые handles/cursors; перекладка не пересоздаёт действующие players.
Отдельные Opus/SoundTouch/PTS/аудиочасы каждого источника, сумма PCM в одном output с limiter.
GAP, завершение и ошибка одного источника не останавливают остальные; размеры RS-колец не менялись.
C/desktop и Android APK собраны; Qt stream_view 19/19, шесть stream/audio recovery целей и native Android PASS.
Android unit 129/129; check.sh 164/165: прежний encrypted_attachment phase=5/heartbeats=12001.
На физических телефонах многопоточный просмотр пока не проверен; сборки не устанавливались.
Контракты и ограничения: doc/stream.txt. Логи: /tmp/multiview-desktop-audit-test.log,
/tmp/multiview-desktop-final-tests.log, /tmp/multiview-android-native-final.log,
/tmp/multiview-android-final.log, /tmp/multiview-full-check.log.
[+] **Multicast с исходящими дескрипторами и входящими подписками** — 20.10.2026:
Отдельный routing_layer/multicast.c; контракт и примеры использования — multicast.h.
Номера 1..65535 выдаются последовательно без поколений; подписки на (группа, source, descriptor).