Lab034: evaluate stale video frame dropping
@@ -188,3 +188,28 @@ git version 2.51.1.windows.1
|
|||||||
- Итоговое восстановление всех кадров после освобождения очереди не означает работу видео в реальном времени.
|
- Итоговое восстановление всех кадров после освобождения очереди не означает работу видео в реальном времени.
|
||||||
- Расчёты и графики выполнены с Matplotlib 3.11.1.
|
- Расчёты и графики выполнены с Matplotlib 3.11.1.
|
||||||
- Следующий этап посвящён отбрасыванию устаревших видеокадров.
|
- Следующий этап посвящён отбрасыванию устаревших видеокадров.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Запись 009
|
||||||
|
|
||||||
|
## Дата
|
||||||
|
|
||||||
|
3 августа 2026 года
|
||||||
|
|
||||||
|
## Тема
|
||||||
|
|
||||||
|
Завершение Lab034: реактивное отбрасывание устаревших видеокадров.
|
||||||
|
|
||||||
|
## Выполнено
|
||||||
|
|
||||||
|
- Сравнены непрерывные FEC-блоки и блоки, выровненные по составным кадрам.
|
||||||
|
- Выравнивание увеличивает видеопоток на 5,975 кбит/с, или на 2,633%.
|
||||||
|
- При 300 кбит/с удаление кадров не требуется.
|
||||||
|
- При 260 кбит/с ограничения 1000 и 1500 мс не срабатывают, а ограничение 500 мс уже чрезмерно.
|
||||||
|
- При 230 кбит/с реактивное удаление устаревших кадров уменьшает очередь, но резко снижает частоту обновления.
|
||||||
|
- Ограничение 500 мс оставляет только 3 из 63 кадров.
|
||||||
|
- Ограничение 1500 мс оставляет 27 из 63 кадров.
|
||||||
|
- Подтверждено, что короткая очередь сама по себе не означает свежее изображение.
|
||||||
|
- Текущая реактивная политика не принята.
|
||||||
|
- Следующий этап посвящён упреждающему допуску кадров и обслуживанию видео целыми кадрами.
|
||||||
|
|||||||
BIN
data/processed/lab034/lab034_control_delay.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
16
data/processed/lab034/lab034_control_metrics.csv
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
channel_kbps,policy,control_mean_age_ms,control_p95_age_ms,control_max_age_ms,control_deadline_misses,control_max_receive_gap_ms,control_replaced,emergency_total_delay_ms,emergency_deadline_met,emergency_blocker_class,emergency_blocker_size_bytes,emergency_blocker_sequence,emergency_blocking_delay_ms,telemetry_mean_age_ms,telemetry_p95_age_ms,telemetry_max_age_ms,telemetry_deadline_misses,telemetry_replaced
|
||||||
|
300.0,continuous_no_drop,9.439928685896334,17.46633333333346,17.813000000000745,0,65.7596666666671,0,1.7066666666671892,True,none,0,-1,0.0,12.424120192306697,20.10688333333437,20.37300000000064,0,0
|
||||||
|
300.0,aligned_no_drop,10.09563301281934,17.46633333333314,17.78700000000022,0,65.7596666666671,0,1.7066666666671892,True,none,0,-1,0.0,12.858206730768112,19.573666666667933,20.160000000000622,0,0
|
||||||
|
300.0,aligned_1500ms,10.09563301281934,17.46633333333314,17.78700000000022,0,65.7596666666671,0,1.7066666666671892,True,none,0,-1,0.0,12.858206730768112,19.573666666667933,20.160000000000622,0,0
|
||||||
|
300.0,aligned_1000ms,10.09563301281934,17.46633333333314,17.78700000000022,0,65.7596666666671,0,1.7066666666671892,True,none,0,-1,0.0,12.858206730768112,19.573666666667933,20.160000000000622,0,0
|
||||||
|
300.0,aligned_500ms,10.09563301281934,17.46633333333314,17.78700000000022,0,65.7596666666671,0,1.7066666666671892,True,none,0,-1,0.0,12.858206730768112,19.573666666667933,20.160000000000622,0,0
|
||||||
|
260.0,continuous_no_drop,10.282265902386632,18.753596153843198,20.66700000000843,0,63.476923076922546,0,1.969230769230279,True,none,0,-1,0.0,13.863505177534366,22.44779230770204,23.620846153853847,0,0
|
||||||
|
260.0,aligned_no_drop,10.671617788489147,18.907692307691043,20.492307692309808,0,67.81538461538617,0,1.969230769230279,True,none,0,-1,0.0,13.490528846181197,21.89960384616611,23.323076923056796,0,0
|
||||||
|
260.0,aligned_1500ms,10.671617788489147,18.907692307691043,20.492307692309808,0,67.81538461538617,0,1.969230769230279,True,none,0,-1,0.0,13.490528846181197,21.89960384616611,23.323076923056796,0,0
|
||||||
|
260.0,aligned_1000ms,10.671617788489147,18.907692307691043,20.492307692309808,0,67.81538461538617,0,1.969230769230279,True,none,0,-1,0.0,13.490528846181197,21.89960384616611,23.323076923056796,0,0
|
||||||
|
260.0,aligned_500ms,10.71385151629976,18.907692307691043,20.492307692309808,0,67.81538461538617,0,1.969230769230279,True,none,0,-1,0.0,13.61641642014566,22.039999999996684,23.323076923056796,0,0
|
||||||
|
230.0,continuous_no_drop,12.250334448143445,21.84782608690261,23.33913043477409,0,69.00869565217427,0,11.547826086889756,True,video,608,458,9.32173913036749,15.51421404680579,25.111304347813142,26.678260869555714,0,0
|
||||||
|
230.0,aligned_no_drop,12.729598662164767,22.499999999942013,24.556521739064863,0,69.00869565217427,0,22.330434782542596,True,video,608,463,20.10434782602033,15.594983277549522,25.377391304327652,27.895652173848262,0,0
|
||||||
|
230.0,aligned_1500ms,12.613043478216982,22.460869565154475,24.556521739064863,0,69.00869565217427,0,22.330434782542596,True,video,608,463,20.10434782602033,15.613377926377664,25.426086956510073,27.895652173848262,0,0
|
||||||
|
230.0,aligned_1000ms,12.76596989961911,22.578260869525746,23.356521739078318,0,69.00869565217427,0,11.652173912974462,True,video,608,484,9.426086956452195,15.511371237411904,25.37739130434555,26.608695652086567,0,0
|
||||||
|
230.0,aligned_500ms,12.377759197289913,22.230434782573028,23.339130434790967,0,69.00869565217427,0,3.6869565216850475,True,video,608,511,1.4608695651627812,15.598996655484031,24.913043478221116,26.678260869573478,0,0
|
||||||
|
BIN
data/processed/lab034/lab034_fec_alignment_overhead.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
data/processed/lab034/lab034_frame_outcomes.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
data/processed/lab034/lab034_image_age.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
data/processed/lab034/lab034_policy_comparison.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
data/processed/lab034/lab034_queue_size.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
98
data/processed/lab034/lab034_report.txt
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
Lab034. Ограничение возраста видеоданных и отбрасывание устаревших кадров
|
||||||
|
|
||||||
|
1. Исходное состояние
|
||||||
|
- Commit Lab033: 21751f41dd0af022fc1f935c67e795e3ae4257c5.
|
||||||
|
- Перед Lab034 рабочее дерево было чистым; main опережала origin/main на один commit.
|
||||||
|
- Lab028-Lab033 и их результаты не изменяются.
|
||||||
|
|
||||||
|
2. Формирование FEC-блоков
|
||||||
|
layout | source | parity | blocks | partial | cross-frame | frames/block mean/max | before/after Lab033 kbps
|
||||||
|
Непрерывные | 862 | 216 | 72 | 1 | 58 | 1.806/2 | 226.951/240.240
|
||||||
|
По кадрам | 862 | 251 | 125 | 62 | 0 | 1.000/1 | 232.926/246.647
|
||||||
|
- Дополнительная избыточность выравнивания: 5.975 кбит/с (2.633%).
|
||||||
|
- Средняя эквивалентная группа кадра: непрерывные 17.111 пакета / 263.968 мс; выровненные 17.667 пакета / 271.007 мс при 300 кбит/с.
|
||||||
|
|
||||||
|
3. Пятнадцать сочетаний
|
||||||
|
speed | policy | offered/capacity | published/drop/partial | update fps | age P95 ms | control P95 ms | emergency ms | queue max/end | drain s | wasted bytes
|
||||||
|
300 | Непрерывные, без отбрасывания | 0.8607 | 63/0/0 | 2.986 | 593.333 | 17.466 | 1.707 | 22/15 | 0.231867 | 0
|
||||||
|
300 | По кадрам, без отбрасывания | 0.8821 | 63/0/0 | 2.986 | 590.000 | 17.466 | 1.707 | 21/13 | 0.199440 | 0
|
||||||
|
300 | По кадрам, 1500 мс | 0.8821 | 63/0/0 | 2.986 | 590.000 | 17.466 | 1.707 | 21/13 | 0.199440 | 0
|
||||||
|
300 | По кадрам, 1000 мс | 0.8821 | 63/0/0 | 2.986 | 590.000 | 17.466 | 1.707 | 21/13 | 0.199440 | 0
|
||||||
|
300 | По кадрам, 500 мс | 0.8821 | 63/0/0 | 2.986 | 590.000 | 17.466 | 1.707 | 21/13 | 0.199440 | 0
|
||||||
|
260 | Непрерывные, без отбрасывания | 0.9931 | 63/0/0 | 2.937 | 690.000 | 18.754 | 1.969 | 28/23 | 0.399200 | 0
|
||||||
|
260 | По кадрам, без отбрасывания | 1.0178 | 63/0/0 | 2.937 | 773.833 | 18.908 | 1.969 | 37/30 | 0.539293 | 0
|
||||||
|
260 | По кадрам, 1500 мс | 1.0178 | 63/0/0 | 2.937 | 773.833 | 18.908 | 1.969 | 37/30 | 0.539293 | 0
|
||||||
|
260 | По кадрам, 1000 мс | 1.0178 | 63/0/0 | 2.937 | 773.833 | 18.908 | 1.969 | 37/30 | 0.539293 | 0
|
||||||
|
260 | По кадрам, 500 мс | 1.0178 | 56/7/7 | 2.648 | 1053.833 | 18.908 | 1.969 | 31/24 | 0.403939 | 69597
|
||||||
|
230 | Непрерывные, без отбрасывания | 1.1227 | 63/0/0 | 2.697 | 2443.833 | 21.848 | 11.548 | 132/127 | 2.547420 | 0
|
||||||
|
230 | По кадрам, без отбрасывания | 1.1505 | 63/0/0 | 2.600 | 2913.333 | 22.500 | 22.330 | 160/155 | 3.125855 | 0
|
||||||
|
230 | По кадрам, 1500 мс | 1.1505 | 27/36/36 | 1.300 | 11064.833 | 22.461 | 22.330 | 87/81 | 1.418064 | 321929
|
||||||
|
230 | По кадрам, 1000 мс | 1.1505 | 14/49/49 | 0.674 | 11731.500 | 22.578 | 11.652 | 63/53 | 0.904881 | 434885
|
||||||
|
230 | По кадрам, 500 мс | 1.1505 | 3/60/60 | 0.144 | 11731.500 | 22.230 | 3.687 | 32/27 | 0.419072 | 532167
|
||||||
|
|
||||||
|
4. Практический смысл ограничений возраста
|
||||||
|
- При 300 кбит/с очередь обслуживается достаточно быстро: ограничения 500, 1000 и 1500 мс не срабатывают и не дают выигрыша.
|
||||||
|
- При 260 кбит/с пределы 1000 и 1500 мс также не срабатывают; предел 500 мс публикует 56 из 63 кадров и отбрасывает 7, поэтому он уже чрезмерно жёсткий для части кадров.
|
||||||
|
- При 230 кбит/с предел 1500 мс сохраняет 27 кадров, 1000 мс — 14, 500 мс — только 3; более строгий предел лучше ограничивает очередь, но резко уменьшает частоту обновления.
|
||||||
|
- В исследованной перегрузке 1500 мс является наиболее мягким из конечных ограничений; 1000 мс сильнее улучшает свежесть ценой обновлений, а 500 мс чрезмерно жёсток.
|
||||||
|
|
||||||
|
5. Интерпретация
|
||||||
|
- Произвольное удаление символа из непрерывного FEC-блока опасно: блок может содержать соседние кадры, и нехватка символов затрагивает их совместное декодирование.
|
||||||
|
- Выравнивание изолирует кадр в собственных блоках, поэтому все ожидающие символы устаревшего кадра можно удалить без повреждения соседей.
|
||||||
|
- Цена изоляции — 5.975 кбит/с дополнительного внешнего потока из-за большего числа неполных блоков.
|
||||||
|
- Полная доставка после освобождения очереди не является целью операторского видео: поздний полный кадр уже не описывает текущее состояние ровера.
|
||||||
|
- При перегрузке отбрасывание старого кадра освобождает ресурс для более свежего изображения и не затрагивает команды.
|
||||||
|
- При 230 кбит/с выравнивание меняет фазу активного видеопакета около 10,0 с; фактическая задержка аварийной команды может отличаться от Lab033, но отбрасывание её не увеличивает, а непереключаемая верхняя граница остаётся прежней, поскольку максимальный размер пакета не изменён.
|
||||||
|
- Слишком малый возраст может отменять кадры быстрее их полной передачи и снижать фактическую частоту обновления.
|
||||||
|
- Поэтому возраст изображения оценивается вместе с частотой обновления: хорошее значение одного показателя не гарантирует полезный видеопоток.
|
||||||
|
- Замена команд допустима только для текущего состояния (скорость, поворот, торможение, телеметрия). Дискретные события заменять нельзя; в Lab034 они не моделируются.
|
||||||
|
|
||||||
|
6. Допущения
|
||||||
|
- Один общий абстрактный ресурс, одна скорость для обоих направлений, без времени переключения и без выбора физического duplex/TDD/FDD.
|
||||||
|
- Ошибки и помехи отсутствуют; начатый пакет не прерывается; форматы Lab028, Lab030 и Lab033 неизменны.
|
||||||
|
- Возраст считается от формирования составного кадра по generation_time_us; служебные frame_id и состояние отмены не сериализуются.
|
||||||
|
- Видеокадр с удалёнными пакетами очищается в приёмнике и никогда не публикуется.
|
||||||
|
- Допустимые возраста 500, 1000 и 1500 мс заданы явно и автоматически не выбираются.
|
||||||
|
|
||||||
|
7. Функциональные проверки
|
||||||
|
- PASS 01_continuous_matches_lab033: PASS
|
||||||
|
- PASS 02_aligned_blocks_one_frame: PASS
|
||||||
|
- PASS 03_aligned_no_drop_recovers_all: PASS
|
||||||
|
- PASS 04_whole_frame_drop_isolated: PASS
|
||||||
|
- PASS 05_only_waiting_video_removed: PASS
|
||||||
|
- PASS 06_active_packet_not_preempted: PASS
|
||||||
|
- PASS 07_partial_cancel_not_published: PASS
|
||||||
|
- PASS 08_age_uses_frame_generation: PASS
|
||||||
|
- PASS 09_receiver_cleans_stale_state: PASS
|
||||||
|
- PASS 10_emergency_never_deleted: PASS
|
||||||
|
- PASS 11_high_priority_before_video: PASS
|
||||||
|
- PASS 12_230kbps_finite_age_bounds_queue: PASS
|
||||||
|
- PASS 13_packet_accounting: PASS
|
||||||
|
- PASS 14_byte_accounting: PASS
|
||||||
|
- PASS 15_all_crc_layers_pass: PASS
|
||||||
|
- PASS 16_incomplete_frame_never_published: PASS
|
||||||
|
- PASS 17_reproducible: PASS
|
||||||
|
- PASS 18_drop_does_not_worsen_emergency: PASS
|
||||||
|
|
||||||
|
8. Созданные файлы
|
||||||
|
- protocol/video_age_policy.py
|
||||||
|
- tests/lab034_stale_video_drop.py
|
||||||
|
- data/processed/lab034/lab034_summary.csv
|
||||||
|
- data/processed/lab034/lab034_video_metrics.csv
|
||||||
|
- data/processed/lab034/lab034_control_metrics.csv
|
||||||
|
- data/processed/lab034/lab034_report.txt
|
||||||
|
- data/processed/lab034/lab034_image_age.png
|
||||||
|
- data/processed/lab034/lab034_frame_outcomes.png
|
||||||
|
- data/processed/lab034/lab034_queue_size.png
|
||||||
|
- data/processed/lab034/lab034_control_delay.png
|
||||||
|
- data/processed/lab034/lab034_update_rate.png
|
||||||
|
- data/processed/lab034/lab034_fec_alignment_overhead.png
|
||||||
|
- data/processed/lab034/lab034_policy_comparison.png
|
||||||
|
|
||||||
|
9. Итоговый Git status
|
||||||
|
- Lab034 не добавлена в индекс и не закоммичена.
|
||||||
|
|
||||||
|
## main...origin/main [ahead 1]
|
||||||
|
?? data/processed/lab034/
|
||||||
|
?? protocol/video_age_policy.py
|
||||||
|
?? tests/lab034_stale_video_drop.py
|
||||||
16
data/processed/lab034/lab034_summary.csv
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
channel_kbps,policy,layout,max_video_age_ms,offered_load_kbps,offered_to_capacity_ratio,transmitted_packets,transmitted_bytes,dropped_packets,dropped_bytes,wasted_transmitted_bytes,mean_queue_packets,max_queue_packets,mean_queue_bytes,max_queue_bytes,queue_at_source_end_packets,additional_drain_seconds,channel_utilization_fraction
|
||||||
|
300.0,continuous_no_drop,continuous,-1,258.21380417335473,0.8607126805778491,1703,670280,0,0,0,8.05016637239154,22,4452.387224542477,12221,15,0.23186699999999405,0.8512086423304386
|
||||||
|
300.0,aligned_no_drop,aligned,-1,264.62022471910115,0.8820674157303372,1738,686910,0,0,0,8.518393226324118,21,4685.69717834665,11415,13,0.1994403333333281,0.8724635473515175
|
||||||
|
300.0,aligned_1500ms,aligned,1500,264.62022471910115,0.8820674157303372,1738,686910,0,0,0,8.518393226324118,21,4685.69717834665,11415,13,0.1994403333333281,0.8724635473515175
|
||||||
|
300.0,aligned_1000ms,aligned,1000,264.62022471910115,0.8820674157303372,1738,686910,0,0,0,8.518393226324118,21,4685.69717834665,11415,13,0.1994403333333281,0.8724635473515175
|
||||||
|
300.0,aligned_500ms,aligned,500,264.62022471910115,0.8820674157303372,1738,686910,0,0,0,8.518393226324118,21,4685.69717834665,11415,13,0.1994403333333281,0.8724635473515175
|
||||||
|
260.0,continuous_no_drop,continuous,-1,258.21380417335473,0.9931300160513643,1703,670280,0,0,0,10.783079034450305,28,6010.224630522928,16384,23,0.39920033333350347,0.9743990170589202
|
||||||
|
260.0,aligned_no_drop,aligned,-1,264.62022471910115,1.017770095073466,1738,686910,0,0,0,13.695290291396285,37,7655.6639836816,21131,30,0.5392926410258561,0.991800947030499
|
||||||
|
260.0,aligned_1500ms,aligned,1500,264.62022471910115,1.017770095073466,1738,686910,0,0,0,13.695290291396285,37,7655.6639836816,21131,30,0.5392926410258561,0.991800947030499
|
||||||
|
260.0,aligned_1000ms,aligned,1000,264.62022471910115,1.017770095073466,1738,686910,0,0,0,13.695290291396285,37,7655.6639836816,21131,30,0.5392926410258561,0.991800947030499
|
||||||
|
260.0,aligned_500ms,aligned,500,264.62022471910115,1.017770095073466,1730,682511,8,4399,69597,13.261018996174682,31,7402.747049027978,16992,24,0.4039387948720048,0.991800947030499
|
||||||
|
230.0,continuous_no_drop,continuous,-1,258.21380417335473,1.1226687137971945,1703,670280,0,0,0,59.749842696627866,132,34307.118394888734,75969,127,2.547420289855012,1.0
|
||||||
|
230.0,aligned_no_drop,aligned,-1,264.62022471910115,1.1505227161700051,1738,686910,0,0,0,76.17493190801534,160,43586.68565075366,92568,155,3.1258550724636294,1.0
|
||||||
|
230.0,aligned_1500ms,aligned,1500,264.62022471910115,1.1505227161700051,1641,637811,97,49099,321929,59.44779896084508,87,33911.67327808277,50035,81,1.4180637681158217,1.0
|
||||||
|
230.0,aligned_1000ms,aligned,1000,264.62022471910115,1.1505227161700051,1609,623057,129,63853,434885,42.60156656500422,63,24223.970784957004,35495,53,0.9048811594201744,1.0
|
||||||
|
230.0,aligned_500ms,aligned,500,264.62022471910115,1.1505227161700051,1580,609090,158,77820,532167,20.03526158908224,32,11270.42233914812,18208,27,0.41907246376802476,0.9999999999999993
|
||||||
|
BIN
data/processed/lab034/lab034_update_rate.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
16
data/processed/lab034/lab034_video_metrics.csv
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
channel_kbps,policy,created_frames,published_frames,intentionally_dropped_frames,partially_transmitted_cancelled_frames,published_after_deadline_frames,published_fraction,actual_update_fps,mean_publication_delay_ms,p95_publication_delay_ms,max_publication_delay_ms,mean_display_age_ms,p95_display_age_ms,max_display_age_ms,display_age_over_500ms_fraction,display_age_over_1000ms_fraction,mean_no_update_duration_ms,p95_no_update_duration_ms,max_no_update_duration_ms,mean_missing_run_frames,p95_missing_run_frames,max_missing_run_frames,delivered_useful_video_kbps,wasted_transmitted_video_bytes,receiver_cleared_frames
|
||||||
|
300.0,continuous_no_drop,63,63,0,0,0,1.0,2.985553772070626,276.3369365079338,322.0720000000001,330.77299999999,437.2938723131218,593.3333333333333,660.0000000000001,0.3142444658325313,0.0,329.6296296296296,388.9899000000025,397.1729999999969,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
300.0,aligned_no_drop,63,63,0,0,0,1.0,2.985553772070626,272.2167195767169,293.13066666666225,301.84000000000003,433.2836060314408,589.9999999999999,623.3333333333348,0.30221366698748797,0.0,329.6296296296296,355.05666666666684,406.506333333331,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
300.0,aligned_1500ms,63,63,0,0,0,1.0,2.985553772070626,272.2167195767169,293.13066666666225,301.84000000000003,433.2836060314408,589.9999999999999,623.3333333333348,0.30221366698748797,0.0,329.6296296296296,355.05666666666684,406.506333333331,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
300.0,aligned_1000ms,63,63,0,0,0,1.0,2.985553772070626,272.2167195767169,293.13066666666225,301.84000000000003,433.2836060314408,589.9999999999999,623.3333333333348,0.30221366698748797,0.0,329.6296296296296,355.05666666666684,406.506333333331,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
300.0,aligned_500ms,63,63,0,0,0,1.0,2.985553772070626,272.2167195767169,293.13066666666225,301.84000000000003,433.2836060314408,589.9999999999999,623.3333333333348,0.30221366698748797,0.0,329.6296296296296,355.05666666666684,406.506333333331,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
260.0,continuous_no_drop,63,63,0,0,0,1.0,2.937399678972713,351.2176776557015,445.84648717956554,459.55930769245157,509.63907603464884,690.0000000000005,783.3333333333314,0.5312800769971127,0.0,334.9462365591397,401.4781615384583,411.91761538461515,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
260.0,aligned_no_drop,63,63,0,0,0,1.0,2.937399678972713,393.259570207603,579.4598205129968,620.584948718161,546.5335258261151,773.833333333333,943.3333333333316,0.62223291626564,0.0,334.9462365591397,363.58000000001095,417.7637692307723,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
260.0,aligned_1500ms,63,63,0,0,0,1.0,2.937399678972713,393.259570207603,579.4598205129968,620.584948718161,546.5335258261151,773.833333333333,943.3333333333316,0.62223291626564,0.0,334.9462365591397,363.58000000001095,417.7637692307723,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
260.0,aligned_1000ms,63,63,0,0,0,1.0,2.937399678972713,393.259570207603,579.4598205129968,620.584948718161,546.5335258261151,773.833333333333,943.3333333333316,0.62223291626564,0.0,334.9462365591397,363.58000000001095,417.7637692307723,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
260.0,aligned_500ms,63,56,7,7,0,0.8888888888888888,2.648475120385233,367.5917000915918,493.7670000001404,498.6567435899403,584.550850176452,1053.8333333333312,1769.9999999999995,0.6212704523580366,0.060153994225216556,370.8333333333333,664.0000000000219,1270.1227435895887,1.4,2.5999999999999996,3,139.5205136436597,69597,7
|
||||||
|
230.0,continuous_no_drop,63,63,0,0,0,1.0,2.696629213483146,1445.9947550034299,2505.9953623187953,2583.976811594141,1464.8845043310876,2443.8333333333317,2740.000000000002,0.9716073147256978,0.78055822906641,364.32748538011697,437.67652173912944,464.34782608695,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
230.0,aligned_no_drop,63,63,0,0,0,1.0,2.6003210272873196,1777.4951000689596,3067.511884057848,3204.707246376671,1723.7872954764196,2913.333333333334,3206.666666666667,0.9735322425409048,0.8267564966313763,377.57575757575756,412.5426086956473,424.00000000000125,0.0,0.0,0,157.76975922953451,0,0
|
||||||
|
230.0,aligned_1500ms,63,27,36,36,0,0.42857142857142855,1.3001605136436598,1010.3600644122084,1414.757101449207,1473.0202898550094,4028.4071222329167,11064.833333333332,12103.333333333334,0.9735322425409048,0.8267564966313763,741.6666666666666,416.659130434783,10626.97971014499,36.0,36.0,36,66.39987158908507,321929,36
|
||||||
|
230.0,aligned_1000ms,63,14,49,49,0,0.2222222222222222,0.6741573033707865,734.7155279503047,977.28927536231,995.617391304334,4667.483156881616,11731.499999999998,12770.0,0.9735322425409048,0.8233878729547642,1384.4444444444443,6308.219130434754,11828.788405797173,24.5,36.65,38,34.951781701444624,434885,49
|
||||||
|
230.0,aligned_500ms,63,3,60,60,0,0.047619047619047616,0.14446227929373998,429.5149758453909,457.5026086956464,460.9971014492756,5508.034969521976,11731.499999999998,12770.0,0.9696823869104908,0.9076034648700674,5191.666666666666,11634.280579710185,12340.614492753682,30.0,37.2,38,7.567126805778492,532167,60
|
||||||
|
224
protocol/video_age_policy.py
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
"""Frame-aware stale-video policy for the Lab034 shared link queue.
|
||||||
|
|
||||||
|
The scheduler remains strict-priority, state-replacing, and non-preemptive.
|
||||||
|
Unlike Lab033, packet availability is separate from the timestamp stored in
|
||||||
|
the common header. Every aligned video packet therefore carries the source
|
||||||
|
composite-frame generation time while FEC construction metadata remains local
|
||||||
|
to the scheduler.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from protocol.link_packet import HEADER_SIZE, LinkPacket, TrafficClass, encode_link_packet
|
||||||
|
from protocol.priority_scheduler import transmission_duration_seconds
|
||||||
|
|
||||||
|
|
||||||
|
TIME_EPSILON_SECONDS = 1e-12
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AgePolicyPacket:
|
||||||
|
"""A common packet plus non-serialized availability/frame metadata."""
|
||||||
|
|
||||||
|
packet: LinkPacket
|
||||||
|
arrival_order: int
|
||||||
|
available_time_us: int
|
||||||
|
composite_frame_id: int | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_time_seconds(self) -> float:
|
||||||
|
return self.available_time_us / 1_000_000.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frame_generation_seconds(self) -> float:
|
||||||
|
return self.packet.generation_time_us / 1_000_000.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wire_size_bytes(self) -> int:
|
||||||
|
return HEADER_SIZE + len(self.packet.payload)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class StateReplacement:
|
||||||
|
removed: AgePolicyPacket
|
||||||
|
replacement: AgePolicyPacket
|
||||||
|
time_seconds: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DroppedVideoPacket:
|
||||||
|
item: AgePolicyPacket
|
||||||
|
drop_time_seconds: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AgeScheduledPacket:
|
||||||
|
item: AgePolicyPacket
|
||||||
|
start_seconds: float
|
||||||
|
end_seconds: float
|
||||||
|
blocked_by: AgePolicyPacket | None
|
||||||
|
blocking_delay_seconds: float
|
||||||
|
wire_packet: bytes
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AgeScheduleResult:
|
||||||
|
channel_bitrate_bps: float
|
||||||
|
max_video_age_seconds: float | None
|
||||||
|
transmitted: tuple[AgeScheduledPacket, ...]
|
||||||
|
dropped_video: tuple[DroppedVideoPacket, ...]
|
||||||
|
replacements: tuple[StateReplacement, ...]
|
||||||
|
|
||||||
|
|
||||||
|
def _enqueue(
|
||||||
|
ready: list[AgePolicyPacket],
|
||||||
|
item: AgePolicyPacket,
|
||||||
|
replacements: list[StateReplacement],
|
||||||
|
) -> None:
|
||||||
|
if item.packet.traffic_class in (TrafficClass.CONTROL, TrafficClass.TELEMETRY):
|
||||||
|
retained = []
|
||||||
|
for old in ready:
|
||||||
|
if (
|
||||||
|
old.packet.traffic_class == item.packet.traffic_class
|
||||||
|
and old.packet.stream_id == item.packet.stream_id
|
||||||
|
):
|
||||||
|
replacements.append(
|
||||||
|
StateReplacement(old, item, item.available_time_seconds)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
retained.append(old)
|
||||||
|
ready[:] = retained
|
||||||
|
ready.append(item)
|
||||||
|
|
||||||
|
|
||||||
|
def schedule_with_video_age(
|
||||||
|
packets: Iterable[AgePolicyPacket],
|
||||||
|
channel_bitrate_bps: float,
|
||||||
|
max_video_age_seconds: float | None,
|
||||||
|
) -> AgeScheduleResult:
|
||||||
|
"""Run a strict-priority non-preemptive scheduler with frame drops.
|
||||||
|
|
||||||
|
At every packet-selection instant all queued packets belonging to a video
|
||||||
|
frame older than ``max_video_age_seconds`` are removed together. An active
|
||||||
|
packet has already left the queue and is never interrupted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if channel_bitrate_bps <= 0.0:
|
||||||
|
raise ValueError("channel bitrate must be positive")
|
||||||
|
if max_video_age_seconds is not None and max_video_age_seconds <= 0.0:
|
||||||
|
raise ValueError("maximum video age must be positive")
|
||||||
|
arrivals = sorted(
|
||||||
|
tuple(packets),
|
||||||
|
key=lambda item: (item.available_time_seconds, item.arrival_order),
|
||||||
|
)
|
||||||
|
if len({item.arrival_order for item in arrivals}) != len(arrivals):
|
||||||
|
raise ValueError("arrival_order values must be unique")
|
||||||
|
for item in arrivals:
|
||||||
|
if item.packet.traffic_class is TrafficClass.VIDEO:
|
||||||
|
if item.composite_frame_id is None:
|
||||||
|
raise ValueError("video packet lacks composite_frame_id")
|
||||||
|
elif item.composite_frame_id is not None:
|
||||||
|
raise ValueError("non-video packet has composite_frame_id")
|
||||||
|
|
||||||
|
ready: list[AgePolicyPacket] = []
|
||||||
|
transmitted: list[AgeScheduledPacket] = []
|
||||||
|
dropped: list[DroppedVideoPacket] = []
|
||||||
|
replacements: list[StateReplacement] = []
|
||||||
|
blocker_by_order: dict[int, tuple[AgePolicyPacket, float]] = {}
|
||||||
|
dropped_frames: set[int] = set()
|
||||||
|
arrival_index = 0
|
||||||
|
cursor = 0.0
|
||||||
|
|
||||||
|
def admit_until(limit: float, active: AgePolicyPacket | None = None, active_end: float = 0.0) -> None:
|
||||||
|
nonlocal arrival_index
|
||||||
|
while (
|
||||||
|
arrival_index < len(arrivals)
|
||||||
|
and arrivals[arrival_index].available_time_seconds
|
||||||
|
<= limit + TIME_EPSILON_SECONDS
|
||||||
|
):
|
||||||
|
item = arrivals[arrival_index]
|
||||||
|
arrival_index += 1
|
||||||
|
if (
|
||||||
|
active is not None
|
||||||
|
and item.available_time_seconds > cursor + TIME_EPSILON_SECONDS
|
||||||
|
):
|
||||||
|
blocker_by_order[item.arrival_order] = (
|
||||||
|
active,
|
||||||
|
max(0.0, active_end - item.available_time_seconds),
|
||||||
|
)
|
||||||
|
if item.composite_frame_id in dropped_frames:
|
||||||
|
dropped.append(DroppedVideoPacket(item, limit))
|
||||||
|
else:
|
||||||
|
_enqueue(ready, item, replacements)
|
||||||
|
|
||||||
|
def expire_video(now: float) -> None:
|
||||||
|
if max_video_age_seconds is None:
|
||||||
|
return
|
||||||
|
expired_frames = {
|
||||||
|
item.composite_frame_id
|
||||||
|
for item in ready
|
||||||
|
if (
|
||||||
|
item.packet.traffic_class is TrafficClass.VIDEO
|
||||||
|
and now - item.frame_generation_seconds
|
||||||
|
> max_video_age_seconds + TIME_EPSILON_SECONDS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if not expired_frames:
|
||||||
|
return
|
||||||
|
retained = []
|
||||||
|
for item in ready:
|
||||||
|
if item.composite_frame_id in expired_frames:
|
||||||
|
dropped.append(DroppedVideoPacket(item, now))
|
||||||
|
assert item.composite_frame_id is not None
|
||||||
|
dropped_frames.add(item.composite_frame_id)
|
||||||
|
else:
|
||||||
|
retained.append(item)
|
||||||
|
ready[:] = retained
|
||||||
|
|
||||||
|
while arrival_index < len(arrivals) or ready:
|
||||||
|
if not ready:
|
||||||
|
cursor = max(cursor, arrivals[arrival_index].available_time_seconds)
|
||||||
|
admit_until(cursor)
|
||||||
|
expire_video(cursor)
|
||||||
|
if not ready:
|
||||||
|
continue
|
||||||
|
selected_index = min(
|
||||||
|
range(len(ready)),
|
||||||
|
key=lambda index: (
|
||||||
|
int(ready[index].packet.traffic_class),
|
||||||
|
ready[index].arrival_order,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
selected = ready.pop(selected_index)
|
||||||
|
start = max(cursor, selected.available_time_seconds)
|
||||||
|
wire_packet = encode_link_packet(selected.packet)
|
||||||
|
end = start + transmission_duration_seconds(
|
||||||
|
len(wire_packet), channel_bitrate_bps
|
||||||
|
)
|
||||||
|
cursor = start
|
||||||
|
admit_until(end, selected, end)
|
||||||
|
blocker, blocking_delay = blocker_by_order.get(
|
||||||
|
selected.arrival_order, (None, 0.0)
|
||||||
|
)
|
||||||
|
transmitted.append(
|
||||||
|
AgeScheduledPacket(
|
||||||
|
item=selected,
|
||||||
|
start_seconds=start,
|
||||||
|
end_seconds=end,
|
||||||
|
blocked_by=blocker,
|
||||||
|
blocking_delay_seconds=blocking_delay,
|
||||||
|
wire_packet=wire_packet,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cursor = end
|
||||||
|
|
||||||
|
return AgeScheduleResult(
|
||||||
|
channel_bitrate_bps=channel_bitrate_bps,
|
||||||
|
max_video_age_seconds=max_video_age_seconds,
|
||||||
|
transmitted=tuple(transmitted),
|
||||||
|
dropped_video=tuple(dropped),
|
||||||
|
replacements=tuple(replacements),
|
||||||
|
)
|
||||||