# 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 в этой работе не начинается.