Привет, Хабр! На связи Женя Фирстов, лид команды разработки бэкенда видеозвонков в Т-Банке. Расскажу про устройство современных систем видеоконференцсвязи. Я затрону широкий спектр тем, но не буду углубляться в детали, чтобы статья была понятна для тех, кто ранее не работал с видеосвязью. Моя цель — дать системное представление о ВКС.

Статья будет полезна инженерам, которые только начинают работать с ВКС или внедряют стороннее решение в своей компании, а еще техническим менеджерам и аналитикам, которые выбирают систему видеосвязи для своей организации. В конце статьи оставлю чек-лист с критериями сравнения ВКС. Это первая статья серии, и в следующей я расскажу про наш подход к реализации ВКС.

Видеозвонки в Т-Банке

Мы активно используем видеозвонки для идентификации клиентов на чувствительных операциях. Например, если вы захотите поменять привязанный к аккаунту номер телефона или решите перевести на криптобиржу три зарплаты, то с большой вероятностью вас могут пригласить на видеозвонок. Нам важно убедиться, что вы — это действительно вы, и что вы действуете по доброй воле, а не сидите в подвале с мешком на голове.

Идентификация по видеозвонку быстрее традиционной идентификации через уточняющие вопросы и работает надежнее, а значит, экономит время сотрудников и делает банк безопаснее для клиентов. Есть пример известного случая, когда само наличие видеозвонка в Т-Банке спасло деньги клиента.

Вот что видит клиент во время идентификации
Вот что видит клиент во время идентификации

Как работает передача видео по сети

В основе большинства современных ВКС лежит WebRTC — протокол передачи медиа по сети, который Google выпустил в opensource в 2011 году. WebRTC поддерживают все популярные браузеры, есть SDK для Android и iOS. Существуют и другие протоколы — например, в Zoom написали свой.

Когда нет ресурсов на разработку своего протокола, единственный доступный вариант — это WebRTC.

Предположим, есть два устройства в сети и нужно в режиме реального времени вывести видео с камеры одного устройства на экран другого. Из-за необходимости передавать трафик в реальном времени мы чаще всего в качестве транспорта будем выбирать UDP.

UDP передается по неоднородной и нестабильной сети, из-за этого у нас будут появляться проблемы, которые придется решать.

Если бы мы не стремились минимизировать задержки, то задача решалась бы иначе. Стриминговые платформы, которым важнее качество видео, а не задержка, используют в работе другие протоколы (например, HLS или DASH), и я не буду рассматривать их в статье. Это тема отдельного университетского курса.

Чтобы отправлять трафик в UDP, нам нужно знать IP и порт принимающей стороны, а еще нужно, чтобы принимающая сторона разрешила прием UDP по этому адресу.

Узнать свой IP и порт можно, отправив запрос на какой-то сервер в интернете и попросив его вернуть твой IP. В терминологии WebRTC этот сервер вместе с протоколом, по которому производятся такие запросы, называется STUN. Если ваши настройки NAT позволяют, то на возвращенном в ответе IP уже можно будет принимать UDP-трафик с произвольного адреса в сети.

В случае с Symmetric NAT эта схема не сработает: потребуется прокси-сервер, который будет получать трафик от отправителя и пересылать конечному адресату за NAT. Такие сервера в WebRTC называются TURN. Весь механизм поиска маршрута для передачи трафика называется ICE, а пара IP-порт — ICE-кандидат. Передача ICE-кандидатов между участниками WebRTC-сессии — задача пользователя, обычно для этого пишут или используют готовые приложения на основе websocket.

Схема работы WebRTC
Схема работы WebRTC

После обмена адресами для приема трафика можно передавать видео и аудио. Качество сети при real-time-коммуникации оказывает решающее влияние на воспринимаемое качество картинки и звука, поэтому в WebRTC встроен набор механизмов для компенсации сетевых проблем.

Первая сетевая проблема — задержки при передаче данных. В WebRTC для их измерения используют обычно RTT — round trip time. Вторая проблема — packet loss, потеря пакетов, измеряется в процентах.

Во время звонка обе проблемы накладываются друг на друга и приводят к эффекту jitter: равномерно отправляемые из источника пакеты приходят на принимающую сторону с неравномерной задержкой и нарушением порядка. Для компенсации jitter используется jitter buffer: пакеты некоторое время перед воспроизведением накапливаются в памяти, дожидаясь отстающих.

С packet loss тоже можно бороться: есть подход избыточного кодирования FEC, который повышает надежность за счет увеличения битрейта. Принимающая сторона может перезапрашивать недостающие пакеты, отправляя NACK-и — negative acknowlegement. Если пакеты-потеряшки все же не удалось восстановить, то можно попробовать скрыть их потерю на принимающей стороне с помощью механизмов PLC.

Наглядная демонстрация эффекта jitter
Наглядная демонстрация эффекта jitter

Свои компромиссы есть и в мире кодеков (алгоритмов преобразования медиапотока в байты и обратно). С одной стороны, можно как следует прогреть процессор и получить отлично сжатое видео, которое можно без проблем передать даже по ограниченному каналу связи, с другой — можно кодировать меньше и из-за этого генерировать больший битрейт. Например, современные видеокодеки VP9 и AV1 сжимают видео лучше, но тратят на это больше ресурса процессора, чем более классический H264. Также нужно держать в голове, что не у всех пользователей будет поддерживаться нужный вам кодек и что избыточная нагрузка на CPU может привести к фризам на клиенте. Глубже погрузиться в трейдоффы между кодеками поможет статья на MDN.

Серверные топологии

WebRTC дает разработчику все необходимое для передачи медиапотока в режиме «точка-точка», но не предлагает какого-то единого механизма для построения больших видеоконференций. Если в звонке больше двух участников, есть несколько вариантов топологий: 

  • Mesh, в которой каждый участник напрямую связан с каждым другим. Единственный ее плюс — низкая задержка, главный очевидный минус — линейный рост передаваемого и принимаемого трафика на клиенте в зависимости от числа участников, из-за чего на практике качество звонка деградирует где-то в районе подключения 8-го пользователя. Использование этой топологии ограничено обычно звонками один на один.

  • MCU, в которой каждый участник отправляет свои потоки на сервер, где происходит микширование аудиопотоков всех участников в один и объединение всех видеопотоков в «сеточку», благодаря чему каждый участник получает один аудиопоток и один видеопоток.

    Трейдофф здесь простой: за счет интенсивной нагрузки на серверный CPU все участники звонка экономят трафик и процессорное время. Помним, что каждый поток нужно не только принять, но еще и декодировать ☝️

  • SFU, где сервер занимается пересылкой потоков: участник отправляет свои потоки на сервер и подписывается на получение нужных ему потоков других участников.

Серверные топологии
Серверные топологии

На практике больше возможностей для горизонтального масштабирования дает симбиоз SFU и MCU: SFU для видео, MCU для аудио. Из определения SFU следует принципиальная приспособленность этой топологии для горизонтального масштабирования: если все потоки в рамках звонка не помещаются на один сервер, то часть участников начинает публиковать свои потоки на соседний, при этом с точки зрения получения потоков на клиенте ничего не меняется. Клиент так же запрашивает видео нужных ему участников, просто часть из них приходит к нему с другого сервера.

SFU можно использовать и для аудио, но здесь разработчики ВКС обычно оптимизируют трафик на клиенте. Микширование аудио на сервере — относительно легкий процесс, он не требует таких колоссальных ресурсов CPU, как микширование видео (из-за чего, кстати, MCU для видео не получило широкого распространения).

Из-за того, что каждый квадратик в видеоконференции — это отдельный видеопоток, разработчики ВКС всячески хитрят: ограничивают максимальное отображаемое в интерфейсе количество участников с включенной камерой, и чем больше показывается участников, тем хуже будет получаемое с сервера качество видео. Также на руку играет то, что сильно много квадратиков с видео обычно просто не помещается на экран.

К слову, о качестве видео: чтобы на клиенте в зависимости от текущего состояния интерфейса можно было динамически переключаться между хорошим и плохим качеством видео другого участника, сервер обычно предоставляет возможность получать один и тот же поток в разных качествах. Здесь есть три основных подхода к реализации: simulcast, при котором каждый пользователь отправляет на сервер видео сразу же в нескольких качествах в отдельных потоках; SVC (scalable video coding), где видео кодируется в нескольких качествах в рамках одного потока; подход с серверным транскодированием, когда клиент передает на сервер видео в максимальном доступном качестве, дальше это видео преобразуется в несколько потоков с разным качеством. Трейдофф здесь понятен: накладные расходы на кодирование видео сразу в несколько разрешений будет нести либо клиент (в случае с simulcast и SVC), либо сервер.

Демонстрация экрана

Закодить передачу видео с экрана через WebRTC просто. Нужно получить из API браузера или операционки поток с экрана и подсунуть его в WebRTC-подключение как еще один видеопоток. Для других участников, равно как и для медиасервера, это будет еще один видеопоток, неотличимый по свойствам от видео с камеры. Звучит просто. Вот только работать такая схема будет не очень.

Если вы будете передавать трансляцию экрана просто как еще одно видео, то картинка получится мыльной, а мелкий шрифт на демонстрируемой странице и вовсе может стать нечитаемым. При демонстрации экрана нам важно сохранить качество картинки. К счастью, иметь высокий FPS при этом хоть и желательно, но не обязательно, ведь картинка на экране преимущественно статична. Поэтому все решения по качественной трансляции экрана основаны на этом трейдоффе: качество видео повышается за счет снижения FPS. В базовом виде это можно реализовать инструментами WebRTC, с использованием так называемого API data channels (видео передается через SCTP), но гиганты индустрии обычно идут еще дальше и в дополнение к data channels реализуют кастомное кодирование потока.

Виртуальные фоны

История виртуальных фонов повторяет историю демонстрации экрана: сделать, чтобы работало, будет легко, но для доведения качества до приемлемого уровня придется постараться.

Процесс генерации виртуального фона состоит из двух этапов:

  1. На кадре из видеопотока выделить силуэт.

  2. Полученный силуэт нарисовать на другом фоне.

Самая простая реализация этого алгоритма в браузере на клиенте выглядит примерно так:

  1.  Разбить исходный поток с камеры на кадры.

  2.  Взять готовую нейросеть для выделения силуэтов, например Google MediaPipe, и выделять через нее силуэт на каждом кадре.

  3. Рисовать силуэт поверх фона, используя canvas.

  4. Соединить полученные кадры в новый поток, который передается на сервер.

Кодится это быстро, у меня написание и отладка заняли пару часов. Нюанс здесь заключается в низкой производительности решения. Дело в том, что для стандартного видео с частотой 25 FPS у нас есть всего 40 ms на обработку одного кадра. Новенькому макбуку разработчика этого будет достаточно, но вот бюджетный смартфон может и не вытянуть. 

Помним, что в видеоконференции у нас и так параллельно кодируются и декодируются несколько дорожек, а тут еще запускается нейросеть — и приложение начинает гонять кадры с видеокарты в память и обратно.

Так почему бы не унести всю вычислительную нагрузку на сервер? Справедливый вопрос, но почему-то никто из известных мне распространенных ВКС так не сделал. Может, жалко серверных мощностей или возникают еще какие-то неочевидные проблемы.

Вместо переноса нагрузки на сервер разработчики ВКС занимаются оптимизацией на клиенте. Первая точка оптимизации — это производительность модели. От Google MediaPipe можно уйти в сторону менее функциональных, но более производительных в задаче выделения силуэта моделей. Вторая оптимизация — использование API WebGL в браузере для рисования прямо на видеокарте.

Немного про шумоподавление

Шумоподавление — обязательная фича для ВКС. По традиции реализовать его можно двумя способами: использовать коробочные средства, чтобы получилось средненько, и вложить ресурс разработки для достижения высокого качества.

В браузеры, например, шумоподавление встроено — для включения нужно указать флаг noiseSupression при вызове getUserMedia(). Флаг поддерживается везде, кроме Safari. Работать будет, но качество получится непредсказуемым. Так что для реализации шумоподавления люди натренировали разные нейронки, например RNNoise и DTLN. Их можно встроить на клиенте.

Для увеличения качества звука нужно использовать VAD — это не только сократит отправляемый на сервер трафик, но и улучшит качество звука. Если в кусочке аудио нет человеческого голоса, то он будет просто проигнорирован, и никакое шумоподавление не потребуется. Реализацию VAD тоже можно либо оставить коробочную, либо подсунуть свою.

Для полноты картины отмечу, что в качественном пайплайне обработки звука также должны присутствовать AEC, вырезающий из аудио то, что момент назад воспроизводилось динамиком, и AGC, который выравнивает громкость участников звонка, чтобы ее не приходилось регулировать вручную.

Запись звонка

О работе с записями видеоконференций нужно знать два важных факта: 

  • Объединение нескольких дорожек в один файл — это всегда затратная по CPU (GPU) операция.

  • Для хранения записей нужно много места. Сначала во время звонка нужно записывать сырые файлы с дорожками, потом надо куда-то складывать итоговый сконвертированный файлик.

Некоторые ВКС грешат записью на клиенте, например Zoom. Кто пользовался их десктопным клиентом, видел окошко с progress bar конвертации исходников в итоговый файл, которое вылезает после завершения звонка.

Серверная запись может работать по-разному. Например, WebRTC-сервер Janus во время звонка дампит дорожки на диск в несжатом виде. Затем их можно самостоятельно сконвертировать с помощью ffmpeg. Такой подход позволяет вынести вычислительно интенсивную операцию конвертации с сервера со звонком на отдельную машину.

Есть и другие подходы к серверной записи. Например, LiveKit зеркалит все дорожки участников на отдельный сервер, на котором происходит конвертация. У этого способа есть один недостаток: по сути, весь трафик в звонке в этом случае удваивается. Нужно пристально следить за утилизацией сетевого канала, для SFU он и так обычно становится бутылочным горлышком.

И, конечно, конвертация видео на GPU работает сильно эффективнее, чем на CPU: в наших тестах прирост скорости был в 11 раз.

Запуск ffmpeg для конвертации видео на CPU станет стресс-тестом для системы охлаждения дата-центра
Запуск ffmpeg для конвертации видео на CPU станет стресс-тестом для системы охлаждения дата-центра

Схемы внедрения ВКС

Есть три варианта внедрить видеозвонки.

Купить лицензию у Zoom или его аналогов. Отечественные решения ищутся по запросу «ВКС для бизнеса». Причем обычно такие ВКС можно не только использовать в облаке, но и установить on-premise. Главный плюс здесь — вы сразу получаете качественное готовое решение, в которое помимо видеоконференции включены другие полезные фичи: трансляции, интеграция с переговорками, возможность рисовать на экране и так далее.

Основных минусов, на мой взгляд, два: лицензии на ВКС дорогие и ограничены коробочным функционалом выбранной ВКС. Не получится бесшовно интегрировать видеозвонок в свой интерфейс или добавить произвольную фичу. Да, у некоторых ВКС есть SDK для встраивания в интерфейсы, но кому-то даже такой гибкости может быть недостаточно. Также при установке on-premise нужно закладывать затраты на железо, потому что проведение звонков и в особенности конвертация и хранение записей — процессы ресурсозатратные.

Важно учитывать, что медиасервера лучше всего работают на «железных» серверах, потому что каждый дополнительный слой виртуализации (гипервизор VM, docker-контейнер) добавляет потенциальные задержки при обработке real-time-медиапотоков. А еще при установке в локальную сеть придется повозиться с настройками NAT-ов и файрволов, отлаживать дропы UDP и так далее — это может надолго занять команду сетевых инженеров.

Распространенная схема внедрения рыночной ВКС к себе в приложение
Распространенная схема внедрения рыночной ВКС к себе в приложение

Установить себе в инфраструктуру open-source-медиасервер, например LiveKit или Jitsi, и использовать на клиентской стороне готовые SDK, которые идут в комплекте. Бонусом обязательно будут накладные расходы на настройку и поддержку инфраструктуры.

У решения будет свой предел гибкости: open-source-проекты заточены под сценарий конференц-звонка на пару десятков участников, и сделать что-то вне этого сценария непросто. Например, сложно будет качественно реализовать звонок с суфлером — участником, который видит и слышит всех в звонке, но его самого слышат только выбранные участники.

Клиентский SDK в любом случае отобразит суфлера всем участникам, так что здесь придется делать логику на стороне клиента, что небезопасно для такой чувствительной фичи и затягивает процессы разработки, если у вас несколько клиентов под разные платформы.

Важно, что у open-source-проектов могут возникать проблемы с качеством и производительностью виртуальных фонов, демонстрацией экрана и шумоподавлением. В SDK этих функций может и не быть, но если они все же есть, то обычно реализованы коробочным способом и поэтому уступают платным аналогам. Еще у таких решений вместимость одной видеоконференции ограничена ресурсами одного сервера, сделать конференцию на 500 участников не получится.

В целом, open-source-медиасервер — хорошее решение, если в приложение нужно быстро и недорого встроить небольшие видеоконференции без претензии на высокое качество и высокую способность к кастомизации.

Обычно open-source-медиасервера предполагают такой вариант использования
Обычно open-source-медиасервера предполагают такой вариант использования

Набраться решимости и написать все самим. Ну, или не совсем все, а написать собственный бэкенд-слой вокруг open-source-медиасервера и сделать свои клиентские SDK. Прежде чем встать на эту скользкую дорожку, нужно трижды подумать, что вам нужно такого, что не может дать условный Jitsi.

На мой взгляд, совпасть должно три главных фактора: полный контроль за качеством решения, возможность встраивать произвольные фичи и наличие бюджета на команду разработки, который будет расти пропорционально требуемой функциональности и качеству. Мы пошли этим путем.

Как реализовано у нас
Как реализовано у нас

Заключение

Небольшой список характеристик, на которые нужно обратить внимание при выборе ВКС:

  • Качество видео. Есть смысл потестировать звонок в условиях плохого интернета и на слабых девайсах, понаблюдать за качеством и синхронизацией аудио с видео в звонке на 100+ участников, если ВКС допускает такие звонки. У SaaS-решений можно поинтересоваться присутствием в регионах: если у хабаровских пользователей звонок будет идти через Москву, то они это заметят и спасибо не скажут.

    При этом нет смысла требовать от ВКС FHD качества: так не может никто в силу физических ограничений пропускного канала со стороны пользователя. Но вот видео 640×480 должно работать стабильно.

  • Качество аудио. Здесь проверяем шумоподавление. Можно провести А/Б-тест с Zoom: провести два звонка в каком-нибудь шумном месте и потом послушать результат на записи.

  • Надежность. При временной недоступности или смене сети на клиенте, например после переключения с Wi-Fi на LTE или поездки в лифте, клиент должен без проблем переподключиться к звонку.

  • Качество демонстрации экрана. Здесь можно провести бенчмарк через показ текстового документа с маленьким шрифтом: полистайте его туда-сюда и сравните качество с Zoom. Отдельно стоит проверить демонстрацию на мобильных клиентах.

  • Качество виртуальных фонов. Опять же, можно сравнить с Zoom: попробуйте разное освещение, разную удаленность от экрана. Как будет вести себя фон, если в кадре два человека? Как увеличивается потребление CPU и GPU в браузере и на десктопном/нативном клиенте?

  • Возможность трансляции (стриминга).

  • Другие фичи. Обычно в рыночных ВКС можно найти ряд полезных опций вокруг функциональности звонка. Например, рисование на экране или в групповом whiteboard, управление удаленным компьютером, возможность дробить одну комнату на несколько или запускать голосовалки. Для корпоративного использования будет полезной интеграция с SSO и Outlook, а также возможность встроиться в переговорки.

    Есть более экзотические фичи — например, возможность подключиться к конференции через GSM-звонок или включить автоматическую транскрипцию текста на записи.

Комментарии (2)


  1. ozzyBLR
    28.06.2024 06:04
    +1

    Кайфанул от статьи, большое спасибо!


    1. ugenef_dev Автор
      28.06.2024 06:04

      Спасибо)