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выделяет один из UDPTranslator-портов для устройства.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 намеренно перемешивается на сервере, чтобы клиенты распределялись между нодами. Клиентский алгоритм:
- Получить routes через
webrtc_routes_request. - Если список не пустой, пробовать routes по порядку.
- Если route не подключился из-за ICE timeout / connect timeout, перейти к следующему
rtc_node_id. - Если все routes не сработали на первичном подключении в режиме
auto, перейти наWSMediafallback. - Если 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
- Клиент входит в конференцию обычным
connect_to_conference_request. - Клиент запрашивает
device_paramsдля микрофона. - Сервер выдает
device_idиauthor_ssrc. - Клиент создает
RTCPeerConnectionи добавляетgetUserMedia({ audio: true })track. - Клиент выбирает
rtc_node_id, генерируетendpoint_idи отправляет SDP offer черезwebrtc_offer. - RTC-нода отвечает
webrtc_answer, затем клиент и RTC-нода обмениваютсяwebrtc_ice_candidate. - Gateway получает Opus RTP из WebRTC, преобразует SRTP в plain RTP и отправляет его на Translator port с
author_ssrc. - Серверная логика устройства остается прежней: участники получают
device_connect.
Subscribe remote microphone
- Клиент получает
device_connectсreceiver_ssrc,author_ssrc,port. - Клиент выбирает
rtc_node_id, генерируетendpoint_idи отправляетwebrtc_offerсо scopeaudio-subscribe. - RTC-нода регистрирует receiver на Translator:
- отправляет adjusting RTP/RTCP от имени
receiver_ssrc, чтобы Translator запомнил UDP-адрес receiver; - получает RTP автора с Translator;
- отправляет этот поток в браузер как WebRTC audio track.
- Браузер воспроизводит входящий звук через нативный WebRTC/audio path, без JS Opus decoder и без
AudioContextкак основного output.
Publish camera
- Клиент получает
device_id,author_ssrcи Translatorportчерез обычный lifecycle устройства. - Клиент создает WebRTC offer со своим video track.
- Gateway принимает RTP от браузера, нормализует SSRC в
author_ssrcи отправляет plain RTP в Translator. Это сохраняет совместимость с native-клиентами, recorder иWSMServer. - Для браузерных WebRTC-подписчиков Gateway может пересылать RTP напрямую между WebRTC publisher/subscriber tracks, если publisher тоже WebRTC. Это не отменяет Translator: Translator остается ядром для native/WSM/recording path.
Subscribe remote camera
- Клиент получает
device_connectудаленной камеры и открываетvideo-subscribeWebRTC flow. - Если источник - native/WSM/recorder path, Gateway получает RTP из Translator и отправляет его как WebRTC video track.
- Если источник - WebRTC publisher, Gateway может использовать прямой WebRTC forwarding между tracks и параллельно продолжать кормить Translator для остальных типов клиентов.
- 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ожидает targetsrtp2; - собирает 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:
- SDP/codec negotiation: предпочитать H.264 для WebRTC, VP8 оставить fallback.
- RTP inspection: добавить H.264 keyframe detection по IDR/STAP-A/FU-A.
- Native clients: добавить OpenH264 software fallback.
- Mobile clients: использовать WebRTC как первый transport contract, без встраивания C++ core.
- Hardware acceleration: оформлять отдельными backend implementations, не смешивая с базовой H.264-интеграцией.