Skip to content

WebRTC media transport

WebRTC сейчас является основным media path для браузерного клиента. WSMedia остается fallback для первичного подключения в сетях и браузерах, где UDP/WebRTC/SRTP заблокирован, а также forced diagnostic mode. Если WebRTC endpoint уже был успешно поднят и затем деградировал после stale/reconnect, клиент восстанавливает его через WebRTC restart/backoff, а не переключает в WSM.

Клиент не подключается напрямую к Translator. Он создает WebRTC endpoint на выбранной RTC route (rtc_node_id) через CommandLoop signaling, а RTC node / WebRTC Gateway связывает этот endpoint с существующим Translator и conference lifecycle.

Текущий совместимый набор кодеков:

  • audio: Opus через WebRTC;
  • video: H.264 для текущего browser WebRTC path;
  • WSMedia browser fallback тоже переносит H.264 RTP и декодирует его через WebCodecs;
  • VP8 остается в legacy native/recorder/fileplayer path и как совместимость для старых сервисов.

Почему нельзя подключить браузер прямо к Translator

Существующий серверный media core уже работает по UDP/RTP:

  • TranslatorPool выделяет один из UDP Translator-портов для устройства.
  • Translator разводит RTP по author_ssrc и receiver_ssrc.
  • Native-клиенты и WSMServer отправляют в Translator обычные RTP/RTCP пакеты.
  • WSMServer сейчас является мостом WebSocket binary frame -> UDP RTP.

Браузерный RTCPeerConnection не умеет отправлять такой plain RTP напрямую в UDP-порт Translator. WebRTC поверх UDP всегда включает:

  • ICE для выбора сетевого пути и consent checks;
  • DTLS handshake;
  • SRTP/SRTCP вместо plain RTP/RTCP;
  • SDP offer/answer и ICE candidates;
  • RTP/RTCP mux и обычно BUNDLE.

Значит, нужен WebRTC terminator/gateway: он принимает WebRTC от браузера, снимает ICE/DTLS/SRTP и связывает WebRTC-треки с существующей моделью device_id, author_ssrc, receiver_ssrc и Translator-портов.

Текущая схема

flowchart LR
    Browser["Browser\nRTCPeerConnection"] -->|ICE + DTLS + SRTP| Gateway["RTC node / WebRTC Gateway"]
    Gateway -->|plain RTP/RTCP bridge| Translator["TranslatorPool / Translator"]
    Translator -->|plain RTP/RTCP for native/WSM/subscribers| Gateway
    Gateway -->|WebRTC media tracks| Browser

    Browser -->|webrtc_routes_request\noffer/ice| Control["CommandLoop"]
    Control -->|CAN job or in-process call| Gateway

CommandLoop остается каналом управления. WebRTC Gateway или внешняя RTC-нода являются media-шлюзами рядом с WSMServer, а не заменой conference logic.

RTC route selection

Перед созданием WebRTC endpoint клиент запрашивает доступные RTC routes:

{
  "webrtc_routes_request": {}
}

Ответ:

{
  "webrtc_routes": {
    "routes": [
      {
        "rtc_node_id": "rtc-eu-1",
        "role": "rtc-translator",
        "version": "3.0.0",
        "capabilities": ["rtc", "webrtc", "rtp_bridge"],
        "messages_in": 128,
        "events_in": 64
      }
    ]
  }
}

Сервер возвращает только online CAN-ноды с role=rtc-translator и capability rtc. Порядок routes намеренно перемешивается на сервере, чтобы клиенты распределялись между нодами. Клиентский алгоритм:

  1. Получить routes через webrtc_routes_request.
  2. Если список не пустой, пробовать routes по порядку.
  3. Если route не подключился из-за ICE timeout / connect timeout, перейти к следующему rtc_node_id.
  4. Если все routes не сработали на первичном подключении в режиме auto, перейти на WSMedia fallback.
  5. Если routes нет, сервер может использовать in-process fallback gateway, если он собран и включен; иначе клиент в режиме auto должен перейти на WSMedia.

rtc_node_id - логический route handle, не сетевой адрес. Реальный ICE address приходит в SDP/ICE от выбранной RTC-ноды.

Endpoint identity

Каждая WebRTC publish/subscribe попытка должна иметь стабильный endpoint_id, уникальный в рамках клиентской сессии и scope. Клиент передает его в webrtc_offer и webrtc_ice_candidate, а сервер возвращает тот же endpoint_id в webrtc_answer и ICE events.

Рекомендуемый формат:

<scope>-<uuid>

Примеры:

Scope Endpoint
audio audio-publish-8b7c...
video video-publish-8b7c...
audio-subscribe audio-subscribe-8b7c...
video-subscribe video-subscribe-8b7c...

peer_id нужен для диагностики и сопоставления endpoint'ов, например local-mic-1005 или remote-video-1006-228800002.

Media flows

Publish microphone

  1. Клиент входит в конференцию обычным connect_to_conference_request.
  2. Клиент запрашивает device_params для микрофона.
  3. Сервер выдает device_id и author_ssrc.
  4. Клиент создает RTCPeerConnection и добавляет getUserMedia({ audio: true }) track.
  5. Клиент выбирает rtc_node_id, генерирует endpoint_id и отправляет SDP offer через webrtc_offer.
  6. RTC-нода отвечает webrtc_answer, затем клиент и RTC-нода обмениваются webrtc_ice_candidate.
  7. Gateway получает Opus RTP из WebRTC, преобразует SRTP в plain RTP и отправляет его на Translator port с author_ssrc.
  8. Серверная логика устройства остается прежней: участники получают device_connect.

Subscribe remote microphone

  1. Клиент получает device_connect с receiver_ssrc, author_ssrc, port.
  2. Клиент выбирает rtc_node_id, генерирует endpoint_id и отправляет webrtc_offer со scope audio-subscribe.
  3. RTC-нода регистрирует receiver на Translator:
  4. отправляет adjusting RTP/RTCP от имени receiver_ssrc, чтобы Translator запомнил UDP-адрес receiver;
  5. получает RTP автора с Translator;
  6. отправляет этот поток в браузер как WebRTC audio track.
  7. Браузер воспроизводит входящий звук через нативный WebRTC/audio path, без JS Opus decoder и без AudioContext как основного output.

Publish camera

  1. Клиент получает device_id, author_ssrc и Translator port через обычный lifecycle устройства.
  2. Клиент создает WebRTC offer со своим video track.
  3. Gateway принимает RTP от браузера, нормализует SSRC в author_ssrc и отправляет plain RTP в Translator. Это сохраняет совместимость с native-клиентами, recorder и WSMServer.
  4. Для браузерных WebRTC-подписчиков Gateway может пересылать RTP напрямую между WebRTC publisher/subscriber tracks, если publisher тоже WebRTC. Это не отменяет Translator: Translator остается ядром для native/WSM/recording path.

Subscribe remote camera

  1. Клиент получает device_connect удаленной камеры и открывает video-subscribe WebRTC flow.
  2. Если источник - native/WSM/recorder path, Gateway получает RTP из Translator и отправляет его как WebRTC video track.
  3. Если источник - WebRTC publisher, Gateway может использовать прямой WebRTC forwarding между tracks и параллельно продолжать кормить Translator для остальных типов клиентов.
  4. Keyframe requests идут через Gateway к publisher и должны быть throttled, чтобы PLI/FIR-loop не превращался в flood.

Signaling API

Минимальный набор команд поверх CommandLoop.

webrtc_routes_request / webrtc_routes

{
  "webrtc_routes_request": {}
}
{
  "webrtc_routes": {
    "routes": [
      {
        "rtc_node_id": "rtc-eu-1",
        "role": "rtc-translator",
        "version": "3.0.0",
        "capabilities": ["rtc", "webrtc", "rtp_bridge"]
      }
    ]
  }
}

webrtc_offer

{
  "webrtc_offer": {
    "peer_id": "local-mic-1005",
    "conference_tag": "test",
    "scope": "audio",
    "rtc_node_id": "rtc-eu-1",
    "endpoint_id": "audio-publish-8b7c",
    "sdp": "v=0...",
    "device_id": 1059,
    "author_ssrc": 1214,
    "port": 5063
  }
}

Поля:

Поле Назначение
peer_id Диагностический id endpoint'а на клиенте.
conference_tag Конференция, к которой привязан media endpoint.
scope audio, video, audio-subscribe, video-subscribe.
rtc_node_id Выбранная route из webrtc_routes. Пустое значение разрешает серверный fallback, если он доступен.
endpoint_id Клиентский id endpoint'а; нужен для корреляции answer/ICE.
sdp Local SDP offer.
device_id Device lifecycle id.
author_ssrc SSRC автора/source.
receiver_ssrc SSRC получателя для subscribe path.
port Translator port из device_connect.

device_id, author_ssrc, receiver_ssrc и port являются media binding. Клиент берет их из обычного device lifecycle: device_params выдает device_id/author_ssrc, а device_connect CreatedDevice или remote device_connect возвращает Translator port и receiver_ssrc. Если binding не передан, сервер может завершить SDP/ICE handshake, но endpoint не будет корректно связан с Translator.

webrtc_answer

{
  "webrtc_answer": {
    "peer_id": "local-mic-1005",
    "conference_tag": "test",
    "scope": "audio",
    "rtc_node_id": "rtc-eu-1",
    "endpoint_id": "audio-publish-8b7c",
    "sdp": "v=0..."
  }
}

webrtc_ice_candidate

{
  "webrtc_ice_candidate": {
    "peer_id": "local-mic-1005",
    "conference_tag": "test",
    "scope": "audio",
    "rtc_node_id": "rtc-eu-1",
    "endpoint_id": "audio-publish-8b7c",
    "candidate": {
      "candidate": "candidate:...",
      "sdpMid": "0",
      "sdpMLineIndex": 0
    }
  }
}

scope определяет назначение binding:

  • audio - publish microphone;
  • video - publish camera/screen;
  • audio-subscribe - receive remote microphone;
  • video-subscribe - receive remote camera/screen.

Корреляция с текущей моделью устройств идет через device_id, author_ssrc, receiver_ssrc и port, которые уже приходят в device_connect.

ICE candidates можно отправлять как объект candidate, совместимый с RTCIceCandidate.toJSON(). Сервер также принимает flat-поля candidate, sdpMid, sdpMLineIndex.

Connect sequence

Publish

sequenceDiagram
    participant C as Client
    participant S as CommandLoop
    participant R as RTC node
    participant T as Translator

    C->>S: device_params
    S-->>C: device_connect(my=1, device_id, author_ssrc, port)
    C->>S: webrtc_routes_request
    S-->>C: webrtc_routes(routes[])
    C->>C: create RTCPeerConnection + local track
    C->>S: webrtc_offer(scope=audio/video, rtc_node_id, endpoint_id, binding)
    S->>R: CAN rtc.endpoint.start
    R->>T: bind translator port/ssrc
    R-->>S: webrtc_answer
    S-->>C: webrtc_answer
    C-->>S: webrtc_ice_candidate
    S-->>R: CAN rtc.endpoint.ice

Subscribe

sequenceDiagram
    participant C as Client
    participant S as CommandLoop
    participant R as RTC node
    participant T as Translator

    S-->>C: device_connect(my=0, device_id, author_ssrc, receiver_ssrc, port)
    C->>S: webrtc_routes_request
    S-->>C: webrtc_routes(routes[])
    C->>C: create recvonly RTCPeerConnection
    C->>S: webrtc_offer(scope=audio-subscribe/video-subscribe, rtc_node_id, endpoint_id, binding)
    S->>R: CAN rtc.endpoint.start
    R->>T: register receiver_ssrc / adjust packet
    R-->>S: webrtc_answer
    S-->>C: webrtc_answer
    R-->>C: media track

Инварианты

  • Conference membership, permissions, visibility/hearability и lifecycle устройств остаются в Processor.
  • Translator остается media router; WebRTC Gateway не принимает решений о правах доступа.
  • WebRTC path не должен создавать WSMedia сессии для media payload.
  • WSMedia остается обязательным fallback для сетей и браузеров, где UDP/WebRTC/SRTP заблокирован или нестабилен.
  • Для браузера WebRTC path должен обходить JS Opus decode/encode и ScriptProcessor как основной audio pipeline.
  • WebRTC SRTP-порты не смешиваются с Translator-портами 5060-5063: это разные transport layers.
  • Translator остается ядром трансляции для native-клиентов, WSMedia, recorder и совместимости с существующей media-моделью.
  • Browser-to-browser video forwarding может оптимизироваться внутри Gateway, но не должен ломать Translator path.

Failover и recovery

WebRTC должен быть предпочтительным media path для браузера, но не единственным. В реальной сети UDP и SRTP могут быть заблокированы, поэтому клиент и сервер сохраняют рабочий WSMedia path как fallback на этапе первичного подключения. Для уже поднятых WebRTC endpoint'ов recovery выполняется через WebRTC restart/backoff, без деградации в WSM.

Режим клиента:

  • auto: сначала пробовать WebRTC; если endpoint изначально не поднялся, перейти на WSMedia.
  • webrtc: использовать только WebRTC для диагностики нового транспорта.
  • wsm: использовать только текущий WSMedia mux для диагностики и совместимости.

Условия перехода auto -> wsm:

  • ICE не вышел в connected/completed за ограниченный таймаут.
  • DTLS/SRTP handshake завершился ошибкой.
  • первичный webrtc_offer не получил webrtc_answer.
  • Браузер или платформа не поддерживает нужный WebRTC/audio path.

Условия, при которых auto -> wsm запрещен:

  • WebRTC endpoint уже был в состоянии connected/ok;
  • recovery выполняется после restore_stale_session;
  • health-check показал stalled или no inbound RTP progress у уже активной WebRTC session;
  • local publish получил disconnected/failed после успешного publish.

В этих случаях клиент должен перезапустить WebRTC endpoint с тем же device_id/sourceDevice, а не создавать WSM-сессию. Инвариант реализации: Processor, conference membership, device lifecycle, device_id, author_ssrc, receiver_ssrc и TranslatorPool не должны зависеть от выбранного транспорта. WebRTC Gateway и WSMedia являются транспортными адаптерами вокруг одной серверной модели устройств.

Во время активной конференции автоматический возврат wsm -> webrtc не обязателен. Его можно добавлять позже только если переключение будет безопасным и без заметного разрыва media.

Server-owned ICE

VideoGrace не должен зависеть от внешних STUN/TURN-сервисов для базового server-relay сценария. WebRTC Gateway работает как публичная UDP-точка самого VideoGrace Server: браузер получает ICE candidate сервера через ControlWS и отправляет WebRTC/UDP на этот адрес.

Если сервер имеет публичный IP прямо на интерфейсе, дополнительных ICE-сервисов не требуется. Если сервер находится за NAT или libdatachannel видит только приватный адрес вроде 192.168.x.x, нужно явно указать адрес, который браузеры должны использовать:

export VG_WEBRTC_ADVERTISE_ADDRESS=core.videograce.ru

Та же настройка может жить в серверном конфиге:

[WebRTC]
AdvertiseAddress=core.videograce.ru

Если WebRTC.AdvertiseAddress не задан, сервер использует Network.Address / VG_ADDR.

Для эксплуатации лучше ограничить UDP-диапазон и открыть его на firewall/NAT:

export VG_WEBRTC_PORT_RANGE_BEGIN=43000
export VG_WEBRTC_PORT_RANGE_END=43100
[WebRTC]
PortRangeBegin=43000
PortRangeEnd=43100

Если нужно привязать libdatachannel к конкретному локальному интерфейсу:

export VG_WEBRTC_BIND_ADDRESS=0.0.0.0
[WebRTC]
BindAddress=0.0.0.0

Инвариант: VG_WEBRTC_ADVERTISE_ADDRESS должен быть достижим с клиентской сети, а UDP-порты из диапазона должны приходить на VideoGrace Server. Эти порты отдельные от Translator-портов. Если UDP недоступен при первичном подключении, web-клиент в режиме auto может перейти на WSMedia.

Для внешнего RtcTranslator через CAN действует тот же ICE-инвариант, но адрес относится уже к RTC-ноде, а не к головному серверу. Core отдает клиенту только rtc_node_id; браузер получает реальный адрес RTC-ноды из SDP/ICE, которые возвращает сам worker. Поэтому на каждой внешней RTC-ноде нужно задавать свой VG_WEBRTC_ADVERTISE_ADDRESS и открывать ее UDP range.

Codec direction

Audio path использует Opus. Это обязательный WebRTC codec и он хорошо подходит для браузеров и мобильных клиентов.

Browser video path сейчас H.264-first: web-клиент выставляет H.264 выше остальных codecs в WebRTC offer/answer, а WSMedia fallback использует H.264 WebCodecs encoder/decoder и H.264 RTP packetizer/depacketizer.

Дальше нужно довести H.264-first до всех native/media services:

  • iOS WebRTC не должен рассматриваться как VP8-capable target;
  • mobile native clients должны публиковать и принимать H.264;
  • native desktop может использовать OpenH264 как software fallback;
  • аппаратное ускорение нужно выносить в отдельные backend implementations: VideoToolbox на Apple, Media Foundation/NVENC/QSV на Windows, VAAPI/NVENC на Linux;
  • VP8 оставить как legacy/fallback для старых desktop/services path, но не как основной browser/mobile codec.

Gateway не должен становиться video transcoder по умолчанию. Его задача - WebRTC termination, RTP routing, keyframe/RTCP control и совместимость транспортов. Кодирование/декодирование должно жить на клиенте или в специализированных media services.

Варианты реализации Gateway

In-process C++ gateway

Рекомендуемый долгосрочный вариант.

Подходящая библиотека: libdatachannel.

Плюсы:

  • C++17, ближе к текущему серверу.
  • ICE/DTLS/SRTP и media tracks без полной тяжести Google libwebrtc.
  • Можно встроить рядом с WSMServer и использовать существующие UDPSocket, TranslatorPool, логирование и конфиг.

Минусы:

  • Нужно добавить новую third-party зависимость и сборку под Linux/Windows.
  • Потребуется аккуратная интеграция с event loop сервера.

В сервере зависимость должна включаться только через backend gateway:

cmake -S . -B build -DVG_ENABLE_LIBDATACHANNEL=ON
cmake --build build --target VideoGraceServer

Без VG_ENABLE_LIBDATACHANNEL сервер собирается со stub gateway и принимает signaling-команды, но не поднимает WebRTC media.

Что дает libdatachannel

libdatachannel закрывает именно WebRTC-терминацию, а не заменяет наш media core:

  • ICE и candidate negotiation;
  • DTLS handshake;
  • SRTP/SRTCP;
  • SDP offer/answer;
  • WebRTC media tracks;
  • SCTP/datachannel support, который сейчас не является целью audio/video path.

Кодеки, микширование, конференционная модель, права доступа, TranslatorPool и устройство/SSRC lifecycle остаются в VideoGrace. Gateway должен получить из WebRTC обычные RTP/RTCP пакеты и передать их в существующий UDP media core.

Сборка libdatachannel binaries

Исходники libdatachannel лежат в thirdparty/libdatachannel, headers - в Lib/libdatachannel/include. Бинарные артефакты не собираются основным CMake автоматически и не должны становиться частью обычного исходного diff. Для каждой платформы они собираются отдельно, публикуются как пакет Lib/libdatachannel, а в рабочую копию подтягиваются через Lib/upd_*_libs.py по той же модели, что и остальные bundled-библиотеки.

Для macOS arm64:

thirdparty/libdatachannel/build_vg_macos.sh

Для Linux x86_64:

thirdparty/libdatachannel/build_vg_linux.sh

Для Windows x64 из Developer PowerShell for Visual Studio:

powershell -ExecutionPolicy Bypass -File thirdparty/libdatachannel/build_vg_windows.ps1 -Platform win_x64

Для Windows Win32:

powershell -ExecutionPolicy Bypass -File thirdparty/libdatachannel/build_vg_windows.ps1 -Platform win32

Скрипт:

  • подтягивает build-only зависимости в thirdparty/libdatachannel/deps;
  • фиксирует libsrtp на v2.7.0, потому что libdatachannel 0.24.2 ожидает target srtp2;
  • собирает static datachannel-static;
  • на Windows собирает libdatachannel и его зависимости с MSVC runtime /MT, как основной Server.vcxproj;
  • на Windows после CMake configure дополнительно патчит generated .vcxproj, чтобы исключить случайный /MD в dependency targets;
  • использует OpenSSL из Lib/OpenSSL/lib/x64 для win_x64 и Lib/OpenSSL/lib/win32 для win32;
  • копирует результат в платформенную папку Lib/libdatachannel/lib/*.

Ожидаемые артефакты:

Lib/libdatachannel/lib/mac_arm64/libdatachannel-static.a
Lib/libdatachannel/lib/mac_arm64/libjuice-static.a
Lib/libdatachannel/lib/mac_arm64/libsrtp2.a
Lib/libdatachannel/lib/mac_arm64/libusrsctp.a

Lib/libdatachannel/lib/lin_x64/libdatachannel-static.a
Lib/libdatachannel/lib/lin_x64/libjuice-static.a
Lib/libdatachannel/lib/lin_x64/libsrtp2.a
Lib/libdatachannel/lib/lin_x64/libusrsctp.a

Lib/libdatachannel/lib/win_x64/datachannel-static.lib
Lib/libdatachannel/lib/win_x64/juice-static.lib
Lib/libdatachannel/lib/win_x64/srtp2.lib
Lib/libdatachannel/lib/win_x64/usrsctp.lib

Lib/libdatachannel/lib/win32/datachannel-static.lib
Lib/libdatachannel/lib/win32/juice-static.lib
Lib/libdatachannel/lib/win32/srtp2.lib
Lib/libdatachannel/lib/win32/usrsctp.lib

После публикации пакета для платформы сервер проверяется так:

cmake -S . -B /tmp/videograce-cmake-webrtc-check -DVG_ENABLE_LIBDATACHANNEL=ON
cmake --build /tmp/videograce-cmake-webrtc-check --target VideoGraceServer

Sidecar gateway

Быстрый прототип.

Подходящая библиотека: Pion WebRTC.

Плюсы:

  • Самый быстрый способ проверить production-like WebRTC audio path.
  • Можно изолировать риск от основного сервера.
  • Gateway общается с основным сервером по ControlWS/API и с Translator по UDP.

Минусы:

  • Новый runtime и deployment unit.
  • Нужно решить авторизацию и discovery Translator ports.
  • После прототипа, возможно, придется переносить в C++.

Full libwebrtc

Не рекомендуется для первого этапа.

Плюсы: максимальная совместимость с браузерами.

Минусы: тяжелая сборка, большой бинарный и операционный вес, сложнее сопровождать.

CORE-10 readiness

Готовый WebRTC gateway должен закрывать:

  • signaling webrtc_offer/answer/ice_candidate через ControlWS;
  • publish microphone: browser mic track -> Gateway -> Translator;
  • subscribe microphone: Translator -> Gateway -> browser remote audio track;
  • publish camera: browser camera track -> Gateway -> Translator;
  • subscribe camera: Translator/Gateway -> browser remote video track;
  • WSMedia fallback без регресса для сетей, где UDP/WebRTC/SRTP недоступен;
  • native/WSM/recorder compatibility через Translator path;
  • throttled keyframe requests, чтобы browser RTCP feedback не создавал бесконечный PLI/FIR flood.

Следующий этап

После CORE-10 video compatibility нужно переводить на H.264-first:

  1. SDP/codec negotiation: предпочитать H.264 для WebRTC, VP8 оставить fallback.
  2. RTP inspection: добавить H.264 keyframe detection по IDR/STAP-A/FU-A.
  3. Native clients: добавить OpenH264 software fallback.
  4. Mobile clients: использовать WebRTC как первый transport contract, без встраивания C++ core.
  5. Hardware acceleration: оформлять отдельными backend implementations, не смешивая с базовой H.264-интеграцией.