Привет, Хабр.
Я писал это не месяц и не год.
EVRT (EVERTY real time protocol)— это результат примерно десяти лет экспериментов, ошибок, переписываний, злости, тестов, ночных сборок и попыток выжать из обычной сети поведение, похожее на игровой real-time transport.
Когда-то я уже писал на Хабре про игровой режим. Тогда это почти никто не оценил. Ну и ладно. Иногда идею начинают понимать только тогда, когда она уже успела стать архитектурой.
Теперь пора вскрывать подробности.
Но сразу обозначу границы: исходников в этот раз не будет. Не потому что нечего показать, а потому что слишком хорошо знаю, как быстро инженерные идеи превращаются в “мы сами так придумали”.
Сегодня будет не репозиторий.
Сегодня будет наука: транспорт, очереди, UDP, feedback, IDR recovery, adaptive relief, ROI и вся та скучная инженерия, из которой на самом деле и рождается низкая задержка.
Можно спорить про Sunshine, Parsec, Steam Link и другие решения. Я выслушаю. Но тут лучший я. Хотите спорить? Слушаю.
Поехали:
EVRT: почему мой real-time протокол сильнее, чем очередная обертка вокруг кодека
Я долго откладывал этот текст, потому что отлично понимаю, как это работает: ты показываешь идею, объясняешь архитектуру, раскладываешь решения по полкам — и через пару месяцев где-то появляется “случайно похожая” реализация, только уже без упоминания автора.
Что такое EVRT
EVRT — это отдельный real-time слой внутри EvertyDesk Lite (До этого 15 лет разработка игрового стриминга)
uth peer discovery relay fallback control path shell security session lifecycle
А EVRT забирает себе то, где обычный надежный канал начинает мешать:
direct UDP video frames audio receiver feedback latency metrics adaptive relief packet recovery behavior
То есть архитектура не пытается переписать весь мир.
Она делает точечный удар:
RustDesk-compatible layer:
login / auth / discovery / relay / control / shell
EVRT layer:
direct UDP / video / audio / feedback / metrics
Это первое принципиальное решение. Начинаем.
Я не ломаю надежный control plane. Я не выбрасываю совместимость. Я не делаю “все через UDP, а там посмотрим”.
Я оставляю надежный путь для того, что должно быть надежным, и выношу видео туда, где важна задержка.
Это и есть нормальная инженерия.
Маленький протокол сильнее жирного
У EVRT бинарный заголовок на 24 байта.
Не JSON.
Не protobuf ради protobuf.
Не “универсальная самоописываемая сущность”.
Не XML, прости господи.
Фиксированный заголовок. Явный magic. Версия. Тип пакета. Frame ID. Индекс пакета. Количество пакетов. Timestamp.
Протокол должен быть маленьким, потому что real-time не любит жир.
Базовая идея такая:
pub const MAGIC: u32 = 0x4556_5254; // "EVRT"pub const VERSION: u8 = 3;pub const HEADER_SIZE: usize = 24;pub const MAX_PACKET_SIZE: usize = 1200;pub const MAX_PAYLOAD_SIZE: usize = MAX_PACKET_SIZE - HEADER_SIZE;
MAX_PACKET_SIZE = 1200 — это не случайное число.
UDP нельзя просто “сделать побольше”. Большие датаграммы начинают фрагментироваться. Фрагментация в real-time видео — это почти всегда зло: потерял один фрагмент, потерял весь пакет.
Поэтому EVRT держится в MTU-safe зоне.
Да, это скучно.
Но именно из таких скучных решений потом складывается система, которая не разваливается на первом Wi-Fi. Воу, сложно?))
Кадр — это далеко не пакет
Видео-кадр почти всегда больше одного UDP-пакета.
Значит, кадр надо:
-
разбить;
-
пронумеровать;
-
отправить;
-
собрать;
-
понять, что делать при потере.
EVRT делает это явно.
Каждый пакет знает:
frame_idpacket_indexpacket_countpresentation_time_usflagspacket_type
Если это keyframe — он помечается флагом.
Клиент не гадает. Он не пытается “примерно понять”, что пришло. Он видит структуру потока.
Это важно, потому что в real-time нельзя притворяться, что потерь нет.
Потерял часть P-frame?
Не надо героически декодировать кашу.
Нужно запросить IDR и синхронизироваться.
Главное решение: один capture, один encoder, два транспорта
Вот здесь начинается самое сильное.
Наивная реализация могла бы сделать так:
capture для TCPcapture для UDPencoder для TCPencoder для UDP
И это был бы инженерный мусор.
Двойная нагрузка. Разные кадры. Гонки. Синхронизация. Лишняя задержка. Лишняя боль.
EVRT делает иначе: (Тут рисуем схему)
Один capture.
Один encoder.
Один encoded frame.
А дальше dispatcher решает, куда этот frame отправлять.
Это фундаментально важно.
EVRT не создает второй мир рядом с основным pipeline. Он встраивается в pipeline как быстрый путь.
Когда EVRT активен, кадры идут по UDP.
Когда EVRT недоступен, система не умирает — остается TCP fallback.
Когда нужен recovery point, IDR может дополнительно уйти через надежный канал.
Вот это называется не “демка на идеальной сети”, а взрослая архитектура.
Очередь должна показывать настоящее, а не прошлое
Обычная очередь работает честно:
первым пришел -> первым вышел
Для фильма это нормально.
Для remote desktop это часто катастрофа.
Представьте: пользователь двигает мышь, а клиент аккуратно показывает ему восемь старых кадров, потому что “ну они же были раньше”.
Поздравляю, вы сделали плавное прошлое.
А пользователю нужно резкое настоящее.
Поэтому в EVRT queue policy построена вокруг свежести.
Игровой режим говорит:
лучше выбросить старый кадр, чем идеально показать то, что уже не актуально
Это простая, но жесткая философия.
Для cinema mode можно позволить чуть больше буфера. Там важна плавность.
Для игры, touchpad, cursor control, remote desktop и интерактивного UI важнее другое: минимальная задержка.
И вот тут многие реализации проигрывают не потому, что у них плохой кодек, а потому что они честно декодируют устаревшую очередь.
EVRT не обязан быть честным к старым кадрам.
EVRT обязан быть честным к пользователю.
Потерял пакет — проси IDR
И вот тут пора об интересном, тут мы остановимся и начyем превращать статью из моего адского технического делирия в науку.
«IDR» Instantaneous Decoder Refresh (мгновенное обновление декодера). — Это особый тип ключевого кадра (I-кадра) в современных видеостандартах, таких как H.264 (AVC) и H.265 (HEVC).
Но тут все таки чертовски важно и для детей разжевать:
IDR-кадр сжимается только на основе самого себя с использованием внутрикадрового предсказания. Он содержит всю информацию, необходимую для построения полноценной картинки, и не ссылается на предыдущие кадры
Пока еще слабо: Представь, что видео — это большая живая книжка-раскраска. Каждый кадр в ней — отдельная страница.
Обычные кадры в видео ленивые. Чтобы сэкономить место, они не рисуют картинку заново. Они просто пишут: «Возьми прошлую страницу и передвинь там мячик на сантиметр вправо».
Потерял пакет — проси IDR
Сжатое видео зависит от предыдущих кадров.
Если поток поврежден, нельзя просто улыбнуться и продолжать. Можно получить артефакты, двоение, зависший кадр, грязь на экране и прочий визуальный мусор.
EVRT делает правильно:
reassembler увидел потерюclient отправил RequestKeyFramequeue ждет keyframeP-frames не проигрываются до IDRЭто не “магия”.Это дисциплина.
Если поток потерял синхронизацию, его надо вернуть к валидной точке.
Именно поэтому keyframe recovery встроен в поведение протокола, а не оставлен на “когда-нибудь потом”. Как Вам?)
Клиент не должен молчать
Очень много систем remote desktop построены по тупой схеме:
host: я шлю 30 fps client: ну я попробую выжить
EVRT так не делает.
Клиент постоянно говорит хосту, что происходит:
queue sizedropsarrival deltadecode deltadecode FPSpressurebacklog frames
Это превращается в feedback packet.
И теперь хост не слепой.
Он понимает, когда клиент не успевает.
Он понимает, когда очередь растет.
Он понимает, когда decode проседает.
Он понимает, когда сеть начинает превращаться в болото.
Это принципиально другой уровень поведения.
Поток не просто отправляется. Поток управляется.
Adaptive relief: уметь отступать — это сила
Многие любят героические настройки:
Давай 50 Мбит/с давай 120 FPS давай максимум качества
Это красиво до первой плохой сети.
EVRT не играет в детский максимализм. Он умеет отступать.
Если клиент сообщает pressure, хост снижает нагрузку:
меньше bitrateосторожнеее encoder policyменьше давление на сетьменьше очередьменьше задержка
И это происходит автоматически.
Не через “пользователь сам пусть полезет в настройки”.
Не через “перезапустите соединение”.
Не через “ну у вас Wi-Fi плохой”.
Хост получает feedback и меняет поведение.
Вот это и есть real-time система.
UDP — не мусоропровод , Фуф, тут мы разгоняемся ребят.
Еще одна любимая ошибка:
у нас UDP, значит шлем как можно быстрее
Нет.
Если вы просто вываливаете пачку пакетов в сеть, вы сами создаете burst, который потом сами же будете героически чинить.
В EVRT есть pacer.
Он дозирует отправку пакетов с учетом target bitrate. Даже учитывает overhead на UDP/IP framing.
Это не выглядит эффектно в README.
Но именно такие вещи решают, будет ли система работать на реальной сети, а не только на localhost.
UDP — это не разрешение вести себя как животное.
UDP — это ответственность.
ROI: не все пиксели равны
В remote desktop обычно меняется не весь экран.
Меняется маленькая область:
курсор, терминал, меню, текст, прогресс-бар, куск окна
Поэтому EVRT несет ROI metadata.
Это позволяет pipeline понимать, какая часть кадра реально изменилась, и использовать это в bitrate policy.
Сегодня ROI уже встроен как metadata и основа для дальнейшей адаптации. Следующий логичный шаг — region encode там, где backend это позволяет.
И это важный момент.
EVRT не закрывает глаза на то, что экран — это не кино.
Desktop video — это другой тип нагрузки. И транспорт должен это понимать.
Почему “просто поднять битрейт” — плохой ответ
Можно сказать:
Зачем все это? Просто поднимите bitrate.
Это ответ человека, который не видел реальных сетей.
Remote desktop живет везде:
LANWi-FiVPNrelay через VPSкорпоративный firewallAndroid TV boxстарый LinuxWindows с NVENCWindows без нормального hardware encoderмобильная сетьдешевый роутер
Одна настройка bitrate не решает эту матрицу.
EVRT решает иначе:
если direct UDP поднялся — используем fast pathесли не поднялся — fallbackесли клиент не успевает — adaptive reliefесли очередь растет — берем свежий кадресли потеряли пакет — просим IDRесли картинка статична — не давим поток зряесли codec/backend слабый — ведем себя осторожнее
Это система.
Не один ползунок. Не один кодек. Не один красивый benchmark. Система.
Где здесь сила?
Свой протокол написать несложно.
Плохой свой протокол написать вообще очень легко.
Сила EVRT не в том, что “я написал свой UDP”.
Сила в наборе решений:
-
Control plane отдельно, video fast path отдельно.
-
RustDesk-compatible слой не ломается, а используется там, где он полезен.
-
Capture и encoder не дублируются.
-
Encoded frame может уходить в разные транспорты.
-
Очередь предпочитает свежесть, а не музей старых кадров.
-
Потеря пакета приводит к keyframe recovery, а не к визуальному мусору.
-
Клиент отправляет feedback.
-
Хост адаптирует bitrate и нагрузку.
-
UDP отправляется через pacer.
-
ROI и telemetry встроены заранее.
-
Fallback остается рядом, а не прибивается гвоздями после провала.
Вот это и есть инженерная архитектура.
Не “у нас тоже есть стриминг”. Не “мы завернули кодек”. Не “на моей машине летает”.
А нормальная, взрослая модель поведения под плохую сеть, слабый клиент и реальный интерактив.
Про Sunshine, Parsec и Steam Link
Теперь можно про Sunshine, Parsec и Steam Link.
Да, это сильные продукты.
Да, они решают похожие задачи.
Да, у них есть свои преимущества, своя история, свои пользователи и свои сценарии.
Но EVRT не пытается быть их копией.
EVRT строится вокруг другой идеи: взять remote desktop архитектуру, оставить совместимый control/fallback слой и добавить к нему специализированный real-time transport, который живет внутри общей системы, а не рядом с ней.
Это не “запустить внешний стример и молиться”.
Это интегрированный transport path:
sessioncontrolvideoaudiofeedbackfallbacktelemetryrecovery
Именно поэтому я считаю EVRT одним из самых сильных решений в своем классе.
Не потому, что я громко сказал “лучший”.
А потому, что архитектура выдерживает разбор.
Но я понимаю что толком ничего не сказал,) это Хабр и тут придется дальше читать, ведь я сам пока на общем языке болтаю, ладно, чуть глубже. Едем дальше.
Тут мы и начнем)
Как надо мерить real-time :D?
Cамая большая ошибка в оценке remote desktop — мерить его как видеоплеер.
Люди любят смотреть на: FPS, битрейт, разрешение, кодек, нагрузку CPU/GPU. Это все полезно, но не главное ребят.
Сколько времени проходит между действием пользователя и видимым результатом?
Вот настоящая метрика.
Не “сколько кадров пришло”.
А “когда пользователь почувствовал ответ”.
Потому что remote desktop — это не кино. Это замкнутый контур управления:
input -> host -> capture -> encode -> network -> decode -> present -> eye -> hand
Если этот контур медленный, пользователь теряет контроль.
Можно иметь 60 FPS и плохое управление.
Можно иметь 30 FPS и ощущение, что система отвечает быстрее.
Можно иметь красивую картинку, которая приходит слишком поздно.
Можно иметь чуть менее красивую картинку, но живую.
И вот вторая почти всегда лучше.
FPS может врать
Представим две системы.
Первая показывает 60 FPS, но держит очередь в 5 кадров.
5 * 16.67 ms = 83.35 ms
Только очередь уже добавила больше 80 миллисекунд.
Вторая показывает 45 FPS, но выбрасывает старые кадры и держит очередь почти пустой.
Формально первая выглядит красивее.
По ощущениям вторая может быть быстрее.
Вот почему я не поклоняюсь FPS.
FPS — это частота обновления.
Но real-time — это задержка обратной связи.
Если система показывает много кадров, но все они из прошлого, это не победа. Это дорогой способ обмануть пользователя.
Хороший real-time иногда выглядит хуже
Это неприятная мысль, но ее надо принять.
Хороший real-time не всегда выглядит идеально на скриншоте.
Он может:
выбросить кадрснизить bitrateуменьшить jitterзапросить IDRпожертвовать плавностьювременно ухудшить качество
И все это ради одной цели: сохранить контроль.
Пользователь не управляет скриншотом.
Пользователь управляет состоянием удаленной машины.
Если ради этого нужно отказаться от красивой, но поздней картинки — надо отказаться.
В этом смысле EVRT не пытается быть самым красивым видеоплеером.
Он пытается быть самым честным transport layer для интерактива.
Почему комментарии часто будут мимо
Я уже примерно представляю часть комментариев.
Кто-то напишет:
“Есть же Sunshine”“Есть же Parsec”“Есть же Steam Link”“Есть же Moonlight”“Есть же WebRTC”“Зачем свой протокол?”“Почему не QUIC?”“Почему не просто RTP?”“Почему не открыть исходники?”
Это нормальные вопросы.
Но часто за ними скрывается одна ошибка: люди сравнивают названия, а не поведение системы.
Мне неинтересен спор в стиле:
продукт А против продукта Б
Мне интересен другой спор:
что делает система, когда потерян пакет?что делает система, когда клиент не успевает?что делает система, когда очередь растет?что делает система, когда UDP недоступен?что делает система, когда изменилась только маленькая область экрана?что делает система, когда нужен control-only режим?что делает система, когда красивый кадр уже устарел?
Вот это инженерный разговор.
Если на эти вопросы нет ответа, то разговор про “у нас тоже low latency” заканчивается очень быстро.
Почему не исходники, да надоело ребят. НачТех статья, учите кодеки. Ссылки вставить я не могу по политике хабра.
Я понимаю, что на Хабре любят код. Я тоже люблю код. Но есть разница между “показать идею” и “подарить готовую карту тем, кто потом забудет имя автора”.
Я уже проходил это.
Десять лет работы — это не только строки кода.
Это:
ошибочные архитектурысломанные пайплайныплохие решенияпереписываниятесты на слабых машинахпопытки понять поведение очередейотладка потерьподбор thresholdsработа с fallbackэксперименты с Androidразделение control и video
Исходник показывает итог.
Но не показывает цену решений.
Сегодня я показываю именно цену решений: почему они такие, где они родились, какую проблему закрывают и почему без них real-time превращается в набор красивых слов.
Сегодня я показываю именно цену решений: почему они такие, где они родились, какую проблему закрывают и почему без них real-time превращается в набор красивых слов.
Это не open-source статья.
Это инженерская статья.
Протокол — это не формат пакета
Еще одна важная мысль.
Протокол — это не только заголовок.
Можно скопировать 24-байтный header.
Можно сделать frame_id.
Можно добавить packet_index.
Можно отправлять UDP.
Можно даже написать reassembler.
Но это еще не EVRT.
Потому что EVRT — это не только wire format.
EVRT — это поведение системы:
как кадр попадает в очередькогда кадр выбрасываетсякогда запрашивается IDRкак клиент сообщает pressureкак хост снижает нагрузкукак UDP дозируется pacer-омкак ROI влияет на bitrateкак fallback остается живымкак control path не смешивается с video path
Вот это и есть протокол в настоящем смысле.
Не “какие байты лежат в пакете”.
А “как система ведет себя во времени”.
В real-time поведение важнее формата.
Время — главный ресурс
В обычных программах главный ресурс часто память или CPU.
В real-time главный ресурс — время.
У кадра есть срок годности.
У input-события есть срок годности.
У mouse movement есть срок годности.
Если событие пришло слишком поздно, оно уже не имеет той же ценности.
Поэтому EVRT построен вокруг жесткого вопроса:
этот кадр еще стоит показывать?
Если нет — его надо выбросить.
Это может звучать грубо.
Но это честно.
Система, которая боится выбрасывать старое, обречена копить задержку.
Система, которая умеет выбрасывать старое, получает шанс остаться живой.
Почему «надежность» может быть врагом
Надежность — хорошее слово.
Но в real-time оно опасно, если не уточнить, что именно мы хотим надежно доставлять.
Control-события должны быть надежными.
Авторизация должна быть надежной.
Session state должен быть надежным.
Shell должен быть надежным.
File transfer должен быть надежным.
Но старый video frame не всегда должен быть надежно доставлен.
Иногда надежная доставка старого кадра — это вред.
Потому что она задерживает новый кадр.
Вот почему EVRT разделяет мир:
надежный control planeбыстрый real-time plane
Это не техническая вкусовщина.
Это признание того, что разные данные имеют разную ценность во времени.
Пароль нельзя потерять. А старый P-frame можно выбросить.
Команду нельзя перепутать. Более того — иногда нужно выбросить.
Слабые устройства важнее топовых
На топовом железе многое выглядит хорошо.
Мощный CPU.
Свежий GPU.
Быстрый Wi-Fi.
Хороший роутер.
Нормальный encoder.
Низкая нагрузка.
В таких условиях многие системы кажутся быстрыми.
Настоящая архитектура проверяется не там.
Она проверяется на слабых клиентах, плохих сетях, Android-приставках, старых ноутбуках, странных Linux-конфигурациях, relay-сценариях и перегруженных Wi-Fi.
Вот где видно, есть ли у системы мозг.
Если клиент не успевает, EVRT получает feedback.
Если очередь растет, EVRT выбирает свежесть.
Если сеть теряет, EVRT просит IDR.
Если UDP недоступен, fallback остается.
Если изменилась маленькая область, ROI помогает не давить поток полным битрейтом.
Сильная система не та, которая летает на идеальном стенде.
Сильная система та, которая не разваливается, когда стенд перестал быть идеальным.
Честно сказать мне не нравится продолжать статью в таком духе, выписывая каждый раз новую строку. Но я долджен уже добить то, о чем начал историю.
Remote desktop — это не один поток
Еще одна причина, почему монолитные подходы плохи: remote desktop кажется одним потоком только снаружи. На самом деле это несколько разных миров:
video audio input clipboard shell file transfer session control auth telemetry feedback codec config transport negotiation
У каждого мира свои требования.
Video хочет свежести. Audio хочет стабильности и низкого jitter.Input хочет минимальной задержки. Shell хочет надежности. File transfer хочет целостности. Telemetry хочет регулярности. Feedback хочет быть легким и частым. Auth хочет безопасности.
Если все это засунуть в одну философию доставки, получится компромиссная каша.
EVRT хорош тем, что не пытается сделать один канал “для всего”.
Он выделяет real-time слой там, где это нужно, и оставляет reliable path там, где надежность важнее скорости.
Это не усложнение, это разбор системы на честные части.
Практический критерий
Для себя я формулирую критерий так:
если сеть стала хуже, система должна стать осторожнее;если клиент стал слабее, система должна облегчить поток;если кадр устарел, система должна его выбросить;если поток поврежден, система должна запросить восстановление;если fast path невозможен, система должна уйти в fallback;если video не нужен, система не должна его поднимать;если изменилась малая область, система не должна вести себя как при полной смене кадра
Это и есть practical real-time. Не идеальный. Но живой.
Вместо заключения
Я не пытаюсь убедить всех. Это бесполезно. Кто-то все равно скажет “зачем”, потому что не упирался в эту стену. Кто-то скажет “уже было”, потому что увидит слово UDP и решит, что понял всю архитектуру. Кто-то попросит исходники, хотя статья не про копирование кода, а про устройство решений. Кто-то будет сравнивать логотипы. Но если вы когда-нибудь пытались сделать remote desktop, который ощущается не как видеоплеер, а как продолжение руки, вы понимаете, о чем речь.
Real-time — это не красивые кадры.
Real-time — это дисциплина времени.
И EVRT — моя попытка сделать эту дисциплину архитектурой.
Без ссылок.
ссылка на оригинал статьи https://habr.com/ru/articles/1046998/