Как устроена видеосвязь на уроках в Skyeng
По материалам доклада Кирилла Рогового на FrontendConf 2019
Учить английский в Skyeng можно где и когда угодно — нужны только интернет и видеосвязь. Чтобы и ученикам, и учителям было удобно заниматься, она должна быть быстрой даже в 3G-сетях, надежной при нестабильном соединении, доступной из браузера.
Для этого два года назад Skyeng отказался от внешнего сервиса видеосвязи в пользу интеграции WebRTC + Janus в собственную платформу Vimbox — и рассказал об этом в блоге на «Хабре». Сегодня мы расскажем, как мы снижаем потери данных при видеосвязи, чтобы во время занятия видео или звук как можно реже прерывались или зависали.
Британский акцент с доставкой по оптоволокну
Все начинается с того, что через API браузера мы запрашиваем у ученика или учителя доступ к камере и микрофону: если вы когда-нибудь были хотя бы на первом уроке, методист показывал вам, как это сделать.
Затем мы захватываем два медиапотока — аудио и видео — и кодируем их, чтобы сделать файлы легче. Одна секунда ролика формата 640х480 — это 30 полноцветных картинок: вместе они весят 288 Мбит/с, а пропускная способность большинства домашних сетей — 100 Мбит/с. Мы используем кодек, который выделяет из этих 30 картинок только ключевые кадры, где картинка резко меняется. А для остальных картинок запоминает и передает разницу между ними.
Закодированные медиапотоки делятся на пакеты данных и «упаковываются» в протокол верхнего уровня RTP (Real-time Protocol): он запоминает порядковые номера пакетов данных, хранит данные о тайминге, которые нужны для синхронизации картинки и звука.
В нашем WebRTC подключено расширение RTCP (RTP Control Protocol). Оно помогает обмениваться информацией о статистике потерь и получения пакетов данных.
Теперь самое интересное: мы должны передать пакеты данных от одного компьютера (точнее, его IP-адреса) к другому по UDP-протоколу. Это протокол, который в отличие от TCP, не гарантирует доставку пакетов. Если сравнить протоколы TCP и UDP с обычными курьерами, первый позвонит в дверь, попросит предъявить паспорт, заполнить бланк получения и расписаться, а второй просто поставит пакеты у двери и уйдет. Заберете ли вы пакеты, похитят ли их соседи — уже не его забота. Второй курьер не ждет, пока получатель откроет дверь и распишется в бланке, поэтому работает значительно быстрее.
Вернемся к UDP-протоколу. Данные по нему передаются быстро: это очень важно, когда преподаватель из Кардиффа тренирует британский акцент с учеником из Нижнего Новгорода, чтобы ни звук, ни видео не зависали. Незначительные потери в данных не страшны: скажем, если ученик не получит 10 из 1800 картинок за минуту видео, то скорее всего, даже не заметит этого. Наша главная задача — избежать крупных потерь пакетов при такой доставке.
Когда пакеты получены, происходит все то же самое, но в обратном порядке — протокол RTCP проверяет, все ли пакеты в наличии, медиапотоки извлекаются из RTP, декодируются и подаются на платформу. Весь обмен данными происходит за считанные доли секунд, пока преподаватель поправляет произношение артикля the.
Так бы все работало в идеальном мире. А как получается в реальности?
Пакеты данных могут потеряться из-за глюков и багов, системных ошибок и поломок оборудования, перегрузки сети, а еще из-за стен и микроволновок. Серьезно, если компьютер в одной комнате, а роутер — в другой, стена между ними может работать блокиратором радиосигнала. Как и включенная микроволновка, если заниматься английским на кухне.
Четыре решения одной проблемы
Как сделать так, чтобы секунды и минуты видео не терялись передаче данных по UDP? Самое популярное решение — джиттер буфер (jitter buffer), трансляция видео с почти незаметной задержкой. К примеру, прямой путь между IP-адресами ученика и учителя занимает 30 мс (миллисекунд).
Добавим к этому времени еще 60 мс: за это время мы проверим, все ли пакеты получены, а если нет, то повторно запросим их. А пока отрисуем кадр с теми пакетами данных, которые у нас есть. Пользователь увидит картинку через 90 мс: это комфортный уровень задержки, который мы даже не замечаем.
Еще один способ снизить потери — временно снизить качество изображения, уменьшить битрейт. Такой способ применяется, когда сеть перегружена: чтобы отправлять пакеты поменьше, используют более сильное сжатие изображения через кодеки. Вероятно, вы сталкивались с тем, что когда скорость вашего домашнего интернета снижается, то ролики на YouTube воспроизводятся не в HD-качестве, а в 240p или 360p.
В Skyeng мы поставили лимит на видеотрафик в 286 кб/с при максимальном разрешении 640х480. Этого вполне хватает, чтобы передавать картинку в высоком качестве, но при этом мы не даем WebRTC произвольно перегружать сети и снижать битрейт, чтобы пользователи не сталкивались с внезапным ухудшением качества видео.
Третий способ — дублировать часть данных при отправке, чтобы в случае потери данных восстановить их. Эта технология называется Forward error correction.
Четвертый способ — гибко настроить собственные сети, чтобы выстраивать каналы между учителями и учениками по принципу наименьшего числа переключений от сети к сети.
Во-первых, мы заворачиваем весь видеотрафик через собственные сервера в Москве и Санкт-Петербурге.
Во-вторых, внимательно настраиваем сервера и маршрутизаторы. К примеру, в Linux для UDP-пакетов есть свои буферы, и эти буферы по настроены на пропускную способность 100 mbps. Если сервер начнет пропускать сквозь себя 200, 300, 400 mbps, пакеты начнут теряться прямо на сервере из-за того, что буфер слишком быстро заполняется.
Теперь, когда вы соберетесь на встрече в Zoom или Hangouts, будете лучше понимать, откуда берутся периодические задержки звука или видео — и какую работу проделывают наши программисты, чтобы ученики сталкивались с ними как можно реже.