diff --git a/doc/stream.txt b/doc/stream.txt index 506ed627..fa3263bc 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -760,8 +760,10 @@ first_video_frame_offset относительно начала video bytes: UINT KEY=bit0 video flags; остальные биты запрещены. frame_id>0, <=16 описаний каждого трека. Один полный видеокадр с конфигурацией <=2MiB. Общий формат допускает медиаблок <=64KiB; реальный максимум дополнительно ограничен входом RS. Аудиокодек фиксирован на source_epoch. Кодек, разрешение, поворот и конфигурация видео меняются по config_revision. -Новая revision может пропускать номера; старая игнорируется. Изменение metadata без новой revision запрещено. -При переходе receiver удаляет неполный видеокадр и ждёт полный KEY, сохраняя историю PTS/кадров и аудио. +Новая revision может пропускать номера; регрессия revision во входящем потоке отвергается. +Изменение metadata без новой revision запрещено. При переходе каждый media cursor удаляет +неполный видеокадр и допускает новую revision только с полным KEY, сохраняя аудио. +Отстающий reader может дочитать прежнюю revision из кольца: сравнение ведётся с revision его выданного видео. video_codec: 1=H.264, 2=H.265, 3=VP9, 4=AV1, 5=H.266/VVC. B-кадры при захвате запрещены. KEY payload: config_length:4BE, decoder configuration (<=64KiB), access unit (непустой). @@ -774,9 +776,17 @@ audio_codec=0: нет аудио; audio_codec=1: Opus mono, audio_codec=2: Opus Оба формата: 48000Hz, 960 кадров на канал (20ms), packet<=1275 bytes; interleaved stereo содержит 1920 отсчётов PCM. Opus-пакеты между медиаблоками не разрезаются. Размеры треков не включают header/описания. -stream_media проверяет весь блок до изменения view. Receiver собирает один последовательный кадр. -При GAP, потере продолжения или конфликте metadata недособранный кадр удаляется, видео ждёт KEY. -Аудио обрабатывается независимо; неверный формат/длины не передаются платформенным декодерам. +stream_media проверяет весь блок до изменения view. Store индексирует подтверждённые пачки, +а отдельный cursor каждого reader собирает один последовательный кадр прямо из общего RS-кольца. +При GAP, потере продолжения или EXPIRED reader сбрасывает сборку и начинает с полного KEY. +Полный KEY передаётся декодеру сразу, без таймера ожидания; частичный KEY и зависимые кадры до него не выдаются. +Потери, восстановленные и проверенные FEC, не создают GAP и не требуют перезапуска декодера. +Аудио читается независимо; неверный формат/длины не передаются платформенным декодерам. +Конфликт metadata завершает чтение ошибкой. Неиспользуемый отдельный stream_media_receiver удалён: +проверки сборки и восстановления выполняются на рабочем store/cursor. +Desktop и Android открывают просмотр через stream_media_open: ядро проверяет выбранный source_epoch +и эпоху существующей session, создаёт подписку и cursor; при ошибке снимает только нового владельца. +Платформы сохраняют собственное планирование чтения и вывод; закрытие использует обычный stream_close. Ретрансляторы не разбирают uAV2 и не пересоздают подписи источника. Android: diff --git a/src/stream/stream_media_buffer.h b/src/stream/stream_media_buffer.h index 771ff806..00363f8d 100644 --- a/src/stream/stream_media_buffer.h +++ b/src/stream/stream_media_buffer.h @@ -4,6 +4,8 @@ * Независимые audio/video позиции читают оригинал из кольца. Рабочая память cursor — один видеокадр, * один Opus-пакет и два scratch медиаблока. Курсоры ничего не закрепляют против вытеснения. * read возвращает заимствованный packet до следующего read/destroy; frontend копирует только ближайшие записи. + * Каждый cursor допускает новую video revision только с полным KEY; готовый KEY выдаётся без дополнительного ожидания. + * Смена video revision не сбрасывает аудио и не запрещает другим readers дочитать прежнюю revision. * WAIT — материал ещё не готов/дальше audio lookahead. GAP/EXPIRED требуют новой точки KEY; END — весь материал выдан. */ #ifndef STREAM_MEDIA_BUFFER_H #define STREAM_MEDIA_BUFFER_H