Когда ты пишешь собственный кодек удалённого рабочего стола, последнее, что хочется услышать от человека из конкурентов:
«EVRTCK слаб».
Особенно когда этот кодек действительно работает.
Он lossless.
Он умеет передавать рабочий стол тайлами 32×32.
Он не пережимает текст в кашу.
Он использует dirty regions, XOR относительно предыдущего кадра, ZRLE, zstd, специальные пути для одноцветных тайлов.
И я мог бы начать спорить.
Мог бы принести бенчмарки.
Мог бы показать, как после устранения создания zstd-контекста на каждом тайле декодирование полного кадра 2560×1440 у меня упало примерно с 89,9 до 14,2 мс, а кодирование 1080p на типичной тайловой нагрузке — примерно с 65,8 до 4,4 мс.
Мог бы ещё месяц оптимизировать AVX, SIMD, dirty map и доказывать, что EVRTCK хороший.
Но постепенно до меня дошло неприятное:
он был прав.
Только не совсем в том смысле, который я сначала услышал.
EVRTCK слаб не потому, что это плохой кодек.
Он слаб в роли единственного ответа на любой экран.
И вот с этого момента проект стал гораздо интереснее.
Ошибка на самом деле была в самом вопросе
Первоначальная архитектура EvertyDesk была довольно традиционной:
capture ↓codec ↓transport ↓decoder ↓screen
То есть мы берём кадр и спрашиваем:
Как его лучше сжать?
Для обычного рабочего стола EVRTCK действительно хорошо попадает в задачу.
Если на экране IDE, терминал, браузер, таблица или окно настроек, между соседними кадрами значительная часть пикселей вообще не меняется.
Зачем превращать это в видео?
Можно взять:
previous frame +changed tiles ↓lossless reconstruction
И отправить считаные килобайты.
Проблема начинается, когда пользователь открывает YouTube.
Или игру.
Или начинает крутить 3D-модель.
Или весь экран превращается в высокоэнтропийное движение.
В этот момент главное преимущество EVRTCK исчезает:
tile changedtile changedtile changedtile changedtile changed...
А H.264/H.265/AV1 как раз создавались для таких сцен.
Мог бы попытаться затолкать motion vectors, temporal prediction и ещё десяток механизмов внутрь EVRTCK.
Получился бы мой собственный плохой аналог современного видеокодека.
Я решил этого не делать.
Вместо вопроса:
Как сделать EVRTCK лучшим кодеком для всего?
я задал другой:
А почему весь экран вообще должен кодироваться одним способом?
Так появился EVRT2CKMAX.
EVRT2CKMAX — это уже не кодек
Название здесь немного обманывает.
EVRTCK — кодек.
EVRT2 — транспортный протокол.
А EVRT2CKMAX я сейчас рассматриваю скорее как visual delivery architecture — систему, которая решает:
-
какую информацию сейчас действительно важно доставить;
-
насколько она уже устарела;
-
какой способ представления подходит этому региону;
-
сколько вычислений стоит на него потратить;
-
какой CPU/GPU/NPU/видеоблок сейчас выгоднее использовать;
-
можно ли временно показать локальное предсказание;
-
и что следует отправить первым, если ресурсов на всё одновременно не хватает.
То есть вместо:
FRAME ↓CODEC
получается примерно:
SCREEN │ ▼ Attention Map │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ text video static UI │ │ │ ▼ ▼ ▼ EVRTCK H265/AV1 reuse │ │ │ └────────────┼────────────┘ │ ▼ EVRT2
Причём выбор потенциально может меняться по регионам и по времени.
И нет, multi-codec я не изобрёл
Здесь надо сразу снять очевидный статейный вопрос.
Идея использовать разные способы кодирования для разных типов содержимого существует давно.
Citrix Thinwire умеет применять lossless к тексту и простой графике, JPEG к изображениям и видеокодеки к движущимся областям. Современный RDP также имеет mixed-mode и классифицирует текст, изображения и видео.
То есть написать:
«Я первый догадался, что текст и видео надо кодировать по-разному»
было бы просто неправдой.
Мне гораздо интереснее другой вопрос:
по какой целевой функции вообще принимать решение?
Compression ratio?
Bitrate?
FPS?
Encode time?
Я пришёл к другой величине.
Perceptual Age Field: сколько лет пикселю в глазах пользователя
Представим простой случай.
На удалённом экране одновременно находятся:
-
курсор;
-
окно, которое пользователь тащит мышью;
-
терминал;
-
системные часы;
-
фоновая анимация.
С точки зрения обычного кадра всё это просто dirty pixels.
Но для человека эти пиксели совершенно не равноценны.
Если часы Windows обновятся на 80 мс позже — скорее всего, никто этого даже не заметит.
Если окно под мышью отстаёт на 80 мс — пользователь мгновенно скажет:
«тормозит».
Отсюда появился Perceptual Age Field — PAF.
Вместо одной свежести всего кадра у каждого региона есть собственный возраст:
region_i → age_i
и собственная важность:
region_i → P_i
Тогда можно считать условный attention-weighted age:
AttentionWeightedAge = Σ(P_i × age_i) ────────────── Σ(P_i)
И задача меняется.
Не:
минимизировать время готовности полного кадра.
А:
минимизировать возраст той информации, которую человек сейчас воспринимает как важную.
Для меня это один из главных поворотов всей архитектуры.
Полный кадр может опоздать. Важный регион — нет
Допустим, канал сейчас позволяет отправить только часть изменений.
Классический scheduler может идти по порядку:
tile 0tile 1tile 2tile 3...
EVRT2CKMAX пытается сделать иначе:
cursor region → NOWdragged window → NOWfocused text → NEXTbackground motion → LATERclock → WHATEVER
То есть bandwidth превращается в Attention Budget.
У каждой области появляется допустимый age ceiling.
Если важный region начинает его превышать, система должна сначала деградировать периферию, а не позволять самому значимому содержимому бороться за остатки полосы.
Это уже реализуется в экспериментальном EVRT2 scheduler:
-
Attention Map;
-
visible region;
-
приоритет порядка отправки;
-
age-ceiling breach detection;
-
jitter bypass для приоритетных данных.
И это как раз тот случай, где Markdown постепенно перестаёт быть фантазией и начинает превращаться в Rust.
Пять объектов вместо одного «битрейта»
В текущей модели практически каждое решение EVRT2CKMAX зависит от пяти объектов.
1. Attention Map
Что сейчас важно человеку?
Источниками могут быть:
motionfocusunexpected changecursor proximityfuture gaze informationapplication semantics
Сегодня часть этих сигналов уже реальная, часть оставлена на будущее.
2. Temporal Confidence
Насколько мы вообще уверены, что имеющееся представление региона ещё соответствует реальности?
Это отдельная проблема.
Старый пиксель не обязательно неправильный.
Статичная кнопка через 200 мс может оставаться абсолютно правильной.
А moving region уже через 16 мс может быть устаревшим.
3. Reconstruction Budget
Сколько ресурсов мы можем потратить прямо сейчас?
Не только bandwidth:
networkCPU timeGPU timelatencypowermemory bandwidth
4. Transport Feedback
Что происходит с настоящим каналом?
RTT, loss, jitter, congestion, recovery.
5. Execution Capability
Какие вычислительные инструменты реально доступны на конкретной машине?
Не:
«у пользователя есть GPU».
А:
какую конкретную задачу этот GPU сейчас решит быстрее CPU с учётом стоимости передачи данных и текущей нагрузки?
И здесь появился ещё один механизм, который мне самому нравится.
Silicon не должен использоваться просто потому, что он есть
Во многих программах выбор выглядит примерно так:
if gpu_available { use_gpu();}
Но наличие GPU ещё не означает, что GPU выгоден.
Для маленькой работы:
CPU: 0.3 msGPU:upload 0.4 msdispatch 0.2 mscompute 0.1 msreadback 0.4 mstotal: 1.1 ms
Поздравляю.
Мы ускорили вычисление в десять раз и получили систему почти в четыре раза медленнее.
Поэтому в EVRT2CKMAX появился Marginal Utility Execution Scheduler.
Сначала система регистрирует capabilities:
EntropyCodingRoiEncodingMotionEstimationWarpHomographyPredictionDmaTransferVideoEngine...
Затем калибрует реальные providers на реальной машине.
Не по рекламной табличке GPU.
Не по номеру модели.
А измерением.
После этого provider имеет смысл использовать только если его включение даёт положительную marginal utility относительно baseline.
Это уже используется в коде хотя бы на первом практическом уровне: вместо одного вечного RAYON_THRESHOLD решение может приниматься исходя из калиброванной стоимости конкретной машины.
AR2R47: режимы — не три новых кодека
Тут название особенно легко понять неправильно.
AR, 2R и 47 — это не три алгоритма compression.
Это три разных режима распределения бюджета.
AR
Рабочий стол, поддержка, терминалы, документы.
lossless > bandwidth > latency
Основной инструмент — EVRTCK.
2R
Смешанный динамический контент.
smoothness+quality+hybrid representations
Статические регионы могут остаться lossless, движущиеся — уйти в silicon video path.
47
Высокая динамика / gaming.
Здесь бессмысленно героически прогонять каждый тайл через XOR.
video siliconlow latencyno B-framesno lookaheadhigh FPS
Главная идея AR2R47 для меня не в самих порогах.
Пороги ещё будут меняться.
И не в названии, хотя я не удержался:
AR · 2R · 47 ↓ ARTUR 47
Главная идея в том, что режим определяет политику распределения Attention Budget, а не просто «выбранный кодек».
First Light: первый пригодный пиксель важнее идеального позднего
А вот это уже один из механизмов EVRT2CKMAX, который мне самому хочется довести до реального эксперимента.
Представим важный регион.
У нас есть несколько способов его получить:
EVRTCK software 2.7 msH265 hardware 1.8 msAV1 hardware 3.1 ms
Обычная архитектура сначала выбирает provider.
EVRT2CKMAX допускает другой подход:
REGION │ ┌─────────┼─────────┐ ▼ ▼ ▼ EVRTCK H265 AV1 │ │ │ └──── FIRST READY ──┘ │ ▼ SEND IT
Это Codec Race.
Нас интересует не тот provider, который в среднем имеет лучший compression ratio.
Нас интересует:
кто первым способен дать визуально пригодный результат в этой конкретной ситуации.
А более поздний результат потенциально может стать refinement/correction.
Это пока не production-механизм.
В roadmap он именно так и обозначен: следующая большая часть работы — реальный race EVRTCK против silicon provider с измерением time-to-first-signal.
И вот здесь для меня проходит важная граница между спецификацией и маркетингом:
пока race не измерен живьём, я не собираюсь писать, что он быстрее чего-либо.
Prediction: можно показать будущее, но нельзя объявить его настоящим
Следующий кусок появился из старой идеи Warp.
Первоначально Warp был описан слишком игровым примером:
last frame+fresh mouse input→camera transform
Для FPS это понятно.
Мышь двинулась вправо — камера почти наверняка тоже повернётся вправо.
Для удалённого рабочего стола это уже не универсально.
Движение мыши не означает, что весь Windows desktop надо сдвинуть на 12 пикселей.
Поэтому появилась отдельная ветка — motion extrapolation.
Клиент имеет два последних подтверждённых кадра:
REAL[n-1]REAL[n]
Находит локально согласованное двумерное движение блоков:
scrollwindow dragpanninganimation
и при кратком отсутствии следующего кадра может синтезировать:
PREDICTED[n+1]
При этом есть жёсткое правило:
prediction ≠confirmed state
Предсказанный кадр существует только для presentation.
Он никогда не становится новым reference frame.
Никогда не участвует в следующем prediction как ground truth.
Следующий настоящий кадр всегда исправляет изображение.
Я назвал эту границу Causal Integrity Principle:
Prediction may reduce perceived latency but must never create unconfirmed reality.
Сейчас block-motion extrapolation уже существует как экспериментальный клиентский модуль: motion estimation, однократная экстраполяция, empirical confidence, отдельный speculative buffer и unit-тесты.
Живой визуальный эксперимент ещё предстоит.
И именно так я хочу вести весь EVRT2CKMAX:
SPECIFIED ↓IMPLEMENTED ↓TESTED ↓LIVE VERIFIED ↓BENCHMARKED ↓PRODUCTION
Не перепрыгивая ступени.
Что из этого действительно новое?
Это самый неудобный и поэтому самый полезный вопрос.
Multi-codec — не новый.
ROI — не новый.
Client-side prediction — не новый.
Heterogeneous CPU/GPU scheduling — не новый.
Даже идея измерять «возраст информации» имеет огромную академическую историю.
Поэтому EVRT2CKMAX не надо продавать как:
«Артур Валиев изобрёл все эти вещи».
Это было бы глупо.
Моя ставка находится в композиции:
human attention ↓Perceptual Age Field ↓acceptable age per region ↓Reconstruction Budget ↓Transport Feedback +Execution Capability ↓provider / scheduler / prediction ↓FIRST LIGHT ↓eventual confirmed reconstruction
То есть кодек становится всего лишь одной из execution capabilities.
AV1 больше не враг EVRTCK.
H.265 не враг.
NVENC не враг.
CPU не враг GPU.
Все они — инструменты.
Главный вопрос:
какой инструмент прямо сейчас сильнее всего уменьшит perceptual age важной для человека информации?
Вот это уже гораздо интереснее моей первоначальной попытки написать «самый быстрый кодек».
И да, значительная часть EVRT2CKMAX пока сырая
Я специально это пишу.
На момент этой статьи часть системы уже работает:
-
EVRT2 wire format;
-
FEC;
-
adaptive jitter;
-
Attention Map;
-
visible-region scheduler;
-
capability registry;
-
marginal-utility scheduling;
-
экспериментальный EVRT2 session path;
-
client-side block-motion prediction;
-
AR2R47 state machine покрыта тестами.
А часть ещё остаётся архитектурой или следующими фазами:
-
AR2R47 пока не управляет всей живой production-сессией;
-
Temporal APF пока в основном wire-level механизм;
-
Codec Race ещё требует живого benchmark;
-
cross-codec splicing впереди;
-
полноценный silicon EVRT2 pipeline впереди;
-
WAN/security/product integration тоже ещё предстоят.
Для меня это не недостаток публикации.
Это причина её публиковать.
Потому что спецификация — это не пресс-релиз о готовом продукте.
Это чертёж.
И теперь по этому чертежу постепенно появляются детали.
Самое смешное — EVRTCK никуда не делся
Можно посмотреть на всё это и решить:
«Ну всё понятно. Валиев понял, что EVRTCK слаб, и выбросил его».
Нет.
Я просто перестал требовать от него невозможного.
EVRTCK остаётся очень интересным специализированным lossless-кодеком для:
-
IDE;
-
терминалов;
-
текста;
-
окон;
-
статического desktop UI;
-
sparse updates.
Он просто больше не обязан хорошо кодировать фильм.
И это, пожалуй, лучшее, что могло с ним произойти.
Раньше архитектура выглядела так:
МИР ↓EVRTCK
Теперь:
EVRT2CKMAX │ ┌────────────┼─────────────┐ │ │ │ EVRTCK AV1 H265 │ │ │ lossless video video │ │ │ └────────────┼─────────────┘ │ prediction │ Attention Map │ perceptual scheduler │ EVRT2
EVRTCK стал меньше.
А система стала больше.
Возможно, коллега из AnyDesk был полезнее, чем думал
Когда мне сказали:
«EVRTCK слаб»,
моей первой реакцией было желание доказать обратное.
Теперь я думаю, что это был неправильный спор.
У каждого специализированного алгоритма есть область, где он проигрывает.
Интересная архитектура начинается не тогда, когда ты научился это отрицать.
А когда научился этим пользоваться.
Если H.265 быстрее на движущемся регионе — использовать H.265.
Если EVRTCK даёт идеальный текст за копейки — использовать EVRTCK.
Если предыдущий кадр всё ещё корректен — не отправлять ничего.
Если сеть задержала следующий frame, а движение предсказуемо — на несколько миллисекунд показать speculative reconstruction.
Если GPU медленнее CPU с учётом transfer overhead — оставить GPU в покое.
Если пользователь смотрит в одну область — не позволять фону украсть у неё latency budget.
В какой-то момент я понял, что больше не пишу кодек.
Я пытаюсь построить систему, у которой нет любимого кодека.
Есть только один любимый результат:
человек должен как можно раньше увидеть то, что для него сейчас важно.
Это и есть EVRT2CKMAX.
А получится ли?
Теперь это уже можно не обсуждать теоретически.
Можно измерять.
И этим я собираюсь заняться дальше.
С Вами был Артур Валиев, разработчик EvertyDesk, EVRT2CKMAX и EVRT2, Всем крутых выходных!
myamyamya@everty.ru
ссылка на оригинал статьи https://habr.com/ru/articles/1077904/