Files
SDR-Rover/docs/video/VIDEO_004.md

311 lines
30 KiB
Markdown
Raw Permalink 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.
# VIDEO-004: скорость программного приёмника и защита видео от потери пакетов
16 сентября 2026 года. Worktree `SDR_Rover_Video`, ветка `video-development`,
исходный HEAD `af7c05f`. Все опыты программные: `RF TX = 0`, SDR не открывался.
Файлы VIDEO-001/002/003 и производственный BPSK не заменялись. Коммит и push
не выполнялись.
## Вывод и граница опыта
`VIDEO-004 STATUS = PARTIAL`. Прогретый доступный программный путь выполнил
порог скорости: фактор реального времени **0,130** на исходном 32-пакетном
сигнале и **0,131** на 40-пакетном пути с внешним FEC. Порог `≤0,67` и
предпочтительный `≤0,50` пройдены на этом ПК. Восстановлены четыре стёртых
пакета и четыре видеокадра побайтно. Существующий H.264 SHORT_GOP проверен
отдельной моделью доставки.
Это **AVAILABLE SOFTWARE PATH REALTIME FACTOR**, а не скорость полного SDR RX.
В потоковом замере сигнал уже дан с известными символьными отсчётами.
`TIMING RECOVERY = NOT_IMPLEMENTED_IN_THIS_BENCHMARK` и
`CARRIER TRACKING FROM RAW IQ = NOT_IMPLEMENTED_IN_THIS_BENCHMARK`.
RRC-проверка с известными тактами существует отдельно. Её время не включено
в число 0,130. Модель не оценивает операторское качество и не доказывает
устойчивость через эфир.
При пяти затравках и двух шестисекундных окнах NORMAL 640×360/10 лучший
проверенный для 1–5% потерь вариант `QPSK 500 / inner FEC 2/3 / outer 8+1`
с дополнительным проверочным пакетом для блока опорного кадра показал
**591/600 кадров при 1%** и **498/600 при 5%**. Потеря 102 кадров из 600
при 5% вместе с малым запасом пропускной способности не позволяет объявить
этот режим надёжно поддерживаемым. `NORMAL SUPPORTED IN SOFTWARE MODEL = NO`
для всего диапазона 1–5% с принятым в VIDEO-003 запасом 20% к пику.
Это не отрицание отдельных
успешных прогонов.
## Скорость: одинаковый вход до и после
Канонический вход содержит 32 полных PHY-пакета QPSK 500 ксимв/с, inner FEC
2/3, 9 dB `Eb/N0`, сдвиг несущей 73 Гц и 42 переходных символа на пакет.
Затравка `20260916`; файл `data/processed/video004/canonical_symbols.npy`
проверяется по SHA-256 из `canonical_manifest.json`. Приёмник узнаёт размер
пакета из принятого PHY-заголовка. Эталонные SHA передаются только проверке
после CRC. Длительность 32-пакетного сигнала: **0,48736 с**.
`cProfile` использован для поиска затрат. Числа скорости ниже получены
отдельно через `time.perf_counter()` без профилировщика. Для каждого прохода
записаны повторные измерения; указан средний по порядку из повторов.
| Замер | Исходный декодер | Прогретый Numba | Ускорение |
|---|---:|---:|---:|
| 32 пакета, приём и видеозаголовок | 2,690 с; фактор **5,520** | 0,0635 с; фактор **0,130** | **42,36×** |
| Только Витерби, те же 32 мягких слова | 2,125 с; фактор **4,360** | 0,0270 с; фактор **0,055** | **78,68×** |
| 256 пакетов, длинный контроль | фактор **4,518** | фактор **0,133** | 34,1× |
| 40 пакетов, CRC → внешний FEC → сборка видеокадра | фактор **4,446** | фактор **0,131** | **33,90×** |
Первый полный проход с компиляцией Numba занял **0,929 с**; это отдельная
стоимость запуска, не включённая в прогретый фактор. Numba 0.61.2 и
llvmlite 0.44.0 взяты из уже существующего локального набора проекта.
Компиляция работает с `fastmath=False`, а отсутствие Numba даёт явную ошибку
выбранного JIT-режима. После отказа CRC другой декодер не запускается.
Эталонные битовые выходы, решения QPSK, CRC, пакеты и SHA видеоданных до и
после совпали. Длинный поток также совпал на всех 256 пакетах.
Предварительный `cProfile` исходного 32-пакетного пути относил 2,743 из
3,196 с профилируемого времени к Витерби; внутри было 262 336 вызовов
`argmin`. После JIT основными расходами стали поиск преамбулы и оценка
несущей. Для нового 40-пакетного пути профиль приведён отдельно:
| Этап | Время `cProfile`, мс | Доля 91,4 мс | Вызовы | Основной массив или копия |
|---|---:|---:|---:|---|
| Витерби вместе с вызовом декодера | 34,8 | 38,1% | 40 | `choices`: 524 672 байта на полный пакет внутри Numba |
| Поиск преамбулы | 27,1 | 29,7% | 40 | короткие срезы по 1280 символам |
| Коррекция фазы/частоты | 11,3 | 12,4% | 120 | копия только текущего заголовка или тела |
| Оценка фазы/частоты | 9,3 | 10,2% | 80 | временные массивы пилота и заголовка |
| Снятие прокалывания | 1,59 | 1,7% | 40 | 16 396 `float64`, около 131 Кбайт на пакет |
| Решения QPSK | 0,56 | 0,6% | 40 | около 98 Кбайт мягких значений |
| Разбор `protocol.packet` и CRC | 0,33 | 0,4% | 40 | до 1024 байт пакета |
| Сборщик внешнего FEC | 1,61 | 1,8% | 36 | буфер максимум 8 блоков; четыре матричных восстановления |
| Выдача фрагмента видеосборщику | 0,36 | 0,4% | 32 | повторная упаковка в байты не выполняется |
| RRC | отдельно | вне пути | один контроль | 11,09 мс на один пакет при известных тактах |
| Восстановление тактов и слежение из IQ | `NOT_IMPLEMENTED` | вне пути | 0 | не измерялись |
Строки со вложенными вызовами нельзя складывать: например, снятие
прокалывания входит во время декодера. Одиночный RRC-контроль с 16 выборками
на символ прошёл CRC, но его 11,09 мс нельзя прибавлять к потоку как
доказательство готового IQ-приёмника. `tracemalloc` показал пик Python-памяти
13,69 Мбайт до правок и 1,07 Мбайт после на 32 пакетах. Он не учитывает всю
нативную память NumPy и Numba.
## Защита от стирания пакета
Внешний систематический код использует уже существующую в проекте
арифметику GF(256) из `protocol/packet_erasure_fec.py`. Упаковка Lab028 не
используется. Новый символ содержит 16 байт метаданных блока, 2 байта длины,
4 байта времени создания кадра и проверенный 13-байтный видеозаголовок.
Максимум данных видео: **977 байт** в полном payload PHY 1012 байт.
Все внешние символы имеют одинаковую длину для точного восстановления
последнего короткого фрагмента. Внешний код восстанавливает стирания после
строгого CRC; он не исправляет неизвестные ошибочные биты внутри пакета.
Систематический фрагмент сразу передаётся сборщику. При потерях декодирование
блока начинается после получения любых `K` различных символов. Полученный
после 300 мс фрагмент отвергается по времени создания кадра. Буфер
ограничен восемью блоками; дубли и противоречивые метаданные проверяются.
Тесты подтвердили одну потерю, потери до `R`, отказ при числе потерь больше
`R`, точное восстановление байтов и короткого последнего фрагмента.
Для полных пакетов расчётный бюджет без пауз:
| K+R | Доля проверочных пакетов | 2/3: чистое видео, кбит/с | 3/4: чистое видео, кбит/с | Время полного блока 2/3, мс |
|---|---:|---:|---:|---:|
| 8+1 | 11,11% | 456,176 | 501,122 | 137,07 |
| 8+2 | 20,00% | 410,558 | 451,010 | 152,30 |
| 12+2 | 14,29% | 439,884 | 483,225 | 213,22 |
| 12+3 | 20,00% | 410,558 | 451,010 | 228,45 |
| 16+2 | 11,11% | 456,176 | 501,122 | 274,14 |
| 16+4 | 20,00% | 410,558 | 451,010 | 304,60 |
Это предел для полного блока с заполненными пакетами. Последний частичный
блок и защита опорного кадра меняют фактическую долю чётности. Например,
для NORMAL 8+1 с усилением опорных блоков расчёт по числу реально созданных
символов в двух окнах даёт **424,922 / 425,142 кбит/с**. Пик SHORT_GOP 400k
за одну секунду, худший из двух окон: **408,8 кбит/с**. Запас в этой паре
всего около 1,04, без учёта ошибок внутреннего PHY. Блок 16+4 из 20 полных
пакетов занимает 304,6 мс, уже больше срока кадра без ожидания накопления
данных. Для каждого прогона модель учитывает готовность исходных фрагментов,
момент появления чётности и истечение срока.
## Потери, перемежение и опорные кадры
В модели использованы настоящие H.264 пакеты, короткий GOP, ограниченный
сборщик VIDEO-002 и декодер PyAV. Случайные потери применяются независимо
к передаваемым радиопакетам; 1440 основных прогонов покрывают ROBUST,
NORMAL и HIGH QUALITY, два окна, обе скорости внутреннего FEC, шесть схем,
равномерную и усиленную защиту, уровни 0/1/2/5/10/20% и серии 2/5/10.
Для выбора NORMAL дополнительно выполнены 180 прогонов на пяти фиксированных
затравках `20260916…20260920`. Числа ниже относятся к 10 окнам по 60 кадров.
| Внешняя защита, inner 2/3 | 1%: показано / 600 | 5%: показано / 600 | 10%: показано / 600 | 5%: кадр/с | 5%: доля потерь кадров |
|---|---:|---:|---:|---:|---:|
| 8+1 одинаковая | 594 | 452 | 264 | 7,53 | 24,7% |
| 8+1, опорный блок +1 parity | 591 | **498** | 330 | **8,30** | **17,0%** |
| 8+2 одинаковая | 580 | 484 | **363** | 8,07 | 19,3% |
При 1% лучшая доля в этой выборке у одинаковой 8+1; при 5% у усиленной
8+1; при 10% у одинаковой 8+2. Эти короткие реализации не дают оценки
длительного канала. В варианте 8+1 с усилением среднее время от первого
непоказанного кадра до следующего показанного опорного кадра при 5%
составило **535 мс** против **665 мс** у одинаковой 8+1. Это показатель
данной модели и затравок, а не обещание восстановления при любой серии.
В отдельном контроле две потери источников **одного блока опорного кадра**
дали 55/60 показанных кадров в каждом окне при 8+1 без усиления; следующий
опорный кадр появился через 652/668 мс. При усилении блока до двух parity
получено 60/60, два фрагмента восстановлены до срока и провала изображения
не было. Одну потерю восстанавливали оба варианта.
Проверка перемежения глубины 2 отклонена. Даже при 0% потерь она показала
лишь 24/120 кадров для 8+1, 21/120 для 8+2 и 3/120 для 12+2 в NORMAL:
ожидание фрагментов следующего блока нарушило срок 300 мс. Без перемежения
при 0% эти схемы показали 120/120. Серии 2/5/10 сохранены отдельно в JSON;
уменьшение влияния серии не окупило задержку в этом способе чередования.
При одинаковых заданных стираниях пакетов inner FEC 2/3 и 3/4 дали одинаковые
числа показанных кадров для трёх сравниваемых схем NORMAL на пяти затравках;
3/4 даёт больше расчётной ёмкости. Эта модель не создаёт битовых ошибок.
Контроль VIDEO-003 при 3 dB `Eb/N0` на пакетах меньшей длины дал 13/32 CRC
для 2/3 против 3/32 для 3/4. Его нельзя переносить как точную вероятность
для максимальных пакетов, но оснований заменить первый диагностический
кандидат на 3/4 пока нет.
## CODE QUALITY AUDIT
Исходная инвентаризация восьми модулей: 1967 строк, 70 функций. Сейчас те же
восемь модулей содержат 2008 строк и 72 функции; новые модули Витерби и
внешнего FEC содержат ещё 309 строк и 11 функций. Размер кода здесь не
критерий качества. Рабочая цепочка: символы → преамбула/PHY → QPSK → снятие
прокалывания → Витерби → CRC → внешний FEC → свежий видеофрагмент.
| ITEM | BEFORE | ACTION | AFTER | EFFECT |
|---|---|---|---|---|
| Витерби | Python-цикл, 262 336 `argmin` за 32 пакета | JIT со скалярным выбором ветви и `fastmath=False` | те же биты | 78,68× для Витерби |
| Коррекция несущей | два прохода по всему оставшемуся потоку на пакет | ограничена заголовком и текущим телом | те же пакеты/CRC | фактор пути 5,520 → 0,130 вместе с JIT |
| Проверка конечности | сканирование всего суффикса на каждом пакете | проверяется кандидат и его тело | повреждение текущего пакета отвергается | 256 пакетов: фактор 0,288 → 0,133 |
| Маска FEC и преамбула | повторные массивы и PRBS7 | точный арифметический счёт, ограниченный кеш неизменяемых масок | те же кодовые длины и биты | меньше выделений |
| Сборщик блоков | состояние росло с числом блоков | ограничен восемью блоками, поздние повторы игнорируются | строгие кадры и счётчики без изменений | ограничена память |
| Подача v2-фрагмента | требовала бы повторной упаковки под v1 | отдельный проверяемый вход `offer_fragment` | v1 сохранён | меньше копий в горячем пути |
Классификация существенных проверок: **A, production safety**: длины PHY и
FEC, конечность текущих символов, метаданные блока, возраст, границы буфера,
CRC и отказ от старого кадра оставлены. **B, development assertion**:
сравнение SHA, точных битов и ожидаемого числа кадров живёт в тестах и
инструментах опыта. **C, offline diagnostic**: `cProfile`, `tracemalloc`,
JSON и исходные H.264 сравнения находятся в `tools/`, вне приёмника.
**D, duplicated check**: повторный проход по всему суффиксу убран;
проверка полей на публичном входе `offer_fragment` оставлена, хотя текущий
вызывающий код уже разобрал заголовок. **E, hot-loop check**: проверка
конечности вынесена из полного потока в границу текущего пакета, а
Numba-цикл Витерби не строит NumPy-массивы на каждом шаге.
| Существенная runtime-проверка | Место | Категория | Решение |
|---|---|---|---|
| PHY-длина, версия и CRC пакета | `video003_modem`, `packet` | A | Оставить до выдачи данных внешнему FEC |
| Длина и конечность мягких значений | `punctured_fec` | A/E | Оставить один раз перед решёткой |
| Диапазоны K/R, индекс, поток и длина символа | `video004_erasure` | A | Оставить перед накоплением блока |
| Время создания и возраст фрагмента | `video004_erasure`, `video_stream_transport` | A | Оставить; после 300 мс не выдавать |
| Предел буфера и повтор старого кадра | оба сборщика | A | Оставить с ограничением 8 блоков и 2 кадров |
| SHA эталона, точные биты и ожидаемые счётчики | `tests/`, `tools/video004_*` | B/C | Держать вне приёмника |
| Повторная проверка полей на публичном входе | `FrameReassembler.offer_fragment` | D → A | Оставить как защиту от другого вызывающего кода |
| Сканирование всего хвоста потока | прежний `receive_frame` | E | Заменить проверкой текущего кандидата и тела |
| ITEM | LOCATION | WHY SUSPICIOUS | ACTION |
|---|---|---|---|
| Сборка `np.concatenate` и SHA эталона | `tools/video004_benchmark.py` | массивы и знание исходного пакета | **KEEP** только в генераторе и проверке после CRC |
| RRC roundtrip | `protocol/video003_modem.py` | не является поточным IQ-приёмом | **KEEP** как отдельный контроль; вне горячего пути |
| Упаковка Lab028 | `protocol/packet_erasure_fec.py` | лишний заголовок и иной формат пакета | **KEEP** для старых лаб; новый видеоформат её не вызывает |
| `_linear_combine` | `protocol/packet_erasure_fec.py` | внутренний API другого модуля | **KEEP** проверенную арифметику; публичный общий API отложить |
| Преобразования в решётке | прежний `convolutional_fec.py` | массивы на каждом шаге | **REWRITE** в отдельном JIT-варианте; старый код сохранён как эталон |
| Большая функция `simulate` | `tools/video004_transport.py` | смешивает график передачи, потери и H.264 | **KEEP** как автономную модель с фиксированными входами; не импортируется рабочим приёмником |
Генерация сигнала, `np.concatenate`, SHA и сравнение с эталоном остались в
`tools/` как диагностика. В критическом пути нет
графиков, исторических Lab-проверок, verifier, подробного JSON и сравнения с
исходными пакетами. Нужные проверки повреждённых данных не удалялись.
После правок повторены модемные векторы, FEC/CRC, тесты стираний,
1440 моделей потерь, 48 случаев перемежения, 180 случаев пяти затравок и
оба замера производительности. Сравнение копии 1440 результатов до и после
аудита: показанные кадры, возраст и восстановления совпали; изменилось
лишь исправленное диагностическое поле числа неполных блоков в 480 строках.
`BEHAVIOR EQUIVALENT = YES` для строгого приёма и показа кадров.
`RUFF = NOT_RUN — TOOL NOT INSTALLED`.
`coverage`, `mypy`, `pyright`, `vulture`, `radon` также `NOT_RUN — TOOL NOT INSTALLED`.
Постоянные новые зависимости не устанавливались. Правило полного обзора после
крупного этапа сохраняется для CONTROL-001/002; мелким правкам достаточно
`pytest`, доступного `ruff` и `git diff --check`.
## Машинные результаты и повторение
Все файлы находятся в `data/processed/video004/`: канонический 32-пакетный
сигнал и его manifest, `benchmark_before.json`, `benchmark_after.json`,
`viterbi_benchmark.json`, 256-пакетный сигнал и `benchmark_256.json`,
40-пакетный `pipeline_benchmark.json`, `transport_results.json`,
`interleaving_results.json`, `normal_seed_sweep.json` и
`keyframe_recovery.json`. Эталонные пакеты не передаются декодеру.
Из корня VIDEO worktree, с доступной Numba 0.61.2:
```powershell
python -B -X utf8 -m tools.video004_benchmark --phase after --numba-packages C:\Users\user\Desktop\projects\SDR_Rover\.lab053_cpu_runtime\packages
python -B -X utf8 -m tools.video004_viterbi_benchmark --numba-packages C:\Users\user\Desktop\projects\SDR_Rover\.lab053_cpu_runtime\packages
python -B -X utf8 -m tools.video004_longrun --numba-packages C:\Users\user\Desktop\projects\SDR_Rover\.lab053_cpu_runtime\packages
python -B -X utf8 -m tools.video004_pipeline_benchmark --numba-packages C:\Users\user\Desktop\projects\SDR_Rover\.lab053_cpu_runtime\packages
python -B -X utf8 -m tools.video004_transport
python -B -X utf8 -m tools.video004_interleaving
python -B -X utf8 -m tools.video004_seed_sweep
python -B -X utf8 -m tools.video004_keyframe
python -B -X utf8 -m tools.video_gate --numba-packages C:\Users\user\Desktop\projects\SDR_Rover\.lab053_cpu_runtime\packages
```
Старый `benchmark_before.json` повторно не создаётся после правок: он
фиксирует исходный код VIDEO-003 на том же сигнале. На другой машине нужно
установить зависимости из `requirements-video.txt`; абсолютный путь локальных
пакетов Numba в командах относится только к этому ПК. Замер скорости следует
повторять на целевом Raspberry/Jetson отдельно.
## Обязательные поля результата
| Поле | Значение |
|---|---|
| `VIDEO-004 STATUS` | `PARTIAL` |
| `QPSK 500K FEC 2/3 INITIAL REALTIME FACTOR` | **5,520**, 32 пакета, доступный путь VIDEO-003 |
| `OPTIMIZED REALTIME FACTOR` | **0,130**, тот же 32-пакетный сигнал, прогретый путь |
| `REALTIME TARGET >=1.5X` | `PASS` для доступного пути; также пройден предпочтительный фактор ≤0,50 |
| `MAIN BOTTLENECK BEFORE OPTIMIZATION` | Витерби, 2,743/3,196 с по `cProfile` |
| `MAIN OPTIMIZATIONS` | JIT без `fastmath`; обработка только текущего пакета; ограниченная проверка конечности; кеш преамбулы и маски |
| `JIT FIRST RUN TIME` | 0,929 с для первого полного 32-пакетного прохода |
| `WARM RUN TIME` | медиана 0,0635 с на 32 пакета |
| `STEADY-STATE REALTIME FACTOR` | **0,130** |
| `VITERBI REALTIME FACTOR BEFORE / AFTER` | **4,360 / 0,055** |
| `AVAILABLE PIPELINE REALTIME FACTOR BEFORE / AFTER` | **4,446 / 0,131** на 40 пакетах с четырьмя стираниями |
| `VITERBI SPEEDUP` | **78,68×** |
| `FULL AVAILABLE PIPELINE SPEEDUP` | **33,90×** |
| `TRANSPORT ERASURE CODE` | Систематический GF(256), отдельная видеооболочка |
| `BEST DATA/PARITY CONFIGURATION` | 8+1 с дополнительным parity для опорного блока в диапазоне 1–5% данной модели; 8+2 одинаковая лучше при 10% |
| `PACKET LOSS 1%: DISPLAYED FPS / FRAME LOSS` | 8+1 с усилением: **9,85 кадр/с; 1,5%**, 591/600 |
| `PACKET LOSS 5%: DISPLAYED FPS / FRAME LOSS` | **8,30 кадр/с; 17,0%**, 498/600 |
| `PACKET LOSS 10%: DISPLAYED FPS / FRAME LOSS` | 8+1 с усилением: **5,50 кадр/с; 45,0%**; 8+2 одинаковая: 6,05 кадр/с; 39,5% |
| `BURST LOSS RESULT` | Глубина перемежения 2 непригодна при сроке 300 мс: даже при 0% потерь лишь 24/120 кадров для 8+1 |
| `KEYFRAME RECOVERY` | Две потери в одном блоке: 55/60 без усиления, 60/60 с усилением, в обоих окнах |
| `NET VIDEO RATE AFTER OUTER FEC` | 8+1 с усилением NORMAL: **424,922 / 425,142 кбит/с**; равномерная 8+2: около 409,4 / 409,8 кбит/с с учётом частичного блока |
| `NORMAL 640x360/10 SUPPORTED IN SOFTWARE MODEL` | `NO` для устойчивой работы на всём интервале 1–5% с инженерным запасом |
| `INNER FEC 2/3 VS 3/4 RESULT` | При заданных стираниях кадры одинаковы в сравниваемых схемах; более слабый внутренний код 3/4 не подтвердил общего выигрыша с учётом AWGN-контроля VIDEO-003 |
| `BEST NEXT RF CANDIDATE` | Отложен; диагностический программный кандидат: QPSK 500 ксимв/с, inner 2/3, outer 8+1 с усилением опорного блока, SHORT_GOP 400k |
| `WHY` | Порог CPU выполнен, но при 5% потерь не показаны 102/600 кадров, запас скорости около 1,04 и нет проверки полного IQ/RF-приёмника |
| `TESTS BEFORE / AFTER` | VIDEO-003: 123; до заключительного обзора VIDEO-004: 143; после: **145 passed** |
| `BEHAVIOR EQUIVALENT` | `YES` для битов, CRC, пакетов и кадров на фиксированных входах |
| `HOT PATH REMAINING BOTTLENECK` | По `cProfile`: Витерби 34,8 мс, поиск преамбулы 27,1 мс на 40 пакетов; отсутствуют timing recovery и IQ carrier tracking |
| `VIDEO-SPECIFIC GATE` | **PASS: 145 тестов**, сверка артефактов и повторный замер |
| `RF TX` | `0` |
| `SDR HARDWARE` | `NOT ACCESSED` |
| `COMMIT / PUSH` | `NOT PERFORMED / NOT PERFORMED` |
Отдельный общий `python tools/quick_gate.py` дал 1070 passed, 2 skipped и
1 failed: в этом worktree отсутствует исторический
`data/raw/lab064/tx_metadata.json`, нужный проверке Lab065. Целевой VIDEO-шлюз
прошёл: 145 passed. Это разные наборы проверок.
Следующий RF-опыт требует отдельного прямого разрешения. VIDEO-005 в этой
работе не начинается.