From ef76ca5de3c68f8eca13f087a47964e5999d8694 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Oct 2026 07:24:13 +0200 Subject: [PATCH] Document audiovisual stream format and Android ownership contracts --- doc/stream.txt | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/doc/stream.txt b/doc/stream.txt index ffae1ac9..ab928e4b 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -703,3 +703,83 @@ END, принятый от первого поставщика, сообщает Restart в той же source_epoch сохраняет consumer cursor: bootstrap от отстающего поставщика не повторяет уже выданные DATA. Повтор restart до отложенного GAP сохраняет предыдущую, ещё не сообщённую приложению границу; пропуск сообщается одним GAP. Инструкция и параметры стенда: tests/stream_netns/README.md. + +8. Live media и Android + +Транспортный wire v3 не меняется. stream_publish_block публикует одну непустую FEC-пачку +немедленно, с сохранением границ. Первая запись выбирает режим источника: byte write или block write; +смешивание отвергается до изменения данных. stream_publish_finish не освобождает буфер источника. + +Медиаблок uAV1 находится целиком внутри входа RS, поэтому header восстанавливается вместе с данными. +При штатных 6+2 полный медиаблок <=7080 байт. Раскладка big-endian, без packed-структур: + +fixed header (32 байта): + magic="uAV1":4, video_codec:1, audio_codec:1, video_count:1, audio_count:1, + width:2, height:2, rotation:2, header_size:2, video_size:4, audio_size:4, + first_video_frame_offset:4, config_revision=1:4. +Далее video_count описаний по 32 байта, audio_count описаний по 16 байт, +затем video_size видеобайтов и audio_size аудиобайтов. Описания задают последовательные +неперекрывающиеся диапазоны внутри соответствующего трека; сумма длин должна совпадать с header. + +video descriptor: + frame_id:8, PTS_us:8, frame_size:4, fragment_offset:4, fragment_length:4, flags:1, reserved=0:3. +audio descriptor: + PTS_us:8, packet_length:4, samples:2, reserved=0:2. + +first_video_frame_offset относительно начала video bytes: UINT32_MAX — начала нет, +иначе это смещение первого фрагмента с fragment_offset=0. Начал может быть несколько, +таблица описывает каждое. Данные конфигурации не являются отдельным кадром. +KEY=bit0 video flags; остальные биты запрещены. frame_id>0, <=16 описаний каждого трека. +Один H.264 кадр <=2MiB. Общий формат допускает медиаблок <=64KiB; реальный максимум дополнительно +ограничен входом RS. Конфигурация, разрешение, поворот и набор треков фиксированы на source_epoch. + +video_codec=1: AVC/H.264 Annex B, без B-кадров. Каждый KEY содержит SPS, PPS и IDR в одном +логическом кадре; это позволяет позднему viewer настроить декодер после получения полного KEY. +audio_codec=0: нет аудио; audio_codec=1: Opus, 48000Hz mono, 960 samples (20ms), packet<=1275 bytes. +Opus-пакеты между медиаблоками не разрезаются. Размеры треков не включают header/описания. + +stream_media проверяет весь блок до изменения view. Receiver собирает один последовательный кадр. +При GAP, потере продолжения или конфликте metadata недособранный кадр удаляется, видео ждёт KEY. +Аудио обрабатывается независимо; неверный формат/длины не передаются платформенным декодерам. +Ретрансляторы не разбирают uAV1 и не пересоздают подписи источника. + +Android: +- Один foreground publisher на процесс, один viewer. Каждая операция имеет новый session ID. +- StreamService владеет камерой, микрофоном и PARTIAL_WAKE_LOCK; пересоздание Activity не завершает источник. + Сервис camera|microphone запускается из видимого приложения после runtime-разрешений; без микрофона — camera. +- Camera2 TEMPLATE_RECORD пишет в Surface AVC encoder. Выбирается совместимое разрешение <=1280x720, + 30fps, AVC baseline, 1–2Mbit/s, I-frame interval=1s. Выход нормализуется в Annex B; + partial output собирается в access unit. Codec config передаётся с каждым IDR. +- Camera MediaCodec Surface timestamps и AudioTimestamp.TIMEBASE_MONOTONIC приводятся к общему + origin System.nanoTime; время callback/получения блока не заменяет capture PTS. +- Camera/codec callbacks — HandlerThread, Opus encoder — audio capture thread, decoder/AudioTrack — viewer worker. +- Native bridge держит pthread mutex, ограниченные очереди и один pending post либо timer. + attach/detach выполняются в uasync вне callbacks. detach отменяет post/timer, закрывает handles, + очищает media, snapshots и запрещает producers до уничтожения instance/ua. + UI не сохраняет borrowed inst/ua. Idle без pending data не будит мобильное ядро. +- TX<=2MiB/128 records, не более 250ms ожидания. Actor публикует <=2 пачек за вызов, + неполную пачку объединяет с аудио/видео до 20ms; продолжение работы через timer 5ms. + При video pressure отбрасываются целые ожидающие кадры, P-кадры запрещены до нового KEY. + Кодировщик получает отказ admission и запрашивает KEY с ограничением частоты 500ms. +- RX<=4MiB/256 records. Pressure очищает media, сохраняет control results и сообщает GAP. + Kotlin viewer дополнительно ограничивает video queue 12 кадрами/2MiB, audio queue 16 пакетами. + Нельзя выбросить reference P-кадр и продолжить с зависимыми: переполнение сбрасывает декодер до KEY. + Просроченный кадр при нормальной последовательности декодируется без показа. +- После prebuffer 150ms AudioTrack clock определяет время показа видео; видео без аудио использует + монотонные часы. Короткие audio gaps заполняются Opus PLC, большой разрыв сбрасывает timeline. + Локальный preview использует обычную подписку на собственный источник с отключённым воспроизведением + своего микрофона, чтобы исключить акустическую обратную связь. Камера при закрытии preview продолжает работать. +- Source list строится в uasync по stream_member_load и stream_member_online, выдаётся UI копией JSON. + Свежий snapshot содержит source ID, epoch, self и экранированное имя. Увиденный источник не создаёт подписку. +- Stop сначала завершает capture producers и encoder EOS (максимум 1s), затем FIN. + Publisher сохраняет конечный буфер ещё 2s для сетевой выдачи, затем закрывается. + Это ограниченный drain, не подтверждение воспроизведения всеми viewers. Abort/core restart очищает очередь сразу. + Поздние команды старого session ID не меняют новый запуск. +- Звонок, рация и voice recording не начинают новый capture во время трансляции; + трансляция также отвергается при активном звонке/рации/voice recording. + +Логи: stream — epoch/owners, очереди, GAP и teardown; video — codec/config, KEY, PTS и декодер. +Kotlin progress — DEBUG раз в секунду; C packet-level диагностика — TRACE по запросу. +Проверки: test_stream_media (все пары потерь 6+2, parser/границы/GAP), test_android_stream +(живое ядро, preview, verified snapshots, FIN/cancel, concurrent producer/detach и leak count), +Android H264AnnexBTest (Annex B/length-prefixed NAL, SPS/PPS/IDR и усечённые данные).