WebRTC: голосовая и видеосвязь в браузере — архитектура, качество и масштабирование

Как устроен WebRTC: установка соединения и сигнализация, STUN и TURN, архитектуры групповых звонков mesh, SFU и MCU, кодеки и мониторинг качества, безопасность, масштабирование и пример платформы на 10 000+ соединений.

WebRTC позволяет передавать голос, видео и данные между участниками в реальном времени прямо из браузера и мобильного приложения — без плагинов и отдельных программ. На нём построены видеосвязь в браузере, звонки из личного кабинета, телемедицина, онлайн-обучение, совместная работа с документами и голосовые платформы. Первый работающий звонок между двумя вкладками браузера делается за вечер по любому руководству. Промышленная система, где связь работает через корпоративные сети и мобильный интернет, выдерживает тысячи одновременных соединений и не превращается в набор разорванных звонков в часы пик, — отдельная инженерная задача. Ниже — как устроен WebRTC, какие компоненты нужны кроме браузерного API, какие архитектуры применяются для групповых звонков, как обеспечить качество, безопасность и масштабирование и когда выгоднее готовая платформа. Из чего состоит WebRTC getUserMedia и getDisplayMedia — доступ к камере, микрофону и экрану. RTCPeerConnection — соединение между участниками: согласование параметров, передача медиа, шифрование, адаптация к качеству сети. RTCDataChannel — передача произвольных данных с низкой задержкой: сообщения, файлы, состояние совместного редактирования, события игры. Важно понимать, чего WebRTC не делает: он не определяет, как участники найдут друг друга и обменяются параметрами соединения. Эту часть — сигнализацию — разработчик реализует сам. Как устанавливается соединение Инициатор создаёт описание сессии — offer — с поддерживаемыми кодеками и параметрами. Offer передаётся второму участнику через сервер сигнализации. Второй участник создаёт ответ — answer — и отправляет его обратно. Параллельно обе стороны собирают сетевые кандидаты — возможные адреса, по которым к ним можно подключиться, — и обмениваются ими. Механизм ICE проверяет пары кандидатов и выбирает работающий путь. Устанавливается зашифрованное соединение, начинается передача медиа. Код: сторона инициатора const pc = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.example.ru:3478' }, { urls: 'turn:turn.example.ru:3478', username: turnUser, credential: turnPass }, ], }); const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); stream.getTracks().forEach((track) = pc.addTrack(track, stream)); pc.onicecandidate = ({ candidate }) = { if (candidate) signaling.send({ type: 'candidate', candidate }); }; pc.ontrack = ({ streams: [remote] }) = { remoteVideo.srcObject = remote; }; const offer = await pc.createOffer(); await pc.setLocalDescription(offer); signaling.send({ type: 'offer', sdp: pc.localDescription }); signaling.on('answer', ({ sdp }) = pc.setRemoteDescription(sdp)); signaling.on('candidate', ({ candidate }) = pc.addIceCandidate(candidate)); Код: сторона получателя signaling.on('offer', async ({ sdp }) = { await pc.setRemoteDescription(sdp); const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); stream.getTracks().forEach((track) = pc.addTrack(track, stream)); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); signaling.send({ type: 'answer', sdp: pc.localDescription }); }); В реальном приложении нужно обрабатывать ситуацию, когда обе стороны одновременно инициируют пересогласование. Для этого используют паттерн «вежливого участника», при котором одна из сторон уступает при конфликте. Сигнализация Сервер сигнализации передаёт между участниками описания сессий и кандидатов, а также управляет комнатами, присутствием и правами. Обычно это WebSocket-сервер. import { WebSocketServer } from 'ws'; const rooms = new Map(); // roomId → Set ws const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', (ws, req) = { const user = authenticate(req); // проверка токена обязательна if (!user) return ws.close(4401, 'unauthorized'); ws.on('message', (raw) = { const msg = JSON.parse(raw); if (msg.type === 'join') { if (!canJoin(user, msg.roomId)) return; ws.roomId = msg.roomId; if (!rooms.has(msg.roomId)) rooms.set(msg.roomId, new Set()); rooms.get(msg.roomId).add(ws); return; } for (const peer of rooms.get(ws.roomId) ?? []) { if (peer !== ws) peer.send(JSON.stringify({ ...msg, from: user.id })); } }); ws.on('close', () = rooms.get(ws.roomId)?.delete(ws)); }); Главное требование — аутентификация и проверка прав до входа в комнату. Сервер сигнализации без проверки позволяет подключиться к чужому звонку, зная идентификатор комнаты. NAT, STUN и TURN Большинство устройств находятся за маршрутизаторами и не имеют публичного адреса. Чтобы участники могли соединиться, используются вспомогательные серверы. STUN сообщает устройству его публичный адрес. Лёгкий сервер, почти не нагружен. Во многих сетях этого достаточно для прямого соединения. TURN ретранслирует медиатрафик, когда прямое соединение невозможно: строгие корпоративные межсетевые экраны, некоторые мобильные операторы, симметричный NAT. Нагружен трафиком и требует пропускной способности. Доля звонков, которым нужен TURN, сильно зависит от аудитории: для пользователей из корпоративных сетей она заметно выше, чем для домашних. Без TURN часть звонков просто не соединится, и пользователь увидит бесконечное подключение. Промышленная система всегда включает собственный TURN-сервер — например, coturn — с временными учётными данными, работой по TLS на порту 443 для прохождения через строгие межсетевые экраны и размещением ближе к пользователям. Публичные бесплатные STUN-серверы подходят для экспериментов, но не для продакшена: их доступность не гарантирована, а для российских проектов важно не зависеть от зарубежной инфраструктуры. Групповые звонки: архитектуры Mesh — все со всеми Каждый участник устанавливает соединение с каждым. Сервер нужен только для сигнализации. Работает для двух-четырёх участников: при каждом новом участнике растут исходящий трафик и нагрузка на процессор у всех, и на мобильных устройствах и слабом интернете качество быстро падает. SFU — сервер выборочной пересылки Каждый участник отправляет свой поток один раз на сервер, а сервер пересылает его остальным, не декодируя. Самая распространённая архитектура для видеоконференций: нагрузка на клиентов не растёт с числом участников, а сервер может отправлять разным получателям разное качество. Открытые решения — mediasoup, Janus, LiveKit и другие. MCU — сервер микширования Сервер декодирует все потоки, смешивает их в один и отправляет каждому участнику. Минимальная нагрузка на клиентов, но очень высокая на сервер и дополнительная задержка. Применяется для интеграции со старыми системами видеосвязи и записи смешанного потока. Simulcast и SVC Отправитель кодирует видео в нескольких качествах одновременно (simulcast) или в многослойном формате (SVC), а SFU выбирает для каждого получателя подходящий слой: высокое качество для крупной плитки на широком канале, низкое — для миниатюр и слабого интернета. Качество связи Кодеки. Opus — стандарт для голоса с адаптацией к потерям и широкой полосой. Для видео — VP8, VP9, H.264, AV1; выбор зависит от поддержки на устройствах и доступности аппаратного кодирования. Адаптация битрейта. WebRTC автоматически снижает качество при ухудшении сети. Приложение может задавать ограничения: максимальный битрейт, приоритет чёткости или плавности. Статистика. RTCPeerConnection.getStats() даёт потери пакетов, джиттер, задержку, битрейт и выбранный путь соединения. Эти данные нужно собирать и отправлять в мониторинг. Обработка звука. Подавление эха и шума и автоматическая регулировка громкости включаются ограничениями getUserMedia . Переподключение. При смене сети — переходе с Wi-Fi на мобильный интернет — выполняется перезапуск ICE без разрыва звонка. setInterval(async () = { const stats = await pc.getStats(); stats.forEach((r) = { if (r.type === 'inbound-rtp' r.kind === 'audio') { metrics.push({ jitter: r.jitter, packetsLost: r.packetsLost }); } if (r.type === 'candidate-pair' r.state === 'succeeded' r.nominated) { metrics.push({ rtt: r.currentRoundTripTime }); } }); }, 5000); Безопасность медиа и данные в WebRTC шифруются обязательно — протоколами DTLS и SRTP, отключить шифрование нельзя; доступ к камере и микрофону возможен только на страницах по HTTPS и с разрешения пользователя; при использовании SFU сервер имеет доступ к расшифрованному трафику на уровне транспорта; если требуется, чтобы оператор платформы не мог получить содержимое, применяют сквозное шифрование через Insertable Streams — это усложняет запись и серверную обработку; сервер сигнализации и TURN проверяют аутентификацию, TURN использует временные учётные данные; идентификаторы комнат не должны быть угадываемыми, а права на вход проверяются на сервере; утечка локальных IP-адресов через кандидатов ICE ограничивается современными браузерами, но для чувствительных сценариев стоит проверить политику кандидатов; запись звонков и обработка голоса — это персональные данные: нужны уведомление участников, основания обработки и хранение с учётом требований 152-ФЗ. Пример из практики: платформа голосовой связи Мы разрабатывали платформу голосовой связи на WebRTC с требованиями к минимальной задержке, высокому качеству звука и работе под нагрузкой тысяч одновременных соединений. Проект занял 8 месяцев, команда — 12 специалистов; стек — WebRTC, React, TypeScript, Node.js, PostgreSQL, Redis, Docker, Kubernetes и кодек Opus. Показатели из опубликованного кейса : задержка передачи — менее 100 мс; 10 000+ одновременных соединений; кодек Opus 48 кГц; доступность — 99,95%; автоматическое масштабирование под нагрузкой; клиенты для веба, iOS, Android и десктопа. В любой подобной системе такие характеристики — результат не одного параметра, а сочетания решений: архитектуры медиасерверов, инфраструктуры для прохождения через NAT, мониторинга качества соединений и масштабирования под нагрузку. Из требований проекта видно, что мониторинг качества связи в реальном времени, автоматическое масштабирование и отказоустойчивость закладывались как отдельные задачи, а не как побочный эффект выбора технологии. Масштабирование и эксплуатация медиасерверы масштабируются горизонтально, а распределение комнат между ними учитывает нагрузку и географию участников; сервер сигнализации хранит состояние комнат во внешнем хранилище, например Redis, чтобы работать в нескольких экземплярах; TURN-серверы размещаются в нескольких регионах и имеют достаточную пропускную способность; метрики качества — потери, джиттер, задержка, доля звонков через TURN, неудачные подключения — выводятся на дашборды с оповещениями; нагрузочное тестирование проводится с эмуляцией реальных сетевых условий, а не только на быстрой локальной сети. Как устроить контейнерную инфраструктуру и её наблюдаемость, — в статьях о Docker и Kubernetes и о мониторинге . Своя разработка или готовая платформа Готовые облачные платформы видеосвязи с SDK дают быстрый старт и снимают эксплуатацию медиаинфраструктуры. Ограничения — стоимость на больших объёмах, зависимость от провайдера, требования к размещению данных и ограниченная кастомизация. Открытые медиасерверы — баланс контроля и скорости: не нужно писать SFU с нуля, но эксплуатация и масштабирование на команде. Полностью собственная разработка оправдана, когда голос или видео — основа продукта, нужны нестандартные сценарии, строгие требования к данным или экономика больших объёмов. Если звонки нужно анализировать — оценивать качество разговоров, извлекать темы, заполнять CRM, — пригодится статья о речевой аналитике . Платформы голосовой и видеосвязи мы разрабатываем в рамках разработки веб-проектов и мобильных приложений . Частые вопросы Нужен ли сервер, если WebRTC — это P2P? Да. Сервер сигнализации нужен всегда, STUN и TURN — для прохождения через NAT и межсетевые экраны, а для групповых звонков больше нескольких участников — медиасервер. Полностью бессерверный WebRTC существует только в учебных примерах. Сколько участников выдержит звонок без медиасервера? В схеме «все со всеми» — обычно не больше нескольких участников с видео, дальше качество падает из-за нагрузки на исходящий канал и процессор. Для групповых з