WebSocket сигналинг, маршрутизация Simulcast и SVC, QoS и быстрый свап

Обновлено:

Для управления комнатами, обмена SDP описаниями и переключения качества видео Slyber SFU Server предоставляет JSON протокол поверх WebSocket соединения /ws и синхронного HTTP эндпоинта POST /signaling. В этой статье подробно разобраны все типы сигнальных сообщений, порядок публикации и подписки в стеке Pion WebRTC, а также алгоритмы управления качеством видео.

Подключение к сигнальному шлюзу и привязка сессии

Клиентское приложение устанавливает постоянное WebSocket соединение по адресу ws://<HOST>:8090/ws. Сервер поддерживает до 16384 одновременных сигнальных подключений, автоматически отправляет проверочные Ping фреймы каждые 30 секунд и требует, чтобы все клиентские WebSocket фреймы были замаскированы по стандарту RFC 6455.

Первое сообщение от клиента обязано содержать валидный токен авторизации в поле token (правила создания токенов описаны в статье Аутентификация и сквозное шифрование). После успешной проверки токена WebSocket соединение жестко привязывается к паре participant_id и room_id. Попытка отправить в том же сокете токен другого пользователя или другой комнаты будет отклонена с ошибкой безопасности. Если в комнате уже находятся другие участники с опубликованными потоками, сервер сразу после входа пришлет новому клиенту список существующих треков сообщениями publish_track.

Формат сообщений SignalEnvelope

Все входящие и исходящие сообщения передаются в едином JSON формате SignalEnvelope. Ниже приведены основные поля структуры.

JSON
{
  "type": "join | joined | offer | answer | trickle_ice | publish_track | subscribe_track | set_layer | roulette_swap | active_speakers | e2ee_key_ratchet | leave | error",
  "room_id": "conf-room-01",
  "participant_id": "user-42",
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "sdp": "v=0\r\no=- ...",
  "candidate": "candidate:1 1 udp ...",
  "track_id": "cam-feed",
  "target_track_id": "partner-2-cam",
  "kind": "video",
  "codec": "vp8",
  "ssrcs": [1001, 1002, 1003],
  "spatial_layer": 2,
  "temporal_layer": 2,
  "e2ee_key_id": 1,
  "mls_key_package": "..."
}

Публикация медиапотока (Join, Offer и подсказки PublishHints)

В архитектуре на базе Pion WebRTC публикация потока от клиента происходит в три шага.

  1. Предварительное уведомление (необязательно). Клиент может отправить сообщение с типом publish_track, указав желаемое имя потока в поле track_id, тип kind (audio или video) и идентификатор ключа шифрования e2ee_key_id. Сервер сохраняет эти данные как подсказки PublishHints для будущей привязки.
  2. Обмен SDP (Join или Offer). Клиент добавляет локальные медиатреки в свой RTCPeerConnection, формирует SDP Offer и отправляет его на сервер в сообщении join (или отдельным сообщением offer). Сервер принимает оффер в методе AcceptOffer (при возникновении встречного конфликта состояний Glare автоматически выполняет откат SDPTypeRollback), собирает локальные ICE кандидаты и возвращает готовый SDP Answer в ответе joined (или answer) вместе со списком внешних TURN серверов в поле ice_servers.
  3. Автоматическая регистрация трека в комнате. Как только завершается установка соединения ICE и рукопожатие DTLS, от клиента приходят первые зашифрованные SRTP пакеты. Внутри Pion срабатывает обработчик pc.OnTrack, который создает объект UpTrack в комнате, запрашивает у издателя опорный кадр через RTCP PLI и рассылает всем участникам комнаты широковещательное уведомление publish_track с именем трека, кодеком и SSRC.

Подписка на поток и серверная ренегоциация Unified Plan

Когда клиент получает уведомление publish_track (или хочет подписаться на известный поток в комнате), он отправляет на сервер запрос subscribe_track с указанием track_id и желаемого пространственного слоя spatial_layer (от 0 до 2).

При обработке этого запроса Slyber SFU Server выполняет следующие действия.

  • Проверяет реально опубликованные слои у целевого UpTrack через метод ClampSpatialLayer. Например, если издатель вещает только два слоя Simulcast (0 и 1), а зритель запросил слой 2, сервер автоматически прижмет подписку к максимальному доступному слою 1, чтобы зритель не завис в бесконечном ожидании несуществующего слоя.
  • Создает локальный исходящий RTP трек TrackLocalStaticRTP и добавляет его в PeerConnection зрителя.
  • Запускает серверную ренегоциацию Unified Plan (метод triggerNegotiation) и отправляет зрителю по WebSocket сообщение с типом offer и новым серверным SDP.
  • Клиентское приложение должно принять этот серверный offer через setRemoteDescription, сформировать ответ createAnswer и отправить его обратно на сервер сообщением с типом answer. Сразу после этого сервер запрашивает свежий ключевой кадр (TriggerPLI) и начинает доставку видео зрителю.

Simulcast, фильтрация SVC и автопереключение качества (BWE)

Сервер поддерживает как многопоточный Simulcast (где слои распознаются по RTP расширениям sdes:rtp-stream-id со значениями q, h, f или 0, 1, 2), так и однопоточный SVC для кодека VP9 (где пространственные и временные индексы извлекаются напрямую из заголовка полезной нагрузки пакета).

Для каждого видеопотока работают три механизма обеспечения качества (QoS).

  • Послойные буферы NACK и PLI. Каждый UpTrack содержит три независимых кольцевых буфера PacketRingBuffer и три независимых подавителя PLICoalescer с интервалом 500 миллисекунд. Когда зритель теряет пакет, сервер мгновенно находит исходный номер пакета через историю ReverseTranslateNACK и досылает его из памяти без обращения к отправителю.
  • Автоматический контроллер полосы пропускания (SubscriberBWE). Сервер непрерывно читает отчеты RTCP TransportLayerCC (TWCC), ReceiverEstimatedMaximumBitrate (REMB) и ReceiverReport от каждого зрителя. При потерях пакетов свыше 10 процентов оценка канала принудительно снижается на 25 процентов. В зависимости от доступной скорости в Кбит/с контроллер автоматически выбирает пространственный и временной слой (от 1800 Кбит/с включается слой 2/2, от 1200 слой 2/1, от 600 слой 1/2, от 350 слой 1/1, от 150 слой 0/1, а ниже 150 Кбит/с включается аварийный слой 0/0).
  • Ручной выбор качества (set_layer). Если клиент отправляет команду set_layer с конкретными полями spatial_layer и temporal_layer, сервер отключает автоматическое переключение BWE для этого трека (DisableAutoLayerForTrack) и фиксирует выбранное пользователем качество. При повышении слоя Simulcast переключение происходит бесшовно строго в момент прихода опорного кадра IsKeyFrame нового слоя.

Детектор активного спикера, политика Last N и свап в рулетке

В аудиопакетах сервер считывает расширение заголовка urn:ietf:params:rtp-hdrext:ssrc-audio-level (RFC 6464) без расшифровки самого звука. Детектор ActiveSpeakerDetector вычисляет сглаженную энергию голоса участников за последние 1.2 секунды. Раз в секунду фоновый таймер комнаты рассылает клиентам сообщение active_speakers со списком до трех самых громких спикеров и вызывает метод EnforceLastNVideo. Если число видеокамер в комнате превышает лимит LAST_N_VIDEO_LIMIT, потоки молчащих участников ставятся на паузу, а при возобновлении речи бесшовно продолжаются с ключевого кадра.

Для видеорулеток один на один предусмотрено сообщение roulette_swap, в котором передаются старый поток track_id и новый поток target_track_id. Метод FastSwapRouletteTrack перепривязывает существующий DownTrack зрителя к новому UpTrack без закрытия соединения и без отправки новых SDP офферов. Модуль SeqTimestampRewriter автоматически пересчитывает смещения seqOffset и tsOffset от последнего отправленного пакета, поэтому декодер браузера продолжает воспроизведение видео нового собеседника без единого разрыва нумерации.