VIDEO-005: add full offline QPSK IQ video receiver

This commit is contained in:
LittleSam129
2026-09-17 13:34:58 +03:00
parent 95f664076a
commit 6df5f8aa39
18 changed files with 2651 additions and 13 deletions

187
docs/video/VIDEO_005.md Normal file
View File

@@ -0,0 +1,187 @@
# VIDEO-005 — приём IQ и проверка видеотракта
17 сентября 2026 года. Статус: **PARTIAL**. Это программный эксперимент в
`video-development`, без доступа к SDR и без передачи в эфир. Исходная точка —
`95f6640`; история Git сохранена. Измерения и SHA записаны в
`data/processed/video005/benchmark.json` (seed `20260917`).
## Рабочая цепочка
Приёмник `receive_iq_stream()` получает одномерный complex IQ и выбранный режим
радиоканала. Он не получает границы пакета, момент выборки, истинный CFO/фазу,
payload или CRC. Цепочка: цифровое смещение несущей → согласованный RRC → поиск
известной преамбулы по всему потоку → дробное уточнение момента → оценка CFO и
фазы по преамбуле → разрешение квадранта QPSK → блочное слежение за фазой тела
по решениям QPSK → PHY/внутренний FEC 2/3/CRC → фрагменты → внешний FEC 8+1
с усилением опорного блока → сборка H.264 → декодированные кадры. Эталонные
пакеты и пиксели используются после обработки только для подсчёта ошибок.
Использованы прежние QPSK mapping, RRC, FEC, Viterbi, CRC и видеотранспорт.
Новые поиск преамбулы, восстановление такта и несущей находятся в отдельных
модулях. Порог корреляции 0,5 отсекает наблюдавшиеся боковые пики PRBS7 около
0,4. Формат преамбулы не менялся. `CURRENT PREAMBLE INSUFFICIENT = NO` **только
для проверенной сетки**; это не доказательство устойчивости в эфире.
## Проверки сигнала
На одном полном пакете и в комбинированных трёхпакетных потоках проверены
дробные сдвиги `0`, `±0,1`, `±0,25`, `±0,4` символа; CFO `0`, `±1`, `±5`,
`±20`, `±100` Гц; начальная фаза `0`, `±15`, `±45`, `90`, `180` градусов.
Синтетическое фазовое отклонение тела от 0 до 0,35 рад уменьшалось решающим
QPSK-трекером. Все заявленные строгие проверки восстановили исходный пакет.
Дрейф частоты дискретизации не вводился: `SAMPLE CLOCK OFFSET = NOT MODELED IN
VIDEO-005`.
AWGN, 16 полных пакетов на точку, фиксированная затравка: при `Eb/N0` 0, 3, 6,
9 дБ получено соответственно **0/16, 1/16, 16/16, 16/16** точных CRC-пакетов;
неверных принятых пакетов — 0. Это наблюдение для выбранного потока, не кривая
ошибок или оценка вероятности для любого канала.
`LAB066-LIKE DIAGNOSTIC CHANNEL MODEL`: медиана измеренной неравномерности
полосы 2,870 дБ и средний показатель matched/residual 5,546 дБ. Суррогат
содержит двухотводный FIR с задержкой 8 отсчётов и вторым коэффициентом
0,176194, а также белый остаток. Он дал **30/32** точных CRC-пакетов, неверных
принятых — 0. Фильтр согласован по одному сводному показателю; модель не
воспроизводит распределение помех, меняющийся канал, аппаратные искажения или
саму запись Lab066. Это не измерение RF-приёма.
## Скорость и профиль
Канонический сигнал: 32 полных пакета, 3 886 112 комплексных отсчётов,
8 Мвыб/с, длительность **0,485764 с**, SHA-256 IQ
`d34c4fbba04708180f60ed759cef8bfa9f67827a57ea5ff201d94701bf32afd2`.
Передача 500 ксимв/с, QPSK, inner FEC 2/3. Первый запуск с JIT занял
**1,203 с**. Три прогретых замера `perf_counter()` — 0,281 / 0,299 / 0,268 с;
медиана **0,281 с**, фактор **0,578**, скорость **1,729×** реального времени.
Получено 32/32 пакета с точным SHA и CRC; целевой фактор ≤0,67 выполнен,
предпочтительный ≤0,50 — нет. Эти числа зависят от нагрузки данного ПК.
В таблице времена этапов взяты из последнего прогретого запуска (0,268 с),
проценты относятся к нему. Числа вызовов взяты из отдельного `cProfile`;
его времена не складываются с таблицей. Копии/буферы перечислены по коду,
точное число выделений NumPy не измерялось.
| Этап | Время, мс | Доля | Вызовы | Основные выделения/копии |
|---|---:|---:|---:|---|
| Смещение несущей | 15,3 | 5,7% | 1 | Периодический осциллятор и complex64 baseband, по 3,89 млн отсчётов |
| Согласованный RRC | 61,5 | 22,9% | 1 | Выход свёртки; внутренние FFT-буферы SciPy |
| Поиск преамбулы | 49,2 | 18,4% | 1 | Две корреляции/массивы энергии на прореженных фазах |
| Восстановление такта | 9,2 | 3,4% | 32 | Индексы и один complex64-массив символов на кандидат |
| Оценка CFO/фазы/квадранта | 12,5 | 4,7% | 32 | Корректор и исправленные символы на пакет |
| Проверка PHY-заголовка | 0,6 | 0,2% | 32 | Малый массив заголовка |
| Слежение за фазой QPSK | 49,9 | 18,6% | 32 | Одна копия символов пакета и векторы блоков по 64 |
| Строгий PHY/FEC/CRC | 53,0 | 19,8% | 32 | Мягкие решения, depuncturing и рабочие массивы Viterbi |
В `cProfile` (полный профилированный вызов 0,282 с): слежение QPSK 74,4 мс,
RRC 54,6 мс, поиск 46,6 мс, Viterbi `decode_soft` 28,6 мс, depuncturing
1,41 мс, QPSK soft decisions 0,48 мс и `parse_packet`/CRC 0,36 мс. На чистом
двухсекундном видео внешний FEC занял 1,10 мс, сборка фрагментов 0,86 мс,
декодирование H.264 15,7 мс. Диагностическая сверка пикселей заняла ещё
11,3 мс и исключена из времени рабочего пути. Доли мелких функций из
`cProfile` относятся к 0,282 с; вложенные функции нельзя суммировать.
## Видео 640×360/10
Первые 20 кадров исходного клипа (SHA-256
`651949922042c223375d40bf8929def97dbb62c6ade651d1f5b08eea47127d5f`)
закодированы PyAV/libx264 в H.264 SHORT_GOP 400k. Создано 117 PHY-пакетов;
длительность IQ 1,991 с. Потери и повреждение вносятся в **IQ-интервалы**, до
приёмника. Возраст отсчитывается от захвата кадра до завершения радиопакета.
| Сценарий | CRC-пакеты | Кадры/FPS | Потеря кадров | P50/P95/max, мс | Просрочено | Восстановлено FEC | Опорный кадр |
|---|---:|---:|---:|---:|---:|---:|---:|
| Без потерь | 117 | 20/20; 10,0 | 0% | 101/167/167 | 0 | 0 | — |
| 1% IQ-потерь (факт 1/117) | 116 | 20/20; 10,0 | 0% | 101/167/167 | 0 | 1 | — |
| 5% IQ-потерь (факт 6/117) | 111 | 17/20; 8,5 | 15% | 119/167/167 | 1 | 3 | — |
| Два фрагмента опорного кадра | 115 | 20/20; 10,0 | 0% | 101/167/167 | 0 | 2 | 0 мс |
| Три фрагмента опорного кадра | 114 | 15/20; 7,5 | 25% | 89/167/167 | 1 | 0 | 667 мс до следующего |
| Один испорченный IQ-пакет | 116 | 20/20; 10,0 | 0% | 101/167/167 | 0 | 1 | 0 мс |
Во всех сценариях нет неверного CRC-пакета, неверно собранного или декодированного
кадра и повторного показа старого кадра. После невосстановленной потери
опорного кадра H.264 ждёт следующий опорный кадр. Чистый путь **IQ →
декодированные кадры** имеет фактор **0,576**. Полный офлайн-прогон от чтения
и кодирования источника через формирование IQ и расчёт синтетического канала
до показа имеет фактор **2,153**; его нельзя представлять как скорость одного
приёмника. В этом прогоне 0,317 с заняли чтение/кодирование, 0,597 с — внешний
FEC и PHY передатчика, 0,923 с — формирование IQ, около 1,3 с — канал и около
1,1 с — радиоприём. Сверка эталона в оба фактора не входит.
## CODE QUALITY AUDIT
**BEFORE:** рабочие модули `video003_modem`, `video004_viterbi`,
`video004_erasure` и четыре новых модуля `video005_*` — около 968 строк и
34 функции после реализации; радиофактор до короткой чистки в одном прогоне
0,513. Главные затраты: RRC, поиск преамбулы, слежение за фазой и FEC.
| Проверка или подозрительное место | Где | Почему и категория | Решение |
|---|---|---|---|
| Длина IQ, конечность, параметры режима, границы интерполяции | `video005_receiver.py`, `video005_timing.py` | A: повреждённый вход и выход за буфер | KEEP |
| PHY-заголовок, CRC, возраст, индексы внешнего FEC | `video005_receiver.py`, старые модули | A: обязательные барьеры до видео | KEEP |
| Равенство длин строк матрицы и векторов внешнего FEC | `packet_erasure_fec.py` | A: обрезка `zip()` могла скрыть невозможное состояние | REWRITE с `strict=True` |
| Предварительный PHY-заголовок и повторная проверка в строгом декодере | `video005_receiver.py` | D: длина тела нужна до FEC, строгий разбор обязателен | KEEP |
| Проверка фазовых решений внутри тела | `video005_carrier.py` | E: решения QPSK нужны для слежения | KEEP, блоками по 64 символа |
| Эталонные SHA пакетов и пикселей | `video005_benchmark.py`, `video005_video.py` | C: диагностика не должна решать судьбу кадра | MOVE_TO_DIAGNOSTICS после обработки |
| `complex128` после интерполяции/фазовой коррекции | `video005_timing.py`, `video005_carrier.py` | Лишний объём временных векторов | REWRITE в `complex64` |
| Повторный H.264 decode эталона | `video005_video.py` | C: нужен для точной пиксельной сверки, не для показа | KEEP вне измеряемого пути |
| `LAB066-LIKE`, `cProfile`, графики и подробный JSON | `tools/` | C: офлайн-диагностика | KEEP вне receiver |
| Периодический осциллятор, RRC/FFT-буферы | `video005_receiver.py` | Выделения велики, но RRC быстрее основной цели | KEEP; дальнейшая оптимизация требует отдельного профиля |
**REMOVED / SIMPLIFIED:** временные векторы символов и фазовой коррекции стали
`complex64`; эталон перестал управлять H.264-декодированием; два `zip()`
внешнего FEC теперь проверяют равенство длин. **MOVED TO TESTS /
DIAGNOSTICS:** точные SHA и сверка пикселей остаются в проверочном коде и
выполняются после рабочего прохода. Неиспользуемых импортов Ruff не обнаружил;
декодер и передатчик не содержат Lab066-ветвей или глобального изменяемого
состояния. Кеш коэффициентов RRC неизменяемый. Большие функции сборки видео
остаются в офлайн-инструменте; их разделение сейчас не даст доказанного выигрыша.
**AFTER:** радиофактор 0,578 в финальном запуске; ускорение от изменения dtype
не заявляется из-за разброса времени между прогонами. Строгие пакеты, CRC,
числа кадров и сценарные выходы остались теми же. Прошли 178 тестов,
`ruff`, `pyright` (0 ошибок/предупреждений), `git diff --check` и
`video_gate.py`. Защита от неверных пакетов, индексов и просроченного видео
не удалялась.
Независимый запуск общего `tools/quick_gate.py` дал 1103 пройденных теста,
2 пропуска и один отказ Lab065: в этом worktree отсутствует игнорируемый
исторический вход `data/raw/lab064/tx_metadata.json`. Это ограничение общего
шлюза, а не воспроизведённый отказ VIDEO-005; целевой VIDEO gate прошёл.
## Решение и границы
Предварительная политика без автоматического переключения: GOOD — NORMAL
640×360/10 с 8+1 и усилением опорных блоков; DEGRADED — снизить FPS или
увеличить избыточность; POOR — ROBUST 320×240/5; CRITICAL — отключить видео
или резко снизить поток. Пороги потерь не закреплены: один короткий сценарий
5% дал 17/20 кадров, а суррогат Lab066 не заменяет эфир.
| Итоговое поле | Результат |
|---|---|
| VIDEO-005 STATUS | **PARTIAL**: радиопуть проходит цель, офлайн-генерация медленнее реального времени, есть потери кадров при 5% |
| RAW IQ RECEIVER | **PASS**: 32/32 точных пакета; фактор 0,578 ≤0,67 |
| PREAMBLE ACQUISITION | **PASS** в проверенных потоках |
| TIMING RECOVERY | **PASS** для 0 и ±0,1/0,25/0,4 символа |
| CARRIER FREQUENCY RECOVERY | **PASS** для 0 и ±1/5/20/100 Гц |
| CARRIER PHASE TRACKING | **PASS** в синтетическом тесте изменения фазы |
| QPSK QUADRANT RESOLUTION | **PASS** для 90°/180° и проверенных фаз |
| FEC 2/3 | **PASS**, битовые/пакетные выходы точны |
| OUTER ERASURE FEC | **PASS** при одном и двух стираниях; три не восстановлены |
| 640x360 / 10 FPS END-TO-END | **PARTIAL**: чисто 20/20, при 5% — 17/20; полный офлайн-фактор 2,153 |
| INPUT VIDEO FPS / DISPLAYED FPS / FRAME LOSS RATE | 10 / 10 / 0% чисто; 10 / 8,5 / 15% при 5% IQ-потерь |
| P50 / P95 / MAX FRAME AGE | 101 / 167 / 167 мс чисто; 119 / 167 / 167 мс при 5% |
| RADIO RECEIVER REALTIME FACTOR | 0,578; IQ → декодированные кадры 0,576 в отдельном видеопрогоне |
| END-TO-END VIDEO REALTIME FACTOR | 2,153 от источника через офлайн-канал до кадра |
| RAW IQ DURATION / JIT FIRST RUN TIME / WARM PROCESSING TIME | 0,485764 / 1,203 / медиана 0,281 с |
| MAIN HOT PATH | IQ → RRC → преамбула/такт/несущая → QPSK → FEC/CRC → фрагменты → erasure FEC → H.264 |
| AWGN RESULT | 0/16, 1/16, 16/16, 16/16 точных пакетов при 0/3/6/9 дБ |
| LAB066-LIKE MODEL RESULT | 30/32 точных пакета; диагностический суррогат |
| SAMPLE CLOCK OFFSET | **NOT MODELED IN VIDEO-005** |
| RUFF / PYRIGHT / VIDEO GATE | **PASS / PASS / PASS** |
| BEST NEXT RF CANDIDATE | QPSK 500 ксимв/с, inner 2/3, outer 8+1 с усилением опорного блока, H.264 SHORT_GOP 400k |
| WHY | 20/20 чисто и при одном стирании; нуждается в отдельной проверке устойчивости к 5% и реальным искажениям |
| RF TX / SDR HARDWARE | **0 / NOT ACCESSED** |
| COMMIT / PUSH | **NOT PERFORMED / NOT PERFORMED** |
STOP перед следующим этапом. RF-испытание требует отдельной постановки и
разрешения; VIDEO-006 не начата.