Управление битрейтом и полосой пропускания в звуке реального времени

Автор: Николай СапуновОбновлено: август 202617 мин чтения
Содержание статьи +

Кратко

В живом звонке свободная ёмкость сети меняется каждую секунду, поэтому приложение постоянно угадывает, сколько данных выдержит канал, и подбирает битрейт звука под это. Эту догадку называют оценкой полосы пропускания (bandwidth estimation), а три механизма, которые вы встретите, – это REMB, transport-cc и алгоритм, который их использует, Google Congestion Control. Звук получает небольшую защищённую долю бюджета и сжимается последним – поэтому видео рассыпается на блоки задолго до того, как пропадёт голос. Статья объясняет, как клиент и сервер договариваются о числе, почему звук адаптируется позже видео и как разумно задать минимальный и максимальный битрейт, чтобы звонок оставался разборчивым на плохой связи.

Почему это важно

Любой продукт реального времени – видеозвонок, вебинар, телемедицинский приём, лайв-шопинг – работает поверх сетей, которыми команда продукта не управляет. Канал между двумя людьми общий, перегруженный и изменчивый, а у софта есть лишь несколько сотен миллисекунд на реакцию, прежде чем звонок начнёт звучать сломанно. Если вы создаёте или покупаете такой продукт, вам нужно понимать, кто решает, каким будет битрейт звука, по каким сигналам и какие две ручки (минимальный и максимальный битрейт) реально меняют опыт. Статья написана так, чтобы продакт-менеджер или основатель прошёл всю петлю и задал инженеру правильные вопросы.

Проблема: ёмкость, которую не видно

Представьте однополосную дорогу между двумя городами. В одни моменты она пуста; в другие выезжает грузовик – и всё замедляется. Нельзя позвонить заранее и узнать, насколько дорога загружена прямо сейчас – можно только смотреть, сколько едут ваши машины, и по этому судить о трафике.

Сетевой путь устроен так же. Объём данных, который он переносит в секунду – доступная полоса пропускания, – нигде не публикуется. Он меняется, когда кто-то на том же Wi-Fi запускает загрузку, когда телефон переходит между сотами, когда домашний роутер заполняет очередь. Приложению приходится оценивать ёмкость по косвенным признакам, а затем выбирать скорость отправки, которая помещается под ней. Отправлять слишком много – копится очередь, растёт задержка, потом теряются пакеты; отправлять слишком мало – теряете качество, которое могли бы иметь.

Эту непрерывную петлю «угадай и подстройся» называют управлением перегрузкой (congestion control), а число, которое она выдаёт – лучшую оценку того, сколько бит в секунду путь перенесёт прямо сейчас, – называют оценкой полосы пропускания.

Рис. 1. Замкнутая петля: отправь, измерь, верни обратную связь, переоцени, перераспредели. Кодер звука стоит слева и получает ту долю, что оставил распределитель.

Две подсказки, которые оставляет сеть

Управление перегрузкой читает два сигнала, и хорошие системы читают оба.

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

Вторая подсказка – потери. Когда очередь переполняется, пакеты отбрасываются. Потери – грубый и поздний сигнал: когда вы их видите, ущерб уже нанесён, – но он однозначен. Оценщик на основе потерь реагирует на долю пропавших пакетов: выше ~10% потерь он резко режет скорость, ниже ~2% – осторожно прощупывает вверх, между ними держится.

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

Google Congestion Control: алгоритм в середине

Алгоритм, который объединяет эти подсказки почти в каждом браузере и WebRTC-стеке, – Google Congestion Control, обычно сокращаемый до GCC. Он описан в Internet-Draft IETF draft-ietf-rmcat-gcc-02 (Holmer, Lundin, Carlucci, De Cicco, Mascolo, июль 2016). Заметьте: это черновик (draft), а не готовый стандарт – он истёк, не став RFC, но остаётся фактическим эталоном, потому что рабочая реализация в libwebrtc следует его структуре. Код ушёл вперёд относительно черновика, но двухчастная конструкция – контроллер на основе задержки и контроллер на основе потерь, берущие минимум, – сохранена.

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

REMB, transport-cc и кто считает

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

REMB – Receiver Estimated Maximum Bitrate. Это старая конструкция, предложенная в draft-alvestrand-rmcat-remb-03. Приёмник сам делает расчёт по задержке и отправляет назад одно RTCP-сообщение, которое, по сути, говорит: «всего на этом пути я могу принять N бит в секунду». Отправитель доверяет этому числу и ограничивает им свою отдачу. REMB опирается на RTP-расширение заголовка absolute send time, чтобы приёмник знал, когда каждый пакет действительно ушёл.

transport-cc – Transport-Wide Congestion Control. Это новая конструкция, предложенная в draft-holmer-rmcat-transport-wide-cc-extensions-01. Здесь роли меняются местами. Отправитель помечает каждый исходящий пакет – и звук, и видео – единым сквозным (transport-wide) порядковым номером. Приёмник почти не думает: он просто сообщает для каждого номера, когда пакет пришёл. Вся оценка происходит у отправителя. Подход «держи приёмник простым» теперь стандарт по умолчанию в современном WebRTC, потому что у отправителя полная картина и он реагирует быстрее.

Позже IETF стандартизировал чистый формат обратной связи для той же идеи в RFC 8888 (RTP Control Protocol (RTCP) Feedback for Congestion Control, январь 2021), который регистрирует сообщение обратной связи управления перегрузкой и является спецификационным преемником черновика transport-cc. RFC 8888 также отмечает реальный компромисс: поскольку обратная связь сквозная и валит все потоки в кучу, труднее понять, какой поток – звук или видео – действительно потерял пакеты, поэтому решения о починке по потокам требуют дополнительной осторожности.

Рис. 2. Сдвиг от оценки на стороне приёмника (REMB) к оценке на стороне отправителя (transport-cc). Цель та же, место расчёта противоположное.

Почему звук адаптируется позже видео

Вот вопрос, который рано или поздно задаёт каждый инженер: звонок портится, видео превращается в кашу, а голос держится – почему?

Ответ – приоритет распределения битрейта (bitrate allocation priority). Когда распределитель делит цель GCC между потоками, звук обслуживается первым и защищён минимумом. Голосовому звонку нужно очень немного, чтобы оставаться разборчивым: кодек Opus, на котором работает почти весь звук WebRTC, идёт от низкого минимума до высокой верности, и чистый голосовой звонок комфортно живёт в диапазоне 16–40 kbps. Видео же прожорливо – оно с пользой поглощает сотни и тысячи kbps и деградирует плавно, когда его душат.

Поэтому логика распределителя проста и намеренна: дай звуку его небольшую защищённую долю, а остаток отдай видео. Когда общая оценка падает, видео сжимают в широком диапазоне, а звук остаётся нетронутым, пока оценка не упадёт к собственному минимуму звука. Лишь на по-настоящему сломанном канале – когда весь путь не тянет даже десятки kbps – звук наконец начинает снижать скорость, переходит в самый агрессивный низкобитрейтный режим и опирается на маскировку потерь пакетов (PLC), чтобы заполнить пробелы.

Этот порядок верен. Люди терпят замёрзшее или блочное видео гораздо лучше, чем рваную, пропадающую речь. Разговор выживает на звуке; без него он гибнет.

Есть и вторая причина, почему звук кажется медленнее в реакции: звук несёт меньше пакетов в секунду, чем видео, поэтому у петли обратной связи меньше точек данных, и оценщик обновляет ситуацию по звуку реже. Защита в основном намеренная, но более редкая обратная связь её усиливает.

Как Opus на самом деле меняет битрейт

Оценка полосы полезна лишь если кодек может действовать по ней мгновенно, и Opus построен именно для этого. По RFC 6716 (спецификация Opus, сентябрь 2012, обновлена RFC 8251) Opus поддерживает любой битрейт от 6 kbps до 510 kbps и может менять скорость покадрово – каждые 20 миллисекунд – без щелчка, провала и без необходимости пересогласовывать сессию. Когда распределитель выдаёт Opus новую цель, следующий кадр просто выходит на новой скорости.

Opus по умолчанию работает в переменном битрейте (VBR), где каждый кадр использует столько бит, сколько нужно звуку в нём: тишина и простые тоны – мало, сложная речь и музыка – больше, ради лучшего качества при заданном среднем. Он поддерживает и постоянный битрейт (CBR), где каждый кадр одного размера. CBR редко уместен для звонков; RFC 6716 отмечает, что его два реальных применения – транспорты, требующие фиксированного размера кадра, и определённые сценарии шифрования. Для звука реального времени почти всегда нужен VBR – пусть распределитель двигает цель.

Разберём пример. Пусть общая оценка GCC падает до 250 kbps на ухудшающемся канале. Распределитель защищает звук, скажем, минимумом 24 kbps и отдаёт остаток видео:

общая оценка          = 250 kbps
минимум звука (Opus VBR) = 24 kbps   ← защищён, обслужен первым
видео получает         = 250 − 24 = 226 kbps

Если канал просядет дальше до 40 kbps, звук всё равно получит свои 24 kbps, а видео останется 16 kbps – едва слайд-шоу, но голос цел. Опустите оценку ниже ~24 kbps – и только тогда Opus начнёт снижаться, в итоге сужая полосу и битрейт звука, чтобы хоть что-то текло.

Как разумно задать минимальный и максимальный битрейт

У вас две ручки, которые действительно формируют опыт, и большинство команд оставляют их по умолчанию – и зря.

Максимальный битрейт ограничивает, насколько хорошим может стать звук при щедрой сети. Для голосового продукта ограничение Opus около 32–40 kbps не тратит ничего на неслышимое качество и освобождает полосу под видео или под больше участников. Для музыкального или высоко-верного продукта поднимите его к 64–128 kbps, чтобы хороший канал действительно использовался.

Минимальный битрейт задаёт минимум, который распределитель защитит – скорость, ниже которой звук не пойдёт. Слишком высокий – слабый канал, который мог бы нести тонкий голосовой поток, вместо этого роняет звонок; слишком низкий – звук портится раньше, чем нужно. Минимум около 16 kbps держит речь разборчивой на плохих каналах; не опускайте голос ниже ~12 kbps, не проверив, как это звучит.

СценарийРекоменд. min OpusРекоменд. max OpusПочему
Голосовой звонок / телемед16 kbps32–40 kbpsРазборчивый голос; место под видео и устойчивость
Вебинар / один-ко-многим12–16 kbps32 kbpsМного слушателей; защити минимум, ограничь потолок
Музыка / высокая верность24 kbps64–128 kbpsХороший канал должен звучать отлично
Конференция с шарингом экрана16 kbps40 kbpsМинимум звука защищён; полоса уводится на экран

Задайте обе при настройке сессии; минимум – то, что распределитель защищает, когда сеть зажимается.

Частая ошибка: добавление избыточности в перегрузку

Самая дорогая ошибка здесь – бороться с потерями добавлением данных, когда потери вызваны как раз избытком данных. Если пакеты теряются из-за перегрузки пути, включение коррекции ошибок (FEC) или избыточности гонит больше бит в и без того полную трубу и делает перегрузку – и потери – хуже. Управление полосой идёт первым: подведите скорость под оценку, дайте очереди слиться, и только потом решайте, случайны ли оставшиеся потери (от них стоит защищаться) или они вызваны перегрузкой (что защитой не исправить). Порядок всегда такой: сначала оценка, потом защита.

Где здесь Фора Софт

Мы строим системы звука и видео реального времени – видеоконференции, телемедицину, онлайн-обучение, лайв-стриминг и видеонаблюдение, – где звонок должен выжить на любой сети, что попалась пользователю. Настройка минимума и потолка битрейта звука, выбор между REMB и transport-cc на конкретном стеке и приучение распределителя защищать голос раньше видео – те неброские решения, что отличают продукт, удерживающий разговор на гостиничном Wi-Fi, от того, что его роняет. Мы поставляем такие системы в конференциях, OTT и телемедицине с 2005 года – в браузерах, нативных приложениях и SFU.

Главное

  • Полоса невидима; софт оценивает её по задержке и потерям пакетов.
  • Google Congestion Control объединяет оценщик задержки и оценщик потерь, берёт меньший.
  • REMB считает оценку у приёмника; transport-cc переносит её к отправителю и теперь стандарт.
  • Звук получает небольшой защищённый минимум и сжимается последним – поэтому видео ломается первым.
  • Opus меняет битрейт каждые 20 мс в диапазоне 6–510 kbps без пересогласования.
  • Задайте разумный минимум (≈16 kbps) и максимум (32–40 kbps для голоса) вместо значений по умолчанию.

Что почитать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.