Publish и Play
Существует две основных функции работы WebRTC на стороне сервера в области потокового видео: публикация и воспроизведение. В случае публикации видеопоток захватывается с вебкамеры и двигается от браузера к серверу. В случае воспроизведения, поток двигается в обратном направлении — от сервера к браузеру, декодируется и воспроизводится в браузерном HTML5 <video> элементе на экране устройства.
UDP и TCP
Видео может двигаться по двум транспортным протоколам: TCP или UDP. В случае UDP активно работают RTCP фидбеки NACK, которые несут информацию о потерянных пакетах, в связи с чем, определение ухудшения UDP канала является достаточно простой задачей и сводится к подсчету NACK (Negative ACK) для определения качества. Чем больше фидбеков NACK и PLI (Picture Loss Indicator), тем больше реальных потерь и тем хуже качество канала
TCP без NACK
В данной статье нас будет больше интересовать протокол TCP. При использовании WebRTC через TCP, RTCP-фидбеки NACK не отправляются, а если и отправляются, то не отражают реальной картины по потерям, и выполнить определение качества канала по фидбекам не представляется возможным. Как известно, TCP является транспортным протоколом с гарантированной доставкой. По этой причине, в случае ухудшения канала, пакеты, находящиеся в сети, будут досылаться на уровне транспортного протокола. Рано или поздно они будут доставлены, но на эти потери не будет сгенерирован NACK, т.к. потерь по факту не было. Пакеты в итоге дойдут с запозданием. Опоздавшие пакеты просто не соберутся в видеофреймы и будут сброшены депакетизатором, в результате чего пользователь увидит примерно такую картинку, полную артефактов:
По фидбэкам будет все хорошо, но на картинке будут артефакты. Ниже представлены скриншоты трафика Wireshark, которые иллюстрируют поведение публикуемого стрима на зажатых TCP и UDP каналах, а также скриншоты статистики Google Chrome. На скриншотах видно, что в случае TCP не растет метрика NACK в отличии от UDP несмотря на то, что с каналом все очень плохо.
TCP
UDP
А зачем вообще стримить по TCP если есть UDP
Резонный вопрос. Ответ: для проталкивания больших разрешений через канал. Например при стриминге VR (Virtual Reality) разрешения могут начинаться с 4k. Протолкнуть поток такого разрешения и битрейта около 10 Mbps в обычный канал без потерь не представляется возможным, сервер выплевывает UDP пакеты и они начинают пачками теряться в сети, потом досылаться, и т.д. Большие сбросы видеопакетов портят видео, и в конечном итоге с качеством становится все плохо. По этой причине для сетей общего назначения и высоких разрешений Full HD, 4k, для доставки видео используется WebRTC через TCP чтобы исключить сетевые потери пакетов ценой некоторого увеличения коммуникационной задержки.
RTT для определения качества канала
Таким образом, нет метрики, которая гарантировано скажет, что с каналом все плохо. Отдельные разработчики пытаются опираться на метрику RTT, но она работает далеко не всех браузерах и не дает точных результатов.
Ниже иллюстрация зависимости качества канала от RTT по версии проекта callstat
Решение на REMB
Мы решили подойти к этой проблеме немного с другой стороны. На стороне сервера работает REMB, который подсчитывает входящий битрейт для всех входящих потоков, вычисляет его отклонение от средней и в случае большого разброса, предлагает браузеру понизить битрейт, отправляя специальные REMB-команды по протоколу RTCP. Браузер получает такое сообщение и снижает битрейт видео энкодера для рекомендуемых значений — так работает защита от перегрузки канала и деградации входящих потоков. Таким образом на стороне сервера уже был реализован механизм подсчета битрейта. Усреднение и определение разброса реализовано через фильтр Калмана. Это позволяет снимать актуальный битрейт в любой момент времени с высокой точностью и фильтровать сильные отклонения.
У читателя наверняка возникнет вопрос — “Что даст знание битрейта, который видит сервер на входящем в него потоке?”. Это даст понимание ровно того, что на сервер заходит видео с битрейтом, значение которого удалось определить. Чтобы оценить качество канала потребуется еще одна компонента.
Исходящий битрейт и почему он важен
Статистика публикующего WebRTC потока показывает с каким битрейтом видеопоток выходит из браузера. Как в бородатом анекдоте: Админ, проверяя автомат: «С моей стороны пули вылетели. Проблемы на вашей стороне..». Идея проверки качества канала заключается в сравнении двух битрейтов: 1) битрейт, который отправляет браузер 2) битрейт, который реально получает сервер.
Админ выпускает пули и подсчитывает скорость их вылета у себя. Сервер подсчитывает скорость их получения у себя. Есть еще один участник этого мероприятия, TCP — это супергерой, который находится посередине между админом и сервером и может замедлять пули случайным образом. Например, может затормозить 10 случайных пуль из ста на 2 секунды, а потом позволит им снова лететь. Вот такая Матрица.
На стороне браузера мы берем актуальный битрейт из статистики WebRTC, далее сглаживаем график фильтром Калмана в JavaScript реализации и на выходе получаем сглаженную версию клиентского браузерного битрейта. Теперь мы имеем практически все необходимое: клиентский битрейт говорит нам как уходит трафик из браузера, а серверный битрейт рассказывает, как сервер этот трафик видит и с каким битрейтом получает. Очевидно, если клиентский битрейт остается высоким, а серверный битрейт начинает проседать по отношению к клиентскому, это означает что не все “пули долетели” и сервер фактически не видит часть трафика, который был ему направлен. На основании этого делаем вывод, что с каналом что-то не так и пора переключать индикатор в красный цвет.
Еще не все
Графики коррелируют, но немного сдвинуты во времени друг относительно друга. Для полной корреляции необходимо совместить графики во времени чтобы сравнивать клиентский и серверный битрейт в одну и ту же точку времени на исторических данных. Выглядит рассинхрон примерно так:
А так выглядит синхронизированный по времени график.
Тестируем
Дело за малым, осталось протестировать. Публикуем видеопоток, открываем и смотрим график публикуемых битрейтов: на стороне браузера и на стороне сервера. По графикам видим практически идеальное совпадение. Индикатор так и называется PERFECT.
Далее начинаем портить канал. Для этого можно использовать такие бесплатные инструменты «winShaper» на Windows или «Network Link Conditioner» на MacOS. Они позволяют зажать канал до установленного значения. Например, если мы знаем, что поток 640×480 может разогнаться до 1Mbps, зажмем его до 300 kbs. При этом помним, что работаем с TCP. Проверяем результат теста — на графиках обратная корреляция и индикатор падает в BAD. Действительно, браузер продолжает высылать данные и даже наращивает битрейт, пытаясь протолкнуть в сеть новую порцию трафика. Эти данные оседают в сети в виде ретрансмитов и не доходят до сервера, в результате сервер показывает обратную картину и говорит, что битрейт, который он видит упал. Действительно BAD.
Мы провели достаточно много тестов, которые показывают корректную работу индикатора. В итоге получилась фича, которая позволяет качественно и оперативно информировать пользователя о проблемах с каналом как для публикации потока, так и для воспроизведения (работает по тому же принципу). Да, ради этой зелено-красной лампочки PERFECT-BAD мы и городили весь этот огород. Но как показывает практика, этот индикатор очень важен и его отсутствие и непонимание текущего статуса может сильно испортить жизнь рядовому пользователю сервиса видеостриминга через WebRTC.
Ссылки
Документация по контролю качества канала при публикации и воспроизведении