Содержание статьи +
- Кратко (TL;DR)
- Зачем это знать
- Петля управления в одном абзаце
- Почему медиа-устройству нужен собственный контроллер перегрузки
- Сигнал: что несёт каждый пакет
- Математика: как времена прихода превращаются в битрейт
- Probing: третий приём
- Альтернативы: SCReAM и NADA
- Что на самом деле делает энкодер с этим числом
- Что можно прочитать из `getStats()`
- Где это ломается в реальных деплоях
- Фронтир 2026: машинное обучение, L4S и ИИ-агенты
- Где Фора Софт в этом
- Ключевые выводы
- Что читать дальше
Кратко (TL;DR)
Каждый вызов WebRTC незаметно для пользователя гоняет петлю обратной связи: десять-двадцать раз в секунду она спрашивает у сети «сколько ты сейчас способна провезти?» – и подгоняет целевой битрейт энкодера под ответ. То, что стандарты называют контролем перегрузки (congestion control), а реализации – оценкой пропускной способности (bandwidth estimation), и есть эта петля. В libwebrtc (тот самый C++-движок внутри Chrome, Edge, большинства серверных SFU и почти любого нативного мобильного SDK) петлю выполняет Google Congestion Control – сокращённо GCC: задержно-ориентированный (delay-based) оценщик читает времена прихода пакетов через Transport-Wide CC Feedback, а потерь-ориентированный (loss-based) оценщик читает долю потерь; финальный битрейт – минимум из двух. У IETF есть два альтернативных алгоритма – SCReAM (RFC 8298) и NADA (RFC 8698), – но в продакшен-браузерах они почти не встречаются; основное движение в 2026 году идёт в новых форматах фидбэка (RFC 8888 CCFB, ECN/L4S). Эта статья проходит петлю целиком: какие пакеты несут сигнал, какие RTCP-сообщения возвращают обратную связь, как из таймстампов получается битрейт, как энкодеру говорят «притормози», и где петля ломается в реальных сетях.
Зачем это знать
Если вы разрабатываете, используете или приобретаете WebRTC-продукт – видеоконференцию, телемедицину, e-learning, контакт-центр с видео, live-шоппинг, инструмент для удалённой совместной работы или голосового ИИ-агента, – пользовательский опыт в значительной степени зависит от того, насколько эффективно работает BWE на самых слабых сетях вашей аудитории. Когда оценщик перебарщивает с битрейтом, кадры начинают подвисать, звук трещит, а индикаторы качества соединения загораются красным. Когда же он занижает – изображение остаётся размытым, скриншот нечётким, а вся комната воспринимается как «дешёвая», хотя на самом деле пропускной способности сети было с запасом. Инженеры настраивают эту систему годами, а продуктовые менеджеры используют результат десятилетиями. Цель статьи – дать продуктовому менеджеру и архитектору ту же ментальную модель, что и у WebRTC-инженера: что измеряет система, какие задачи решает, где действует осторожно, где ошибается и какие параметры можно изменить.
Петля управления в одном абзаце
Представьте, что вы наливаете воду в шланг, а конец его не видите. Начинаете осторожно. Через мгновение друг на дальнем конце кричит: вода дошла – вовремя, с задержкой или вообще не дошла. Если ответы чёткие – увеличиваете напор; если с опозданием – сразу снижаете. WebRTC-отправитель делает то же самое десять-двадцать раз в секунду, только вместо воды – пакеты, а вместо друга – RTCP-сообщение обратной связи. Отправитель присваивает порядковый номер каждому исходящему RTP-пакету, получатель отвечает компактным RTCP-отчётом с этими номерами и временными метками прихода, а контроллер перегрузки на стороне отправителя сравнивает время прихода с временем отправки. Если разница между отправкой и получением растёт – сеть перегружена, нужно снизить нагрузку. Если разница стабильна и потерь нет – сеть свободна, можно аккуратно увеличить скорость. Эта обратная связь и есть основа всей системы. Всё остальное в статье – детали того, как эта система работает в условиях реальных потерь, джиттера мобильного радио, событий NAT-ребиндинга, кросс-трафика и десятитысячестрочной кодовой базы внутри каждого билда Chromium. (Транспортный контекст подробнее раскрыт в TCP и UDP в стриминге и Контроль перегрузки простыми словами: BBR, CUBIC, Copa.)
Почему медиа-устройству нужен собственный контроллер перегрузки
Веб-страница, скачивающая файл по HTTP, использует TCP и наследует его механизм контроля перегрузки – обычно CUBIC, иногда BBR или Reno (см. подробный разбор в Контроль перегрузки простыми словами: BBR, CUBIC, Copa). WebRTC-вызов делает иначе – и делает это осознанно. Дело в том, что TCP реагирует на потерю пакета ретрансляцией, и при этом новые данные ждут восстановления старых – возникает head-of-line blocking. Для загрузки файла это допустимый компромисс: лучше медленная, но полная передача, чем повреждённая. А вот для реального времени – неприемлемый: задержка в 200 мс из-за ретрансляции уже делает звук рваным, а видео – заметным по скачкам, а задержка в секунду полностью разрушает разговор. Поэтому медиа в WebRTC передаётся по UDP, упаковано в RTP с шифрованием SRTP (см. DTLS, SRTP, TLS, mTLS – шифрование медиа), а контроль перегрузки ложится на движок WebRTC, а не на TCP. Задача контроллера – определить, с какой скоростью приложение может отправлять медиа, и приложение ему подчиняется: снижает целевой битрейт кодировщика, отбрасывает слои simulcast или замедляет поток пакетов в пейсере.
Размен у такого контроллера отличается от TCP. TCP оптимизирует пропускную способность – передаёт как можно больше байт. Контроллер WebRTC, напротив, стремится к низкой сквозной задержке при приемлемом качестве, даже если это означает, что часть пропускной способности остаётся неиспользованной. Именно поэтому стандартный стек WebRTC не так активно «давит» на узкое место, как параллельная загрузка файла: цели у видеозвонка и передачи данных разные.
Сигнал: что несёт каждый пакет
Чтобы петля работала, отправителю нужно, чтобы получатель мог определить, какой пакет пришёл и когда. Порядковые номера RTP существуют (16 бит, per-SSRC, определены в RFC 3550) и позволяют обнаруживать потери, но они не дают получателю использовать единую временную шкалу для нескольких медиапотоков в одном транспорте. В типичном звонке отправитель передаёт аудиопоток и видеопоток – иногда в формате simulcast в трёх разрешениях, иногда трек скриншота, иногда слои SVC – а контроллеру нужно анализировать всё это вместе, поскольку у них общий сетевой узкое место. Именно эта задача решается с помощью расширения заголовка RTP для транспортного управления перегрузкой (Transport-Wide Congestion Control).
Порядковый номер на уровне транспорта
Отправитель добавляет к каждому исходящему пакету соединения RTP расширение заголовка с именем transport-wide-cc-02. В этом расширении содержится 16-битный номер, который увеличивается на единицу для каждого отправленного транспортного пакета – независимо от SSRC. Формат описан на сайте проекта WebRTC: transport-wide-cc-02 – два байта на ID и длину расширения, два байта на transport sequence number, итого четыре байта на пакет. Поскольку нумерация ведётся на уровне транспорта, а не потока, получатель формирует единый временной ряд по приходу пакетов для аудио, видео, simulcast-слоев и любого другого SSRC, использующего общий транспорт. Отправитель же может анализировать один сетевой путь, а не отдельные пути по SSRC.
Предшествующее расширение abs-send-time (abs-send-time на сайте WebRTC) передаёт время отправки от отправителя с разрешением 24 бита и используется в REMB-обратной связи (описана ниже). Современные отправители почти всегда согласуют оба параметра: abs-send-time – для совместимости с приёмниками, поддерживающими REMB, и transport-wide-cc-02 – для использования современного пути обратной связи. Оба параметра согласуются через SDP; если получатель не указывает поддержку transport-wide-cc-02, отправитель переходит на старый REMB-путь.
Обратная связь от получателя
Получатель отправляет обратно RTCP-пакет, в котором перечисляет для группы transport-номеров: когда каждый пакет пришёл (относительно базового времени) и пришёл ли он вообще. Формат – Transport-Wide CC Feedback: разработка Google, не стандартизированная как RFC, но описанная в draft-holmer-rmcat-transport-wide-cc-extensions-01 и реализованная во всех решениях, основанных на libwebrtc. Частота обратной связи настраивается, но обычно составляет около 50 мс для видео – при этом transport-номера группируются по интервалу. На выходе получается высокочастотный временной ряд, по которому отправитель может корректировать свою работу.
Второе, более старое сообщение обратной связи – Receiver Estimated Maximum Bitrate, REMB (определено в draft-alvestrand-rmcat-remb-03) – до сих пор используется некоторыми приёмниками. В случае с REMB получатель сам запускает delay-оценщик и возвращает одно число: «не отправляй больше X бит в секунду на этот поток». Отправитель подчиняется. В случае с Transport-Wide CC получатель отправляет только таймстампы, а оценка производительности выполняется уже на стороне отправителя. Современный стандарт – именно такой подход: у отправителя есть дополнительная информация, которой нет у получателя (например, что пытался отправить энкодер, был ли в сети пробный пакет), и в результате оценка оказывается точнее.
Стандартная альтернатива IETF: RFC 8888 CCFB
В 2020 году IETF опубликовал RFC 8888 – RTP Control Protocol (RTCP) Feedback for Congestion Control. Концептуально это то же самое, что Transport-Wide CC Feedback: компактное RTCP-сообщение, возвращающее отправителю поэлементную информацию о прибытии пакетов. Отличия в деталях: RFC 8888 предоставляет отчёт по каждому SSRC, а не на уровне транспорта, явно включает поле для ECN-CE-меток (Explicit Congestion Notification), что особенно важно для развития L4S (см. ниже), и имеет статус официального RFC. Исходный код WebRTC прямо указывает: расширение transport-wide-cc нельзя использовать одновременно с фидбэком CCFB из RFC 8888. К 2026 году поддержка RFC 8888 появилась в некоторых реализациях, но по умолчанию в libwebrtc пока не включена; переход происходит постепенно.
Математика: как времена прихода превращаются в битрейт
К этому моменту у отправителя примерно каждые 50 мс есть список transport-номеров, которые он отправил, и времена их получения. Параллельно с этим на этом списке работают два оценщика. Их результаты комбинируются, а финальный битрейт, передаваемый энкодеру, выбирается как минимум из двух.
Оценщик 1 – delay-based
Delay-based оценщик – основа механизма управления перегрузками в Google. Каноническим референсом служит (уже устаревший) Internet-Draft draft-ietf-rmcat-gcc-02 (Holmer, Lundin, Carlucci, De Cicco, Mascolo, июль 2016); алгоритм в libwebrtc претерпел изменения, но его структура остаётся той же, что и в драфте.
Идея проста. Если сеть не перегружена, интервал между двумя пакетами на приёмнике совпадает с интервалом на отправителе: пакет A отправлен в момент t, пакет B – в t + 10 мс, оба прошли пустую очередь и пришли с разницей ровно 10 мс. Если сеть начинает загружаться, пакет B приходит позже ожидаемых 10 мс – он ждал в очереди. Оценщик измеряет разницу между межпакетными интервалами – одностороннюю вариацию задержки – и передаёт её в фильтр Калмана, который оценивает медленно меняющийся тренд: в какую сторону движется задержка? Адаптивный порог (контроллер сам подстраивает его под изменения сети) классифицирует тренд в одно из трёх состояний: under-using (интервал сжимается – можно наращивать), normal (стабильный – держать), over-using (растёт – снижать). Состояние управляет rate controller, который мультипликативно увеличивает битрейт в режиме under-using, сохраняет его в normal и уменьшает (мультипликативно, примерно до 85% от текущего receive-rate) в over-using.
Пример в замедленной съёмке. Допустим, отправитель передаёт данные со скоростью 1200 кбит/с. Он отправляет 1250-байтный видеопакет в момент 100 000 мкс и ещё один – в 110 000 мкс (интервал между отправками – 10 мс). Получатель фиксирует их прибытие в 100 300 мкс и 110 330 мкс (интервал на его часах – 10,03 мс; смещение в 300 мкс контроллеру не важно). Разница – 10,03 − 10 = 30 мкс, что практически ничтожно. Отфильтрованный тренд остаётся около нуля, пороговая проверка показывает «нормально», и контроллер ничего не делает.
Появляется кросс-трафик, и очередь узкого места начинает расти. Следующая пара пакетов имеет интервал отправки 10 мс, а интервал прибытия – 13 мс, то есть разница составляет 3 мс. После нескольких таких измерений подряд оценка фильтра превышает порог, состояние меняется на over-using, контроллер умножает целевой битрейт на коэффициент backoff (обычно 0,85 от текущего receive-rate, то есть около 1000 кбит/с в нашем примере) и передаёт команду энкодеру. Энкодер увеличивает QP или отбрасывает кадр, скорость передачи падает до 1000 кбит/с, очередь постепенно исчезает, следующие измерения возвращают разницу к нулю, состояние возвращается в «нормальное», и через несколько секунд контроллер снова начинает увеличивать битрейт – мультипликативно, обычно на ×1,05–1,08 за RTT – в поисках нового предела.
Вся петля выполняется один раз на каждый пакет обратной связи – примерно раз в 50 мс. За минуту разговора контроллер принимает 1200 отдельных решений о целевом битрейте. Энкодер получает новый таргет через интерфейс libwebrtc Call::OnBitrateAllocationChanged (или аналогичный в другом стеке) и либо перекодирует следующий кадр под новый таргет, либо, при использовании simulcast, меняет передаваемый слой (что с точки зрения SFU выглядит как переключение слоя, см. Simulcast и SVC в SFU).
Оценщик 2 – на основе потерь
Delay-based алгоритм хорош, но он не замечает один режим отказа – потерю пакетов в канале, ещё до того, как очередь начинает расти. Радиоканал со слабым сигналом, Wi-Fi с коллизиями, спутник в плохую погоду – всё это часто характеризуется низкой базовой задержкой (очереди малы), но при этом теряется 2–10% пакетов случайным образом. Из-за этого delay-based алгоритм будет продолжать наращивать скорость, поскольку задержка остаётся стабильной.
Loss-based ловит этот случай. Он анализирует тот же RTCP-фидбэк (какие пакеты пришли, а какие – нет), вычисляет долю потерь в скользящем окне и применяет пороговые значения: при потере ниже 2% оценщик считает ситуацию нормальной и выдаёт высокий «безопасный» битрейт; при потере выше 10% – снижает его примерно вдвое; в диапазоне от 2 до 10% – использует линейную интерполяцию. Финальная оценка, которую получает энкодер, – это min(delay-based, loss-based). Loss-based выступает страховкой на случай потерь без перегрузки сети.
Сам loss-based прошёл три поколения внутри libwebrtc, как разбирает webrtcHacks-обзор Густаво Гарсии (Loss-based bandwidth estimation in WebRTC): оригинальные «пороги и ступеньки» 2011 года, «сглаженные пороги и тренды» v1 примерно 2019 года и «максимум правдоподобия из набора кандидатов» v2 с 2023 года. Именно v2 используется в продакшен-стеках 2026 года – он моделирует зависимость loss-rate от sending-rate в вероятностной форме и выбирает sending-rate, максимизирующий совместное правдоподобие наблюдаемых потерь и подтверждённых скоростей передачи. Разница особенно заметна на нестабильных сотовых каналах, где старые версии склонны к переоценке и недооценке пропускной способности.
Probing: третий приём
У контроллера, реагирующего только на то, что уже передаёт энкодер, есть проблема: он не различает ситуацию «энкодер передаёт 800 kbps и сеть работает нормально» и «энкодер передаёт 800 kbps, но сеть способна пропустить 8000 kbps». Сигнал rate ограничен сверху выходом энкодера. Чтобы определить реальный потолок пропускной способности, контроллер должен иногда отправлять данные быстрее, чем позволяет энкодер, проверить, доходит ли этот избыток вовремя, и при успехе увеличивать битрейт энкодера.
Это и есть bandwidth probing. Контроллер отправляет короткий всплеск padding-пакетов – обычно burst длительностью до 100 мс с заведомо более высокой скоростью, чем текущий таргет, – и анализирует обратную связь. Если пакеты из этого всплеска приходят с такой же околонулевой вариацией задержки, как и обычный трафик, значит, у сети есть запас пропускной способности – таргет можно увеличить. Если же пакеты задерживаются и «толпятся» в очереди, значит, запаса нет – таргет следует оставить без изменений. Содержимое пробных пакетов берётся из собственных padding-байт SFU или, в некоторых реализациях, из дубликата существующего RTP-потока. Подробное описание истории и поведения можно найти на webrtcHacks в статье Probing WebRTC Bandwidth Probing и в исходном коде libwebrtc под modules/pacing/bitrate_prober.cc.
Probing – то, что делает старт быстрым. Без него поток рос бы от стартового битрейта (обычно 300 кбит/с на видео) до максимума только за счёт ожидания, пока энкодер сам не сгенерирует больше данных – а он этого не сделает, пока контроллер не разрешит. С probing контроллер за пару сотен миллисекунд после старта отправляет всплеск на условные 2000 кбит/с, фидбэк подтверждает, что путь свободен, и энкодеру сразу разрешают работать на 2000. Видимый эффект: картинка становится чёткой уже в первую секунду, а не на десятой.
Альтернативы: SCReAM и NADA
Рабочая группа IETF Real-time Multimedia Application coordination (RMCAT) в 2017–2019 годах опубликовала три алгоритма: Google Congestion Control в виде Internet-Draft, SCReAM как RFC 8298 и NADA как RFC 8698. Все три решают одну и ту же задачу и используют одни и те же сигналы RTCP. Они различаются формой алгоритма и тем, что именно оптимизируют.
SCReAM (Self-Clocked Rate Adaptation for Multimedia) – алгоритм компании Ericsson, разработанный специально для мобильных радиосетей. Он основан на окне (отслеживает «байты в полёте» относительно congestion window, как в TCP), а не является чисто rate-based, и сочетает в себе признаки loss- и delay-ориентированных подходов. Благодаря window-based архитектуре он становится «self-clocked»: скорость передачи неявно определяется размером окна и RTT, поэтому явная синхронизация пейсинга не требуется. SCReAM уже используется в некоторых RTC-стеках в продакшене (в частности, в решениях самой Ericsson; публичная эталонная реализация доступна по адресу EricssonResearch/scream), а в 2026 году ожидается обновление до версии 2 – draft-joansson-ccwg-rfc8298bis-screamv2-03.
NADA (Network-Assisted Dynamic Adaptation) – алгоритм от Cisco. Это rate-based алгоритм, использующий ECN-метки при их наличии и переключающийся на анализ задержки и потерь при отсутствии ECN. ECN позволяет сетевому устройству ставить пометку в IP-заголовке вместо отбрасывания пакета, когда очередь начинает заполняться; получатель возвращает эту метку обратно, и отправитель снижает скорость передачи, не теряя данные. NADA особенно хорошо вписывается в концепцию L4S (Low Latency, Low Loss, Scalable Throughput) – семейства стандартов RFC 9330–9332, – поскольку L4S базируется на ECN. При отсутствии ECN NADA, как и другие алгоритмы, переходит к использованию задержки и потерь.
Что в сухом остатке: какой алгоритм используется в браузерах? В 2026 году ответ остаётся прежним: Chrome, Edge, Safari и Firefox используют Google Congestion Control, потому что libwebrtc работает с GCC. Стандартизованные алгоритмы (SCReAM, NADA) встречаются лишь в узкоспециализированных стеках и академических исследованиях. Это важно, потому что вся экосистема – от browser-to-browser до SFU-to-browser – наследует поведение GCC. Если ваш продукт проходит через браузер на каком-либо этапе, особенности GCC становятся вашими особенностями.
Сравнительная таблица
| Свойство | Google Congestion Control (GCC) | SCReAM (RFC 8298) | NADA (RFC 8698) |
|---|---|---|---|
| Форма алгоритма | Rate-based, delay + loss, min(delay, loss) | Window-based, гибрид delay + loss | Rate-based, delay + loss + ECN |
| Статус стандарта | Истёкший IETF draft (draft-ietf-rmcat-gcc-02, 2016); de-facto в libwebrtc | IETF RFC 8298 (фев 2018); v2 в работе в 2026 | IETF RFC 8698 (фев 2020) |
| Сообщение фидбэка | Transport-Wide CC Feedback (Google) или REMB legacy | RFC 8888 CCFB | RFC 8888 CCFB |
| Реагирует на ECN | Нет (планируется через L4S) | Опционально | Да (по дизайну) |
| Где в продакшене | Chrome / Edge / Safari / Firefox / SDK на libwebrtc / все крупные SFU | Стек Ericsson; немного специальных стеков | Исследовательский и специальный |
| Поведение на радио | В v2 loss-based – хорошо | Спроектирован под это | Спроектирован под это |
| Где использовать в 2026 | Любой браузерный WebRTC-звонок | Часть Ericsson-стеков; исследования | L4S-сети; исследования |
Если ваш продукт работает в браузерах, GCC – ваш алгоритм, хотите вы того или нет. SCReAM и NADA становятся актуальными только тогда, когда ваши конечные точки – не браузеры: встраиваемые устройства, кастомные RTC-стеки, оборудование для broadcast-трансмиссии. И даже в этом случае нужно взвесить операционные издержки алгоритма, который не используется остальной частью WebRTC-интернета. (Где WebRTC-доставка вписывается в семейство стриминговых протоколов – см. Дерево семейства протоколов доставки.)
Что на самом деле делает энкодер с этим числом
Контроллер, выдающий таргетный битрейт, – лишь половина истории. Энкодер, менеджер simulcast/SVC и пейсер обязаны преобразовать это значение в реальное поведение на проводе.
Энкодеру сообщают новый таргет, и он реагирует на следующем кадре. В случае одиночного WebRTC-видео – один паблишер, одно разрешение, один битрейт – энкодер просто обновляет rate: libvpx-VP8 или libvpx-VP9 с обновлённым rc_target_bitrate применит новый rate в следующем кадре, обычно корректируя параметр квантования (QP). Качество плавно снижается с уменьшением битрейта до минимального уровня, после которого энкодер меняет стратегию и начинает отбрасывать кадры вместо дальнейшего увеличения QP – типично это происходит в диапазоне 150–250 кбит/с для разрешения 720p.
Для simulcast контроллер не передаёт энкодеру одно число; он сообщает менеджеру simulcast-слоёв общий бюджет, и менеджер выбирает, какая комбинация в него поместится. Типичный клиент на базе libwebrtc транслирует три слоя: 720p со скоростью 1500 кбит/с, 360p – 500 кбит/с и 180p – 150 кбит/с. Если общий лимит составляет 1500 кбит/с – публикуются все три слоя. Если пропускная способность падает до 700 кбит/с – менеджер отбрасывает 720p и транслирует 360p и 180p (всего 650 кбит/с, укладываемся в лимит). При падении до 200 кбит/с публикуется только 180p. На каждом этапе отбрасывание происходит резко, поэтому при ухудшении сети качество simulcast меняется «ступеньками», а не плавно. (Подробный разбор simulcast – в Simulcast и SVC в SFU.)
В SFU-архитектуре сам SFU выполняет per-subscriber BWE на даунлинке к каждому подписчику и решает, какой simulcast-слой переслать тому или иному. mediasoup прямо документирует: оценённый исходящий битрейт (на основе REMB или Transport-Wide CC) распределяется между consumer’ами с приоритетом более важных треков. Архитектура LiveKit описывает тот же подход: клиент по умолчанию публикует три simulcast-слоя, а SFU выбирает слой для подписчика на основе REMB- и TWCC-сигналов по каждому даунлинку (LiveKit architecture deep dive). В итоге петля замыкается дважды – один раз между паблишером и SFU, второй – между SFU и каждым подписчиком, – и некорректная настройка BWE на стороне даунлинка SFU может испортить пользовательский опыт даже при стабильном аплинке.
Пейсер наконец превращает целевой битрейт в реальные пакеты, равномерно распределённые во времени. Без пейсера энкодер 30 fps при 1 Мбит/с отправил бы все байты кадра одним всплеском – в течение одного кадра, затем простоял бы 33 мс, после чего повторил бы всплеск. Такой паттерн всплесков вреден для контроллера: он искусственно увеличивает задержку в очереди и сбивает оценщик, основанный на задержке. Пейсер из libwebrtc (в modules/pacing/) сглаживает исходящий поток, удерживая пакеты в очереди и выпуская их с целевой скоростью – сеть видит стабильный поток, а не всплески. В пейсере также реализована логика проб: когда контроллер хочет провести пробное измерение, именно пейсер впрыскивает дополнительные пакеты.
Что можно прочитать из `getStats()`
Каждая современная реализация WebRTC предоставляет доступ к параметрам петли через W3C-API RTCPeerConnection.getStats(). Соответствующие поля, согласно W3C Candidate Recommendation webrtc-stats, находятся в репортах outbound-rtp и remote-inbound-rtp:
- outbound-rtp.targetBitrate – текущая просьба контроллера к энкодеру. Это наиболее близкая к «оценке полосы» величина, выраженная одним числом.
- outbound-rtp.totalEncodedBytesTarget – кумулятивный таргет за звонок; скорость его изменения отражает недавний таргет.
- remote-inbound-rtp.fractionLost – доля потерь, которую получатель сообщает за последний интервал. Используется loss-based оценщиком.
- remote-inbound-rtp.roundTripTime – выборки RTT по RTCP. Полезны для проверки реактивности контроллера.
- candidate-pair.availableOutgoingBitrate – объём данных, который контроллер готов отправить прямо сейчас, включая запас на probing.
Если вы разбираетесь, почему картинка выглядит «мыльной», – регулярно (раз в секунду) забирайте targetBitrate и fractionLost из getStats(), постройте график – и ответ обычно становится очевиден уже в первые 30 секунд звонка. Таргет, зависший на 250 кбит/с при чистой сети, – как правило, слишком низкий min-bitrate. Таргет, скачущий между 2000 и 400 кбит/с каждые пять секунд, – скорее всего, loss-based реагирует на кратковременные всплески потерь. А таргет, плавно растущий с 300 до 1500 кбит/с за первые десять секунд и стабильный далее, – значит, петля работает корректно.
Где это ломается в реальных деплоях
Петля надёжна в лаборатории, но хрупка в полевых условиях. Наиболее частые режимы отказа в продакшене:
- Медленный старт. Начальный таргет консервативен (по умолчанию libwebrtc – 300 кбит/с на видео). Без эффективного probing’а – а он требует стабильного канала и нескольких замеров RTT – скорость растёт медленно. Звонок, который должен работать на 2 Мбит/с, через 30 секунд всё ещё ползёт на 600 кбит/с. Решение: включить агрессивный probing в публикующем SDK и при необходимости поднять min-bitrate. (Фича LiveKit dynacast решает похожую проблему, приостанавливая слои, на которые никто не подписан, и увеличивая их только при появлении подписчика – см. Bringing Zoom's end-to-end optimizations to WebRTC.)
- Переход Wi-Fi → сотовая сеть. Характеристики активного пути резко меняются: задержка удваивается, доступная полоса падает на порядок, узкое место перемещается на радиоканал. Реакция петли управления отстаёт на RTT плюс интервал фидбэка – обычно 100–200 мс – за это время энкодер сильно перебирает. Результат – фриз на одну-две секунды. Митигация: отслеживать смену пути на уровне ICE (см. NAT, STUN, TURN, ICE в WebRTC) и заранее снижать таргет, не дожидаясь фидбэка.
- Bufferbloat на бытовом роутере. Потребительский Wi-Fi-роутер с FIFO-очередью в 500 мс способен поглотить множество пакетов, прежде чем начать их отбрасывать – он маскирует перегрузку от loss-based контроллеров, искусственно увеличивая одностороннюю задержку. Delay-based алгоритмы реагируют, но только когда очередь уже переполнена – а к этому моменту аудио начинает трещать, потому что аудиопакеты застряли позади видеопотока. Меры на сетевом уровне: AQM вроде CoDel или L4S/ECN по всему пути. На уровне приложения – держать запас контроллера консервативнее на сетях, которые приложение распознаёт как домашние.
- Кросс-трафик от параллельной TCP-скачки. TCP-поток (Netflix, YouTube, фоновое обновление) по тому же аплинку будет давить изо всех сил, пока WebRTC-контроллер сознательно отступает. Скорость WebRTC падает, потому что GCC фиксирует рост очереди; TCP-поток ничего не замечает и продолжает работать. Результат: качество звонка обваливается, а скачивание идёт. Это хорошо известная проблема «реального времени против массового трафика» (real-time vs bulk fairness), и на уровне протокола решения нет; требуется либо сетевое решение (та же история с AQM и L4S), либо продуктовое (предупредить пользователя, чтобы он приостановил скачивание).
- Асимметричные пути и обратная связь по другому каналу. Пакеты фидбэка идут по пути, возможно отличному от прямого. Если обратный путь перегружен – обратная связь приходит с задержкой, контроллер работает на устаревших данных. На практике случается редко, но бывает на спутниковых каналах и некоторых конфигурациях TURN-релеев. Диагностика: растёт roundTripTime со стороны фидбэка, а availableOutgoingBitrate в прямом направлении выглядит нормально.
- Узкое место – даунлинк SFU, а паблишер не помогает. Паблишер с мощным аплинком отправляет три simulcast-слоя на 2500 кбит/с в SFU. Подписчик с медленным даунлинком тянет только 700 кбит/с. Оценщик SFU на уровне каждого подписчика понижает ему качество до нижнего simulcast-слоя (180p на 150 кбит/с), и подписчик видит мыльное 180p, хотя 360p в принципе тоже публикуется. Решение: либо SFU с SVC вместо simulcast (любой подписчик может получить байты любого слоя), либо SDK паблишера, который адаптирует битрейт под худшего подписчика. (Команды mediasoup, LiveKit, Janus и Jitsi Videobridge обсуждали различные варианты этого компромисса; см. Сравнение SFU: mediasoup, Janus, LiveKit, Jitsi, Pion.)
Распространённая ошибка: настраивать maxBitrate энкодера, не настраивая лимиты контроллера
Частая ошибка в WebRTC-продуктах – задавать RTCRtpEncodingParameters.maxBitrate слишком высокое значение (например, 5 Мбит/с для 720p-слоя simulcast), рассчитывая, что «звонок сможет использовать больше полосы на хорошей сети». Однако контроллер не будет использовать этот лимит, пока сам не убедится, что сеть его выдержит. В результате на большинстве сетей энкодер всё равно ограничивается оценкой контроллера, а не значением maxBitrate. Проблема проявляется на редких сетях, где оценка контроллера опережает реальный узкое место – тогда энкодер пытается передать 5 Мбит/с по каналу, который не справляется, очередь переполняется, аудио начинает трещать, а звонок разрушается. Решение – установить maxBitrate в значение, соответствующее целям звонка (1500–2500 кбит/с для типичной 720p-конференции), и не позволять контроллеру превышать этот лимит. Задавать потолок выше реальных потребностей – значит заранее создавать проблемы.
Фронтир 2026: машинное обучение, L4S и ИИ-агенты
Три изменения будут внедрены в продакшен в этом и следующем году – в порядке убывания зрелости.
ML-усиленные оценщики. Meta опубликовала Optimizing RTC bandwidth estimation with machine learning в начале 2024 года: описанный обученный контроллер уже используется в видеозвонках Messenger и WhatsApp. Модель выдаёт тот же сигнал target-битрейта, что и GCC, но обучена на телеметрии по каждому вызову, а не на вручную заданных порогах. Команда отмечает заметный прирост качества в условиях плохих сетей – особенно на длинном хвосте. Несколько вендорских решений (LiveKit, Daily и другие) тестируют аналогичные модели; open-source версия libwebrtc пока остаётся на GCC. Стоит следить за развитием.
L4S и ECN сквозь весь путь. L4S (RFC 9330–9332, январь 2023) – это сетевое изменение, которое при полном развертывании по всему пути способно свести дополнительную задержку к почти нулю, не теряя пропускной способности. Идея заключается в следующем: queue-aware AQM на узком месте помечает пакеты меткой ECN-CE, как только очередь начинает расти, вместо того чтобы их отбрасывать; отправитель замедляется при получении такой метки, и очередь не увеличивается. WebRTC-стеки начинают интерпретировать ECN-метки через механизм обратной связи, описанный в RFC 8888. Новая статья ACM Lag-Busting WebRTC: L4S-Enabled Adaptive Video Streaming демонстрирует работу L4S-оптимизированного WebRTC-видео с заметно лучшим балансом между задержкой и качеством. Главный недостаток – L4S работает эффективно только при условии поддержки на каждом участке пути, а это означает медленное развитие экосистемы.
ИИ-агенты и нагрузка «разговор от имени модели». Новая нагрузка, массово появившаяся в 2025 году, – это голосовой LLM-агент: real-time WebRTC-сессия между человеком и серверной моделью с низкой задержкой аудио в обе стороны, а часто и с видеопотоком. Профиль трафика таких агентов отличается от обычной человеческой конференции: всплески речи генерируются моделью и обычно более ровные, паузы – предсказуемые, а соединения нередко проходят между colocated дата-центром и конечным пользователем. Несколько стеков начинают адаптировать контроллеры под эту нагрузку; команда LiveKit писала об этом в анонсе LiveKit agents. Последствия для BWE в стандартах пока не устоялись, но на практике эта нагрузка уже становится заметным драйвером новых разработок в области BWE.
Где Фора Софт в этом
Фора Софт работает с WebRTC с 2010 года – тогда это был экспериментальный флаг в Chrome. В проектах по видеоконференцсвязи, телемедицине, e-learning, live-шопингу и AR/VR наши команды настраивали петли BWE и контроля перегрузки на всех типах сетей, с которыми сталкивались в продакшене: клинический Wi-Fi за пятилетним роутером, мобильный LTE в регионах, где основной стандарт всё ещё 3G, спутниковые каналы с задержкой в сотни миллисекунд, корпоративные сети с жёсткими ограничениями, где только TURN-ретрансляция выдерживает ICE. Приведённые выше паттерны – результат этих проектов. Если качество звонка не то, чаще всего проблема в петле; если проблема в петле – решение, как правило, одно из шести режимов отказоустойчивости. Относитесь к контроллеру как к полноценному компоненту продукта, а не к унаследованному «чёрному ящику».
Ключевые выводы
- WebRTC использует собственный механизм контроля перегрузки поверх UDP, потому что поведение TCP не подходит для передачи реального времени.
- Сигнал контроллера – это время прибытия пакета, передаваемое в Transport-Wide CC Feedback (или устаревший REMB, или CCFB по RFC 8888).
- Google Congestion Control объединяет delay- и loss-based подходы; конечный целевой битрейт – минимальный из двух.
- Probing помогает определить верхний предел пропускной способности; без него старт медленный, и соединение не может эффективно использовать доступный запас.
- SCReAM и NADA существуют, но если конечные точки – браузеры, вы всё равно используете GCC.
- Энкодер, менеджер simulcast/SVC и пейсер преобразуют целевой битрейт в реальное поведение передачи по сети.
- RTCPeerConnection.getStats() раскрывает состояние петли – targetBitrate и fractionLost – основные поля для отладки.
- Петля может нарушаться из-за bufferbloat, перехода с Wi-Fi на сотовую сеть, кросс-трафика и асимметричных маршрутов; меры по устранению – продуктовые, а не протоколные.
Что читать дальше
- Simulcast и SVC в SFU – что означает таргетный битрейт контроллера, когда вы публикуете три слоя вместо одного.
- Сравнение SFU: mediasoup, Janus, LiveKit, Jitsi, Pion – как каждый SFU обрабатывает даунлинк-петлю.
- Контроль перегрузки простыми словами: BBR, CUBIC, Copa – широкий контекст различий между медиаконтроллером и TCP.