Как улучшить качество WebRTC-звонков на примере сервиса VK Звонки

aafe206da5fe9f4df4adbfef81d4b416.png

Привет! Я Иван Шафран, уже 4 года работаю с WebRTC, ВКонтакте руковожу командой звонков на Android. В этой статье на примере VK Звонков расскажу, что можно сделать, чтобы улучшить качество сервисов для аудио- и видеосвязи. Обсудим достоинства и недостатки WebRTC. Расскажу, как работать с аудио, видео и режимом демонстрации экрана и какие есть варианты сбора статистики.

Статья написана на основе доклада «VK Звонки: Поднимаем планку качества WebRTC-звонков» на Mobius Spring 2024.

Какие функции есть в VK Звонках 

VK Звонками пользуются около 20 млн человек в месяц. У нас нет ограничений по числу участников и продолжительности встречи. В сервисе реализованы совместный просмотр видео, демонстрация экрана в 4К, анимированные аватары, умное шумоподавление и AR-технология замены фона. Есть и стандартный набор инструментов: трансляции, планирование, администрирование и запись звонков.

Но все эти возможности не имеют смысла, если в звонке кого-то не слышно, видео тормозит, а демонстрация экрана размыта. Рассмотрим несколько приёмов, которые помогут этого избежать.

Пользователи, которые столкнулись с плохим качеством звонков

Пользователи, которые столкнулись с плохим качеством звонков

WebRTC

Наши звонки работают на технологии WebRTC. С 2011 года она используется в проекте Chromium от Google. Позднее появилась в Яндекс Браузере, Google Chrome, Microsoft Edge, Safari и Mozilla. 

SDK WebRTC доступен на iOS, Android и десктопных операционных системах. При желании инструмент можно встроить даже в холодильник или робот-пылесос. 

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

Качество и оптимизация

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

Аудио

Главное, без чего звонок не состоится. Пользователи готовы потерпеть временную потерю видео, но не звука.

Подсветка говорящего

Посмотрите на изображение ниже. Как понять, что в звонке сейчас кто-то говорит? В этом помогает подсветка говорящего на экране.

a1ce81d7e9b844454338e2c6830423b0.png

Стандартный приём — обводка говорящих: например, зелёным цветом, как на изображении ниже.

65ffcef784afb5e0107d36280612a640.png

У WebRTC есть набор технических характеристик соединения, что позволяет узнать степень громкости каждого аудиопотока и идентифицировать говорящего. Для этого используется класс RTCAudioSourceStats и его поле audioLevel.

32d9483d74cabb276b6eabe3ccd92b29.png

На графике видно, что громкость аудиопотока — это не бинарный признак, и чаще всего её уровень оказывается выше нуля из-за фонового шума.

60f886a8815557550286189e83aeb2bc.png

Исправить эту проблему можно, считывая средний уровень громкости на протяжении звонка. Например, используя «скользящее среднее», чтобы подстроиться под изменяющиеся условия. На практике это работает хорошо, но вы также можете подключить детектор голоса (Voice Activity Detector) из поставки WebRTC. По умолчанию он работает в пайплайне на аудиовыходе, но его также можно использовать для входа. В групповых звонках такую информацию эффективнее будет подсчитывать на бэкенде, чтобы снизить нагрузку на клиента.

8e51b0668a487dc25f5fbd4bbdf2c094.png

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

Разрешения для звонков на Android

Для работы звонков могут понадобиться следующие разрешения:

• доступ к аудио;

• доступ к видео;

• демонстрация экрана;

• доступ к памяти устройства;

• доступ к подключённым Bluetooth-устройствам.

Все разрешения, кроме аудио, можно раздать по мере необходимости. Так пользователю не придётся отвечать на множество запросов при старте звонка. Если же разрешить доступ к аудио при включении микрофона по кнопке, мы начнём терять соединение на другой стороне.

9dc32b7af0cbebb2ad7610527195d38b.png

Всё из-за того, что мы используем аудиоканал, чтобы определить проблемы с сетью. Если аудиоданные не идут, значит, что-то не так.

Чтобы исправить проблему, мы:

• сделали форк WebRTC;

• нашли класс org.webrtc.audio.WebRtcAudioRecord;

•стали отправлять тишину до получения разрешения на использование микрофона.

Аудиокодеки

Аудиокодеки для WebRTC просты в использовании. Есть бесплатный открытый кодек Opus, который подходит для работы с разными битрейтами.

cf5ce2fe2c7adb45e0457a1c3d0d5545.png

В этом кодеке есть функция исправления будущих потерь пакетов — FEC, или Forward Error Correction. Принцип её работы в том, что к аудиопакетам добавляются фрагменты предыдущих пакетов. Если что-то потеряется, мы сможем восстановить аудио.

c1682fbec1568c05d3464cf5045924cc.pngcf4c1b6890dea93b79e8bfffcd2fb838.png

Это полезный механизм, однако стандартный FEC имеет ряд недостатков:

• включается, когда потери уже пошли;

• имеет ограничения по доле в пакетах;

• сам учитывается как часть битрейта и затрудняет настройку.

В качестве альтернативы FEC можно попробовать механизм RED — Redundant Coding, то есть излишнее кодирование.

Принцип его работы схож с FEC. RED улучшает качество звука, добавляя целые предыдущие пакеты в аудиоканал, хотя это более низкое качество по сравнению с видео и возможностями современных сетей. Тем не менее RED отлично проявляет себя в сетях с большой долей потерь.

d1f082aec6c972966ad7b4f72a4917dc.png

На графике выше приведены данные по количеству синтезированных аудиопакетов. WebRTC формирует пакеты, если произошла потеря, компенсируя заметные «разрывы» речи.

08538f9faea9f90b9430fed955ecf7cf.png

При A/B-тестировании мы также заметили существенное снижение качества синтезированных аудиопакетов. Другие метрики изменились не сильно.

Видео

Видео — второй по важности канал связи между пользователями.

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

95418130784a27eb73b4f1fb89f5e2f8.png

Базовые настройки

Кодеков для видео больше, чем для аудио. Самым распространённым и надёжным считается устаревший H.264. Он поддерживается на большинстве устройств.

Ещё одна базовая настройка, на которую стоит обратить внимание, — разрешение. Десктопам и мобильным устройствам достаточно разрешений 720p, HD или ниже. Если вы снизите разрешение, это позволит оптимизировать весь пайплайн — от захвата кадров камерой до отправки их через сеть. Не стоит пренебрегать этой настройкой, если ваш сценарий использования сервиса это позволяет.

Эксперименты с кодеками

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

У более новых популярных кодеков лучше качество передачи. Они также лучше сжимают в плохих сетях. Однако у их новизны есть минус: на большинстве Android-устройств есть только их программная реализация, которая выполняется на ЦПУ. Аппаратная поддержка — кодирование и декодирование на выделенном чипе — есть не у всех устройств.

Мы можем использовать новые аппаратные кодеки, если они доступны. Если нет, то используем новые кодеки в программном виде при плохой пропускной способности сети. В последнем варианте картинка будет лучше, но батарея станет садиться быстрее. Нагрузку на центральный процессор можно дополнительно облегчить, если уменьшить частоту кадров и снизить разрешение. Это будет выглядеть лучше, чем «рассыпанный» кадр от H.264 при плохой сети.

72f4d81d3f338348458f6381a7fc203b.png

Проблемы больших звонков

При звонках один на один почти все функции без ошибок работают «из коробки». Но если на экране появляется более шести видео, могут начаться проблемы с аппаратными кодеками. Они реализованы на чипе и имеют ограничения по одновременному количеству декодеров. Таким образом, придётся обратиться к программному декодированию после превышения определённого числа видео в звонке.

b6347f50d14a82e32ea6291c478379e4.png

Если же мы пойдём ещё дальше, то возникнут проблемы со стороны OpenGL. Закончатся так называемые контексты, которые отвечают за состояние движка. Они нужны для работы на потоках, где выполняются вычисления OpenGL. Как правило, на устройствах доступно около 30 контекстов. На каждое видео по умолчанию выделяется два OpenGL-контекста — на декодирование и на отрисовку. Мы сможем увидеть около 10 видео вместо возможных 15 из-за запоздания с «релизом» контекстов и их использованием при кодировании.

Для решения проблемы с контекстами можно переписать рендеринг — так, чтобы он выполнялся на одном потоке для всех видео.

Для больших звонков на 100 и более участников есть широкий простор для оптимизаций. Решения для VK Звонков мы разбирали на Mobius. Посмотреть видео можно по ссылке.

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

Демонстрация экрана — одна из самых востребованных дополнительных функций звонка. В зависимости от сценария на первом месте для вас может быть плавность картинки и игрового контента либо чёткость текста и графики для презентаций. 

У WebRTC на Android уже есть класс для демонстрации экрана ScreenCapturerAndroid. Однако он работает посредственно для обоих сценариев. Сложнее всего с ним добиться чёткого текста. Также отсутствует возможность передать аудиодорожку с телефона. Обсудим варианты решения этих проблем.

Передача аудио

По умолчанию WebRTC сама выделяет медиаканалы для микрофона, камеры и демонстрации экрана. При этом канала для аудио с телефона нет. Это может быть связано с тем, что API для доступа к такому аудио появился лишь в Android 10, а WebRTC под Android реализовали задолго до этого.

Как и с демонстрацией экрана, для получения аудио нужно запросить разрешение у пользователя. Используйте API по ссылке.

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

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

Второй способ — выделить отдельный медиаканал и поддерживать его приём на всех клиентах. Для групповых звонков VK Видео мы миксуем всё аудио на сервере и получаем один аудиопоток.

Тест на зрение

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

Подсказки для WebRTC

Во фреймворке WebRTC на случай проблем с чёткостью картинки предусмотрены подсказки. С помощью ContentHint можно указать, что мы ожидаем увидеть текст, используя команду TEXT. С помощью DegradationPreference можно указать на необходимость сохранять разрешение MAINTAIN_RESOLUTION и возможность жертвовать частотой обновления кадров.

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

Своя версия демонстрации экрана 

Если результат демонстрации экрана нас не устраивает, можно написать свою версию. Самое сложное возьмут на себя готовые библиотеки.

В решении будет две подсистемы. Одна станет отправлять кадры, вторая — получать и отображать. Полный путь данных выглядит так:

• получаем кадр с камеры (работает в рамках старой логики);

• кодируем кадр кодеком;

• нарезаем кодированный кадр на пакеты для отправки;

• отправляем пакеты;

• другая сторона принимает пакеты;

• из этих пакетов собирается кодированный кадр;

• используется кодек для декодирования;

• кадр передаётся в пайплайн обработки;

• данные отображаются на экране.

Для кодирования и декодирования кадров будем использовать класс MediaCodec, запросив у него кодек Vp9. У этого кодека есть два вида API: синхронный и асинхронный. Можно использовать любой подход, но в случае асинхронного проще следить за тем, успевает ли кодек справляться с работой, — он сам выдаёт входные и выходные буферы по готовности.

Для передачи пакетов будем использовать WebRTC DataChannel, так как медиатреки недоступны в самописном варианте передачи. Дата-канал можно представить как протокол WebSocket, но с возможностью передавать бинарные данные и устанавливать очередь отправки.

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

В итоге получим чёткий текст на видео. 

Статистика

Рекомендую замерять все эксперименты с аудио, видео или демонстрацией экрана. Без замеров мы не узнаем, улучшилось качество звонков или стало хуже. 

Локальная отладка

Для локальной отладки можно воспользоваться логами WebRTC. Приятная опция — наличие встроенной страницы с отладкой в браузерах Сhromium. Чтобы увидеть эту информацию в Google Chrome, откройте страницу по адресу chrome://webrtc-internals. Параллельно запустите VK Звонки. Для теста создайте пустой звонок, а затем откройте ссылку в режиме инкогнито. Тогда в звонке будет два участника, установится соединение и на странице отладки появятся графики по параметрам соединения.

26d5d18e7df66a10afe9cc2b13cfbde9.png

Замеры на большой аудитории

Для снятия метрик медиатреков в WebRTC доступны методы getStats.

Один из методов возвращает набор готовых к использованию метрик, но может отличаться в зависимости от браузера. Для Android это не будет проблемой. Однако стоит учитывать, что метод получил отметку «устаревший» и будет удалён в новых версиях. Нам стоит его избегать.

Второй метод getStats стандартизирован, хотя на практике не всё реализовано в точном соответствии с документацией. Актуальное описание характеристик можно найти здесь. В отличие от старого метода здесь есть «сырые» данные, которые нужно самим комбинировать в осмысленные метрики. Также нужно быть аккуратными с типами данных. Они приходят в виде строк и могут быть как Int, Long, Float, так и BigInteger в зависимости от отправленных пакетов.

Выводы

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

Habrahabr.ru прочитано 13766 раз