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