Files
SDR-Rover/docs/lab050_current_state.md
2026-09-08 14:15:36 +03:00

380 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lab050: потоковая архитектура видео и возраст изображения
8 сентября 2026 года. SOFTWARE ONLY. RF TX = 0. Оборудование не запускалось
и не инвентаризировалось. Управление ELRS/CRSF и SDR-видео остаются раздельными.
**Получен блочный независимый RX с ограниченным IQ-буфером и модель свежей
видеопередачи. Текущий RX не обеспечивает 5 или 10 свежих изображений/с.**
По времени радио оба уровня возможны на уменьшенном JPEG без QPSK; итог
полной системы — CONDITIONAL, с конкретными ещё не выполненными условиями ниже.
## Фиксация Lab049 и исходный аудит
Исходный HEAD `ac6cc792b896e34f7dd6d291955a0387fdafcf41`, ветка main.
Проверены status, diff/stat, diff/check и точный staged scope. В отдельный
коммит вошли 12 файлов Lab049: три скрипта, три файла тестов, три отчёта,
README, PROJECT_LOG и LABS_INDEX. Сгенерированные данные, `.codex`, локальные
AGENTS/CLAUDE и посторонние файлы не добавлены.
Создан разрешённый коммит:
`cdf5396e807b83f82a264632dc46f71428fcd616`
— `Lab049: define ELRS control and SDR video architecture`.
От него выполнена Lab050; её изменения не добавлялись в индекс и не коммитились.
Push/pull/fetch не выполнялись.
Аудит Lab034–037 и связанных модулей показал:
| Уже существовало | Как используется в Lab050 |
|---|---|
| `image_fragments.py`, Lab046: image ID, индекс/число фрагментов, размер объекта | Неизменная фрагментация JPEG, отдельный ID каждого снимка |
| Lab034: группировка изображения и изоляция стирающего FEC | Переиспользована семантика границ изображения; старый 12+3 FEC не включён |
| Lab035, `video_frame_scheduler.py`: активный кадр, очередь ожидающих, LATEST_ONLY, прогноз завершения | Расширен существующий планировщик; новая независимая очередь не создана |
| Lab036–037: один самый свежий ожидающий кадр, завершение активного | Сохранена политика; очередь ожидающих не больше одного изображения |
| Lab048: корреляционный поиск и строгий PHY/FEC/CRC | Использованы в блочном API; чтение IQ отделено от memmap |
Изменения кода: `protocol/video_frame_scheduler.py` получает готовность JPEG
отдельно от времени съёмки, произвольный MAX_VIDEO_AGE и прогноз задержки после
радио. Старые вызовы и все пять прежних политик сохраняют поведение.
`lab046c_offline_recovery.decode_candidate_from_memmap` принимает необязательный
читатель IQ; прежнее чтение CS8 остаётся поведением по умолчанию.
Новые файлы: [модель и эксперимент](../experiments/lab050_video_stream.py),
[блочный RX](../experiments/lab050_streaming_receiver.py),
[тесты](../tests/test_lab050_video_stream.py).
## Политика свежести
Активное изображение является выбранной атомарной единицей обслуживания:
после допуска оно завершается целиком. Отдельный физический пакет тем более
не прерывается. Во время передачи каждого пакета принимаются события готовности
новых JPEG и заменяется ожидающий снимок. При постоянной скорости, отсутствии
CONTROL в SDR и известном числе пакетов точный прогноз допуска позволяет
не начинать изображение, уже не укладывающееся в возраст.
Свежесть определяется парой `(capture_timestamp, image_id)`, а не порядком
завершения кодирования. Поздно готовый старый снимок не вытесняет более свежий
и не возвращается на экран после опустошения очереди. Обнаруженный независимыми
проверяющими отказ этого правила исправлен и покрыт двумя регрессиями.
Причины удаления различаются: `replaced_by_newest`, `older_capture_arrived_late`,
`prediction_reject`; при оценке загруженного RX возможен также отказ допуска
к обработке. Если уже нижняя оценка задержки RX делает кадр просроченным,
его отклоняет TX-допуск, до занятия эфира. Удаление снимков — нормальная
политика видео, а не потеря обязательной управляющей команды.
Планировщик здесь остаётся событийной моделью с известными входами и историей
для отчёта. Его массив входов/результатов зависит от числа исследуемых кадров;
**логическая ожидающая очередь** ограничена одним снимком. Готового адаптера
живой камеры с ограниченным журналом пока нет. Независимость памяти от длины
сессии реализована и проверена непосредственно для IQ-приёмника.
## Профили JPEG и источник
Источник — реальное фото `data/raw/lab011_scene.jpg`. Для каждого профиля
созданы 30 исходных кадров: первый повторяет исходную подготовку, остальные
получены детерминированным циклическим сдвигом `(7i mod 61, 3i mod 31)` пикселей
до уменьшения. Это искусственная последовательность одного фото, не съёмка камеры.
Разрешение, цвет и quality фиксированы; размеры файлов не подбирались вручную.
Во всех профилях subsampling=2, optimize=False, progressive=False.
| Профиль | Разрешение | Цвет | Quality | Первый JPEG, байт | Диапазон 30 JPEG, байт | PSNR первого, dB |
|---|---|---|---:|---:|---:|---:|
| [A](../data/processed/lab050/profile_A.jpg) | 320×240 | RGB | 35 | 8724 | 8724–9250 | 28,12 |
| [B](../data/processed/lab050/profile_B.jpg) | 160×120 | RGB | 35 | 2771 | 2771–3031 | 28,11 |
| [C](../data/processed/lab050/profile_C.jpg) | 128×96 | RGB | 25 | 1740 | 1740–1893 | 27,17 |
| [D](../data/processed/lab050/profile_D.jpg) | 160×120 | grayscale | 30 | 2028 | 2028–2233 | 30,15 |
PSNR = 10 log10(255²/MSE); сравнение с несжатым изображением **того же
разрешения и цветового режима** после преобразования. Оно характеризует
JPEG-искажение внутри профиля, не потерю деталей при уменьшении и не
пригодность картинки для управления. Между RGB и grayscale эти числа не
доказывают превосходство качества. Четыре выходных JPEG визуально проверены;
субъективный вердикт пригодности для оператора не присваивался.
Первый A побайтно совпал с Lab047, SHA-256:
`e913dab9ac32795250880a4be67fbbbf89a4f96536842476ab591d1acaafc02a`.
Исходный контрольный JPEG не изменён. Перекодирование новых профилей разрешено
постановкой Lab050; новый видеокодек не разрабатывался.
## Точная ёмкость нынешнего PHY
Один RF-пакет переносит 244 байта JPEG плюс 12 байт метаданных фрагмента,
дополняется до 256 байт нагрузки packet и до 268 байт вместе с packet header/CRC.
FEC R=1/2 K=7, шесть хвостовых бит, перемежитель, PHY header, PRBS7 и BPSK
сохранены. RF-пакет занимает 5724 передаваемых бита и 42 дополнительных
символьных интервала, итого **5766**. Проверено по настоящему плану Lab045.
Для JPEG размера J: N=ceil(J/244), T_RF=N×5766/Rs.
Точный максимальный размер для f кадров/с:
`Jmax = 244 × floor(Rs/(5766×f))`.
Это ступенчатая граница с добивкой, не деление полезного битрейта на восемь.
| Rs, ксимв/с | Выборок/символ при 8 Мвыб/с | Jmax для 3 кадров/с | Jmax для 5 кадров/с | Jmax для 10 кадров/с |
|---:|---:|---:|---:|---:|
| 200 | 40 | 2684 | 1464 | 732 |
| 400 | 20 | 5612 | 3172 | 1464 |
| 500 | 16 | 6832 | 4148 | 1952 |
| 800 | 10 | 11224 | 6588 | 3172 |
| 1000 | 8 | 13908 | 8296 | 4148 |
Все размеры в байтах. На пределе нет запаса для потерь, пауз, объёмных данных
или роста JPEG. Никакие ELRS CONTROL/TELEMETRY из этого бюджета не вычитаются.
Физическая стоимость старого LinkPacket используется лишь как эквивалентная
шкала времени существующего симулятора: его длительность принудительно равна
5766/Rs. LinkPacket не добавлен в реальную PHY-упаковку Lab046.
Максимум изображений/с по времени радио для **первого** JPEG каждого профиля:
| Rs, ксимв/с | A, 36 пакетов | B, 12 пакетов | C, 8 пакетов | D, 9 пакетов |
|---:|---:|---:|---:|---:|
| 200 | 0,964 | 2,891 | 4,336 | 3,854 |
| 400 | 1,927 | 5,781 | 8,672 | 7,708 |
| 500 | 2,409 | 7,226 | 10,839 | 9,635 |
| 800 | 3,854 | 11,562 | 17,343 | 15,416 |
| 1000 | 4,818 | 14,453 | 21,679 | 19,270 |
По всей последовательности A использует до 38 пакетов, B до 13, D до 10;
C остаётся в восьми пакетах. Именно C удобен для следующего воспроизводимого
опыта. Профиль B также укладывается по радио в 5 кадров/с на 400 ксимв/с.
JPEG A не достигает 5 кадров/с даже на 1 Мсимв/с: 8724 > 8296 байт.
## Матрица очередей и фактический смысл FPS
Выполнено 240 сценариев: четыре профиля × входные 3/5/10 кадров/с × пять Rs
× пределы возраста 200/333/500/1000 мс. По 30 входных изображений в каждом.
Идеальный пакетный путь сериализует packet, проверяет CRC и собирает точные
JPEG. Стоимость FEC/поиска IQ в этой матрице сначала равна нулю **по модели**;
её реальный замер выполнен отдельно, ниже. Ошибок радио в матрице нет.
Окно наблюдения H=30/input_fps: 10, 6 или 3 с. Основные RAW RECEIVED FPS и
FRESH DISPLAY FPS считают только события внутри H. Полученные при дальнейшем
опустошении кадры показываются отдельно. Свежим считается только показанный
снимок с возрастом ≤MAX_VIDEO_AGE. Просроченное изображение на экран не выпускается,
поэтому raw display FPS совпадает с fresh display FPS; повторный показ старого
изображения не засчитывается новым обновлением.
| Модель | Возраст | Вход | Передано | Удалено до TX | Получено / показано всего | Fresh FPS в H | После H |
|---|---:|---:|---:|---:|---:|---:|---:|
| C, камера 5, Rs=500k | 200 мс | 30 | 30 | 0 | 30 / 30 | **5,000** | 0 |
| C, камера 10, Rs=500k | 200 мс | 30 | 30 | 0 | 30 / 30 | **10,000** | 0 |
| B, камера 5, Rs=400k | 200 мс | 30 | 30 | 0 | 30 / 30 | **5,000** | 0 |
| C, камера 10, Rs=200k | 200 мс | 30 | 0 | 30, прогноз возраста | 0 / 0 | **0** | 0 |
| C, камера 10, Rs=200k | 333 мс | 30 | 14 | 16, замена свежим | 14 / 14 | **4,333** | 1 |
| A, камера 5, Rs=1000k | 200 мс | 30 | 0 | 30, прогноз возраста | 0 / 0 | **0** | 0 |
Для перегруженного C/10/200k при 333 мс возраст показанных снимков примерно
231–329 мс, ожидающая очередь ≤1. Увеличение предела до 500/1000 мс в данном
сценарии не меняет 14 передач и 16 замен; оно не создаёт дополнительную ёмкость.
При 200 мс уже само радио требует 230,64 мс и все снимки обоснованно отклонены.
Результат C/5/500k: IMAGE AGE min/median/max = **92,563 / 92,590 / 92,629 мс**.
Это возраст **в модели идеального RX и мгновенного экрана**, не камера→монитор.
Вход 30, transmitted=received=displayed=30, dropped=0, H=6 с,
RAW RECEIVED FPS=RAW DISPLAY FPS=FRESH DISPLAY FPS=5.
## Блочный приёмник
```mermaid
flowchart LR
I[IQ-блок до 65536 выборок] --> B[Ограниченный буфер и хвост]
B --> C[Грубый поиск в принадлежащем блоку интервале]
C --> S[Штатная строгая проверка PHY/FEC/CRC]
S --> P[Выдача принятого пакета из push_iq_block]
P --> A[Сборщик текущего image ID]
A --> J[JPEG decode; далее проверка возраста и экран]
```
`StreamingReceiver.push_iq_block()` принимает непрерывные complex64-блоки
без границ TX, payload, ожидаемых ID или числа кадров. `finish()` проверяет
остаток, не дополняет недостающую запись нулями. Выдача пакетов не ждёт конца
сессии. Глобальные индексы сохраняются; принятая область исключается из
повторной строгой проверки. Список кандидатов относится лишь к одному блоку.
Внутри RX нет истории принятых пакетов; вызывающий код должен сразу их забирать.
Размер удерживаемого хвоста определяется максимально допустимым PHY packet
1024 байта, а не известными передатчику границами. Сохраняются левый запас
уточнения, опорная последовательность, счётчики и текущие IQ. Порог Lab047
`0.030964473262429237` не меняется и не подстраивается по успеху JPEG.
Скалярная статистика корреляций не объявляется новой калибровкой фона.
Децимация 20 старого поиска непригодна как универсальная константа для
высоких скоростей. В адаптере она равна min(20, samples_per_symbol//2):
**20/10/8/5/4** для 200/400/500/800/1000 ксимв/с, оставляя две выборки/символ.
Меняются параметры поиска, но не FEC, CRC, модуляция или порог.
Константы старых лабораторий восстанавливаются после вызова; это
последовательный программный адаптер, не потокобезопасная многопоточная библиотека.
IQ-блоки должны быть непрерывными. При будущих потерях блоков драйвера нужно
явно сбросить незавершённый поиск/объект и обозначить разрыв; live-подключение,
обработка таких разрывов и управление переполнением драйвера здесь не реализованы.
Синхронный `push` создаёт обратное давление программному источнику; при текущей
скорости RX настоящий источник 8 Мвыб/с потребовал бы потерь/переполнения.
## Интеграционный опыт IQ и производительность
На **каждой** из пяти скоростей поданы три JPEG C, 24 физических пакета,
с неизвестным началом, межкадровыми интервалами и границами входных блоков.
CS8-квантование передатчика сохранено. Шум один для всех: coded-bit Eb/N0=7 dB,
seed=50050, остаточная частота +10 Гц, фаза 0,73 рад и чередование полярности.
После добавления шума поток complex64 не переквантован. Фон также генерируется
детерминированно. TX создаёт лишь очередной RF-пакет, целая IQ-запись не хранится.
**Все пять проходов: 24/24 пакета, 3/3 JPEG, точный SHA; все JPEG выданы до finish.**
Сборщик принимает ID/индексы только из проверенных CRC пакетов, сопоставление
с TX-хешами происходит после сборки. В этом опыте ID монотонны в одной сессии;
wrap/restart видеосеанса не реализован. Разрешён только текущий объект,
ограничены его размер (64 КиБ) и число фрагментов; журнал результатов опыта
отделён от памяти RX. Дополнительно пройдены интеграционные тесты идеального
канала на всех скоростях, границ двух блоков, усечённого потока и длинного фона.
| Rs, ксимв/с | IQ, с | Обработка, с | Обработка / RF-секунда | Кандидатов строгой проверки | Оценка массивов RX, МиБ |
|---:|---:|---:|---:|---:|---:|
| 200 | 0,792483 | 35,124167 | **44,322** | 2191 | 143,412 |
| 400 | 0,401868 | 59,470863 | **147,986** | 2575 | 83,450 |
| 500 | 0,323745 | 55,157553 | **170,374** | 2374 | 71,457 |
| 800 | 0,206561 | 60,954975 | **295,095** | 3113 | 53,469 |
| 1000 | 0,167499 | 55,921951 | **333,865** | 2995 | 47,473 |
Знаменатель — все поданные IQ, включая фон/хвост. В JSON отдельно есть время
чистой передачи полезных RF-пакетов и соответствующее ещё худшее отношение.
CPU-замер включает push/finish, сборку и JPEG decode; генерация TX/IQ,
преобразование исходной фотографии и сравнение TX-хешей исключены.
Это однократные настольные wall-clock замеры, не норматив CPU и не оценка
предельной дальности. Они не сравниваются напрямую с Lab048 на другой записи.
На 500 ксимв/с грубый поиск занял 0,492 с, строгая обработка — 54,586 с.
Из 2374 кандидатов приняты 24; большинство остальных отклонено по magic
PHY-заголовка. Новый ограниченный буфер **не устранил** стоимость ложных
кандидатов. Увеличение Rs не делает этот приёмник быстрее реального времени.
Факт Lab048 «15 с → 13,966 с» остаётся характеристикой прежней записи.
Пять тяжёлых проходов выполнены один раз. Первый запуск после сохранения
замеров остановился на несовпадении API `build_packet/parse_packet` в модели
очереди; вызовы исправлены и проверены тестами. Итоговая матрица построена
с `--reuse-benchmarks`, без повторения RF/IQ-замеров. Режим проверяет скорости,
параметры шума/порог и хеши восстановленных контрольных объектов.
## Память
Предел блока 65536 выборок. Для каждого Rs буфер ограничен выражением:
`core + maximum_PHY_waveform + refinement_margin + left_margin + input_block`.
Число кадров в сессии в формулу не входит. В тесте 100 последовательных
блоков сохраняется тот же предел и не появляется история IQ.
| Rs, ксимв/с | Измеренный пик удерживаемых IQ, выборок | Проверяемый предел, выборок | IQ в пике, МиБ |
|---:|---:|---:|---:|
| 200 | 853027 | 853556 | 6,508 |
| 400 | 493587 | 496316 | 3,766 |
| 500 | 424309 | 424864 | 3,237 |
| 800 | 308819 | 317696 | 2,356 |
| 1000 | 279589 | 281968 | 2,133 |
С запасом на копирование, грубый и строгий поиск расчёт массивов даёт
47,5–143,4 МиБ против исторической оценки Lab048 около 1,44 ГиБ.
Это **расчёт массивов, не измеренный RSS процесса**: Python/SciPy, аллокатор,
кэши и вызывающая программа добавляют память. История отчёта и заранее
подготовленные 30 JPEG относятся к эксперименту, не к обязательному IQ-состоянию.
## Задержка и условие достижимости
Для каждого снимка JSON хранит capture, encode_complete, queue_enter,
first_tx, last_tx, first_rx, object_complete, decode_complete и display.
У не переданных/не допущенных стадий стоит null, причина удаления явна.
QUEUE AGE=first_tx−queue_enter; RF AGE=last_tx−first_tx;
END-TO-END IMAGE AGE=display−capture.
| Слагаемое | Статус |
|---|---|
| T_CAPTURE | Реальная экспозиция/считывание неизвестны; источник задан событиями, длительность моделируется нулём |
| T_ENCODE | Измерено CPU-кодирование подготовленных изображений; C медиана около 0,143 мс; уменьшение/сдвиг вне этого замера |
| T_QUEUE | Рассчитано существующим планировщиком; не измерение живой очереди |
| T_RF | Рассчитано по настоящему формату; C на 500k — 92,256 мс |
| T_RX_PROCESSING | Измерено в IQ-опыте, включая взаимодействие с буфером; в модели подставляется отдельно как стоимость пакета |
| T_DECODE | Измерено CPU JPEG decode; C медиана около 0,187 мс; в общем IQ-замере уже включено, повторно туда не прибавляется |
| T_DISPLAY | Экран не подключён; длительность моделируется нулём |
В конвейере RF и обработка ранних пакетов перекрываются. Нельзя прибавлять
полный CPU service time к полному RF-времени и называть это измеренной
сквозной задержкой. Прогноз без предыдущей очереди RX после last_tx:
`lookahead + p + (N−1)×max(0,p−T_packet) + JPEG_decode`, где p — стоимость
пакета. Формула независимо проверена последовательным расчётом 32 наборов
событий и допуском по обе стороны точной границы возраста.
На 500k консервативный предел выдачи из буфера без стоимости CPU — 52,608 мс.
Средняя измеренная стоимость полного RX-опыта — 2,298 с на переданный RF-пакет
(включает фон и отвергнутых кандидатов, не только принятый FEC). При подстановке
этой стоимости все 30 кадров C/5fps отклоняются ещё до TX при любом проверенном
MAX_VIDEO_AGE; **FRESH DISPLAY FPS=0**. Это прогноз с измеренной стоимостью,
не эксперимент с живым экраном.
Проверены также два явно условных варианта ускоренного RX:
| Условие | 5 кадров/с | 10 кадров/с |
|---|---:|---:|
| Выбранный JPEG | ≤1952 байт, 8 пакетов; C фактически ≤1893 | То же |
| Выбранный Rs | 500 ксимв/с, 16 выборок/символ | То же |
| Нижняя граница только по радио для 8 пакетов | 230,640 ксимв/с | 461,280 ксимв/с |
| Предположенная стоимость RX-пакета | ≤16 мс | ≤12 мс |
| Ускорение относительно данного замера | **≥143,64 раза** | **≥191,52 раза** |
| MAX_VIDEO_AGE | 200 мс | 200 мс |
| Расчётный возраст min/median/max | 192,447 / 192,474 / 192,513 мс | 160,447 / 160,474 / 160,513 мс |
| Свежих в исходном окне | 30/6 с = **5** | 29/3 с = **9,667** |
| С учётом опустошения | 30; без дополнительных | 30; один после 3 с |
| Частота по интервалам последовательных показов | около 5 | около 10 |
| FEASIBLE | **CONDITIONAL** | **CONDITIONAL** |
Для 10 кадров/с стартовая задержка превышает период камеры, поэтому первый
трёхсекундный отчёт честно даёт 9,667, а не 10 с включением позднего кадра.
Частота около 10 относится к установившимся интервалам. Условия не означают,
что такое ускорение достигнуто или достижимо только небольшой правкой.
Живые capture/display, входной jitter, потери и запас CPU ещё должны уложиться
в оставшийся бюджет. Для 5 кадров/с запас возраста этого примера лишь около 7,5 мс.
Итак, **по пропускной способности BPSK достаточно**, без QPSK и нового FEC;
**с нынешним измеренным приёмником — нет**. Аппаратная работоспособность
HackRF на этих Rs в данном задании не проверялась. 3 кадра/с остаются
промежуточным уровнем, цель проекта >3, ориентиры 5 и 10.
## ELRS и следующий опыт
CONTROL LINK OK/LOST — внешнее состояние для контроллера ровера. Видеопланировщик
вообще не принимает его как радиопакет. Проверка на существующем TwoStageFailsafe:
при отсутствии новых команд 100 мс → замедление, 200 мс → торможение,
а результат видеомодели остаётся тем же. Это проверка разделения моделей,
не готовое сопряжение CRSF. Safety/session/emergency-модули не менялись.
Рекомендуемый Lab051 — программное профилирование и ускорение строгого
обработчика ложных кандидатов на сохранённом детерминированном сценарии
C/500k, с отдельной проверкой фоновых ложных принятий и неизменными FEC/CRC.
Измерять стоимость уточнения, оценки частоты, заголовка и FEC; сохранить
независимость от TX и корректность на границах блоков. Не объявлять 5 кадров/с
до полного непрерывного опыта со свежими JPEG и устойчивым временем обработки.
RF, QPSK, новый кодек, 12+3 FEC и перенос на 180 МГц этим этапом не разрешаются.
## Воспроизведение
```powershell
python experiments/lab050_video_stream.py
# Только пересчёт матрицы с уже сохранёнными локальными замерами IQ:
python experiments/lab050_video_stream.py --reuse-benchmarks
python -m pytest tests/test_lab050_video_stream.py -q -p no:cacheprovider
python tools/quick_gate.py
python -m pytest tests/ -q
git diff --check
```
Полный первый запуск занимает минуты; новые RF-сеансы в нём отсутствуют.
[result.json](../data/processed/lab050/result.json) содержит 240 сценариев,
все временные отметки, профили, расчёты ёмкости и отдельные условные модели.
[receiver_benchmarks.json](../data/processed/lab050/receiver_benchmarks.json)
содержит исходные пять IQ-замеров. Большие IQ-файлы не создавались.
44 целевых теста проходят. Verifier/adversary подтвердили исправление
поздно завершённого старого кадра; verifier независимо проверил границы
ёмкости, 60 сценариев очереди, 32 прогноза RX и арифметику IQ-замеров.
Итог: quick_gate PASS — 650 тестов и 14 функциональных проверок;
полный pytest — 650 passed за 78,73 с, одно предупреждение о доступе
к `.pytest_cache`; git diff --check PASS. Подробности — в записи 039 журнала.
LAB050 GIT ADD = NOT PERFORMED. LAB050 COMMIT = NOT PERFORMED.
PUSH = NOT PERFORMED. RF TX = 0. STOP.