Для управления комнатами, обмена 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. Ниже приведены основные поля структуры.
{
"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 публикация потока от клиента происходит в три шага.
- Предварительное уведомление (необязательно). Клиент может отправить сообщение с типом
publish_track, указав желаемое имя потока в полеtrack_id, типkind(audioилиvideo) и идентификатор ключа шифрованияe2ee_key_id. Сервер сохраняет эти данные как подсказкиPublishHintsдля будущей привязки. - Обмен SDP (Join или Offer). Клиент добавляет локальные медиатреки в свой
RTCPeerConnection, формирует SDP Offer и отправляет его на сервер в сообщенииjoin(или отдельным сообщениемoffer). Сервер принимает оффер в методеAcceptOffer(при возникновении встречного конфликта состояний Glare автоматически выполняет откатSDPTypeRollback), собирает локальные ICE кандидаты и возвращает готовый SDP Answer в ответеjoined(илиanswer) вместе со списком внешних TURN серверов в полеice_servers. - Автоматическая регистрация трека в комнате. Как только завершается установка соединения 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 от последнего отправленного пакета, поэтому декодер браузера продолжает воспроизведение видео нового собеседника без единого разрыва нумерации.