Автор: Александр Трищенко
Я расскажу о своем хобби — организации видеотрансляций в браузере по технологии WebRTC (Web Real-Time Communication — веб-коммуникация в режиме реального времени). Этот проект с открытым исходным кодом Google активно развивает с 2012 г., а первый стабильный релиз появился в 2013 г. Сейчас WebRTC уже хорошо поддерживается самыми распространенными современными браузерами, за исключением Safari.
Технология WebRTC позволяет устроить видеоконференцию между двумя или несколькими пользователями по принципу P2P. Таким образом, данные между пользователями передаются напрямую, а не через сервер. Впрочем, сервер нам все равно понадобится, но об этом скажу далее. Прежде всего, WebRTC рассчитана на работу в браузере, но есть и библиотеки для разных платформ, которые тоже позволяют использовать WebRTC-соединение.
Если мы используем WebRTC, мы решаем следующие проблемы:
- Снижаем расходы на содержание серверов. Серверы нужны только для инициализации соединения и чтобы пользователи обменялись сетевой информацией друг о друге. Также они используются для рассылки каких-то событий, например, оповещений о подключении и отключении пользователей (чтобы информация на каждом клиенте была актуальной).
- Увеличиваем скорость передачи данных и уменьшаем задержки при передачи видео и звука — ведь сервер для этого не нужен.
- Усиливаем приватность данных: нет третьей стороны, через которую шел бы поток данных (конечно, за исключением шлюзов, через которые проходят данные до выхода в сеть).
Инициализация соединения
JavaScript Session Establishment Protocol
Соединение инициализируется по протоколу JavaScript Session Establishment — сейчас есть только черновик, который описывает спецификацию этого решения. В нем описывается:
- процесс подключения к серверу;
- генерация уникального токена для нового пользователя, который позволит идентифицировать его на back-end’е;
- получение пользователем ключа для доступа к разговору — signaling;
- инициализация RTCStreamConnection для соединения двух пользователей;
- получение всеми участниками разговора широковещательного сообщения о новом участнике.
Подключение нового участника к видеоконференции происходит следующим образом: мы отправляем этому пользователю сообщение, что у нас уже идет видеоконференция, пользователь отправляет запрос на подключение, который мы удовлетворяем, выслав ему всю необходимую информацию для подключения. В то же время мы высылаем всем участникам конференции информацию для соединения с новым пользователем.
Как это работает? Есть уровень браузера, который позволяет клиентам непосредственно обмениваться медиаданными, и есть уровень сервера-маяка (signaling server), на котором происходит остальное взаимодействие между клиентами:
Session Description Protocol
Для протокола описания сессий (SDP — Session Description Protocol), который позволяет описать информацию о конкретном пользователе, есть уже утвержденная спецификация RFC 4556.
SDP описывает следующие параметры:
- v= (версия протокола; сейчас версия всегда 0);
- o= (идентификаторы создателя/владельца и сессии);
- s= (имя сессии, не может быть пустым);
- i=* (информация о сессии);
- u=* (URL-адрес, используемый WWW-клиентами, с дополнительной информацией о сессии);
- e=* (e-mail лица, ответственного за конференцию);
- p=* (номер телефона лица, ответственного за конференцию);
- c=* (информация для соединения — не требуется, если есть в описании всех медиаданных);
- b=* (информация о занимаемой полосе пропускания канала связи);
- одна и более строк с описанием параметров времени (пример см. ниже);
- z=* (установка для временной зоны);
- k=* (ключ шифрования);
- a=* (одна или несколько строк с описанием атрибутов сессии, см. ниже).
SDP-информация о каком-либо клиенте выглядит примерно так:
Interactive Connectivity Establishment (ICE)
Технология ICE позволяет подключиться пользователям, находящимся за межсетевым экраном. Она включает в себя четыре спецификации:
- RFC 5389: Session Traversal Utilities for NAT (STUN).
- RFC 5766: Traversal Using Relays around NAT (TURN): Relay Extensions to STUN.
- RFC 5245: Interactive Connectivity Establishment (ICE): A Protocol for NAT Traversal for Offer/Answer Protocols.
- RFC 6544: TCP Candidates with Interactive Connectivity Establishment (ICE)
STUN — клиент-серверный протокол, который активно применяется для VoIP. STUN-сервер здесь считается приоритетным: он позволяет маршрутизировать UDP-траффик. Если мы не сможем воспользоваться STUN-сервером, WebRTC попытается подключиться к серверу TURN. Также в списке есть RFC по самому ICE и RFC по TCP-кандидатам.
Реализация клиента для ICE-серверов в WebRTC уже предустановлена, так что мы можем просто указать несколько серверов STUN или TURN. Более того, для этого при инициализации достаточно просто передать объект с соответствующими параметрами (далее я приведу примеры).
GetUserMedia API
Одна из самых интересных частей WebRTC API — GetUserMedia API, который позволяет захватывать аудио- и видеоинформацию непосредственно с клиента и транслировать ее другим пирам. Этот API активно продвигает Google — так, с конца 2014 г. Hangouts работает полностью на WebRTC. Также GetUserMedia полноценно работает в Chrome, Firefox и Opera; есть также расширение, которое позволяет работать с WebRTC на IE. В Safari же эта технология не поддерживается.
Важно учитывать, что, согласно недавно вышедшему ограничению, в Chrome GetUserMedia API будет работать только под HTTPS на сервере-маяке. Поэтому многие примеры, лежащие в сети на HTTP-серверах, у вас работать не будут.
Интересно, что раньше GetUserMedia API позволял транслировать экран, но сейчас этой возможности нету — есть только custom-решения с помощью дополнений, или же в Firefox можно включить соответствующий флаг на время разработки.
Сейчас у GetUserMedia есть следующие возможности:
- выбор минимального, “идеального” и максимального разрешения для видеопотока, что подразумевает возможность изменять разрешение видео в зависимости от скорости подключения;
- возможность выбрать любую из камер на телефоне;
- возможность указать частоту кадров.
Все это настраивается очень просто. Когда мы вызываем GetUserMedia API, мы передаем туда объект, имеющий два свойства — “audio” и “video”:
{ audio: true, video: { width: 1280, height: 720 } }
Где video, там также может стоять “true” — тогда настройки будут по умолчанию. Если будет стоять “false”, то аудио или видео будет отключено. Мы можем конфигурировать видео — указать ширину и высоту. Или же мы можем, например, установить минимальную, идеальную и максимальную ширину:
width: { min: 1280 }
width: { min: 1024, ideal: 1280, max: 1920 }
А вот так мы выбираем камеру, которую хотим использовать (“user” — фронтальная, “environment” — задняя):
video: { facingMode: "user" }
video: { facingMode: "environment" }
Также мы можем указать минимальную, идеальную и максимальную частоту кадров, которая будет выбираться в зависимости от скорости подключения и ресурсов компьютера:
video: { frameRate: { ideal: 10, max: 15 }
Таковы возможности GetUserMedia API. С другой стороны, в нем явно не хватает возможности определить битрейт видео- и аудиопотока и возможности как-либо работать с потоком до его фактической передачи. Также, конечно, не помешало бы вернуть возможность трансляции экрана, присутствовавшую ранее.
Особенности WebRTC
WebRTC позволяет нам менять аудио- и видеокодеки — их можно указать непосредственно в передаваемой информации SDP. Вообще, в WebRTC используются два аудиокодека, G711 и OPUS (выбираются автоматически в зависимости от браузера), а также видеоформат VP8 (WebM от Google, который отлично работает с HTML5-видео).
Технология WebRTC в той или иной степени поддерживается в Chromium 17+, Opera 12+, Firefox 22+. Для других браузеров можно использовать расширение webrtc4all, но лично мне не удалось его запустить на Safari. Есть также С++ библиотеки для поддержки WebRTC — скорее всего, это говорит о том, что в будущем мы сможем увидеть реализации WebRTC в виде настольных приложений.
Для обеспечения безопасности используется DTLS, протокол безопасности транспортного уровня, который описывается в RFC 6347. А для соединения пользователей, находящихся за NAT и межсетевыми экранами, как я уже говорил, используются TURN и STUN-серверы.
Маршрутизация
Теперь рассмотрим подробно, как осуществляется маршрутизация с помощью STUN и TURN.
Traversal Using Relay NAT (TURN) — это протокол, который позволяет узлу за NAT или межсетевым экраном получать входящие данные через TCP или UDP-соединения. Это уже старая технология, поэтому в приоритете стоит использование Session Traversal Utilities for NAT (STUN) — сетевого протокола, позволяющего установить только UDP-соединение.
Для обеспечения отказоустойчивости есть возможность выбрать несколько STUN-серверов, как это сделать, показано в инструкции. А протестировать подключение к STUN- и TURN-серверам можно здесь.
STUN- и TURN-серверы работают следующим образом. Допустим, есть два клиента с внутренними IP и с внешним выходом в сеть через межсетевые экраны:
Чтобы связать эти два клиента и перенаправить все порты, нам необходимо использовать STUN и TURN-серверы, после чего пользователю передается необходимая информация и устанавливается прямое соединение между компьютерами.
Алгоритм работы с ICE-серверами
- Обеспечить доступ к STUN-серверу на момент инициализации RTCPeerConnection. Можно использовать публичные STUN-серверы (например, от Google).
- Слушать событие инициализации подключения и в случае успеха начинать передачу потока.
- В случае ошибки выполнить запрос к TURN-серверу и постараться соединить клиентов через него.
- Позаботиться о достаточной пропускной способности TURN-сервера.
Производительность и скорость передачи видеопотока
- 720p at 30 FPS: 1.0~2.0 Mbps
- 360p at 30 FPS: 0.5~1.0 Mbps
- 180p at 30 FPS: 0.1~0.5 Mbps
Сильнее всего влияет на производительность WebRTC траффик, который нужно выделять на трансляцию видео. Если в трансляции участвуют всего два пользователя, то можно организовать без проблем хоть FullHD, в случае же видеоконференции на 20 человек у нас будут проблемы.
Вот, например, результаты, полученные с MacBook Air 2015 (4 Гб оперативной памяти, процессор Core i на 2 ГГц). Просто реализация GetUserMedia грузит процессор на 11 %. При инициализации соединения Chrome-Chrome процессор загружен уже на 50 %, если три Chrome — на 70 %, четыре — полная загрузка процессора, а пятый клиент приводит уже к тормозам, и WebRTC в итоге вылетает. На мобильных браузерах, конечно, еще хуже: мне удалось связать только двух пользователей, а при попытке подключения третьего все стало тормозить и соединение оборвалось.
Как с этим можно справиться?
Масштабирование и решение проблем
Итак, что мы имеем? На каждого пользователя конференции надо открывать свое UDP-соединение и передавать данные. В итоге на трансляцию 480p-видео со звуком одному пользователю нужно 1-2 мебагита, и 2-4 мегабита в случае двухсторонней связи. На железо транслирующего ложится большая нагрузка.
Проблема производительности решается путем использования прокси-сервера (ретранслятора), который будет получать данные от вещающего, если это конференция, и раздавать их всем остальным. Если нужно, для этого можно использовать не один сервер, а несколько.
Также стоит стоит учитывать, что, если мы проводим видеотрансляцию, передаем очень много избыточной клиентской информации, от которой можем отказаться. Например, при проведении курса нам от других может быть нужен только звук без видео или даже только сообщения. Также после смены активного докладчика может иметь смысл прервать одно из соединений, изменить тип информации, который мы получаем в потоке, после чего подключиться заново. Это позволяет оптимизировать сетевую нагрузку.
Важно учесть программные ограничения — мы можем подключить не более 256 пиров к одному инстансу WebRTC. Это лишает нас возможности использовать, например, какой-нибудь громадный и дорогой инстанс на Amazon для масштабирования, т. ч. рано или поздно нам придется связывать между собой несколько серверов.
CreateOffer API
Как это все работает? Есть API CreateOffer(), который позволяет создать SDP-данные, которые мы будем отправлять:
CreateOffer — promise, который после создания offer позволяет получить непосредственно localDescription, поместить в него offer и использовать его далее как SDP-поле. sendToServer — абстрактная функция для запроса к серверу, где написано имя нашей машины (name), имя целевой машины (target), тип offer и SDP.
Хочу сказать пару слов о сервере: для данных целей очень удобно использовать WebSocket. Как только кто-то подключается, мы можем заэмитить событие и добавить на него слушателя. Также нам будет довольно просто эмитить свои события с клиентом.
GetUserMedia API в действии
В реальной жизни все можно сделать немного проще — мы можем обратиться к GetUserMedia API. Вот как тогда выглядит инициализация звонка:
Здесь мы хотим передать и видео и звук. На выходе получаем промис, который принимает в себя объект mediaStream (это и есть поток данных).
А вот так мы отвечаем на звонок:
Когда кто-то хочет ответить на звонок, он получает наш offer (getRemoteOffer— абстрактная функция, которая получает наши данные с сервера). Затем getUserMedia инициализирует потоковую передачу, а об onaddstream и addstream я уже сказал. setRemoteDescription — мы это инициализируем для нашего RTC и указываем его в качестве offer’а и передаем ответ (answer). send the answer… > тут мы просто описываем процесс передачи offer на сервер, после чего происходит установление связи между браузерами и начало трансляции.
Клиент-серверная архитектура для WebRTC
Как все это выглядит, если мы масштабируем через сервер? Как обычная клиент-серверная архитектура:
Здесь между транслирующим и веб-сервером, ретранслирующим поток, есть WebSocket-соединение и RTCPeerConnection. Остальные устанавливают RTCPeerConnections. Информацию о транслирующем можно получить пулингом, можно сэкономить на количестве WebSocket-соединений.
Способов масштабирования такой архитектуры пока не очень много, и все они похожи. Мы можем:
- Увеличить количество серверов.
- Разместить серверы в зонах наибольшего скопления целевой аудитории.
- Обеспечить избыточную производительность — ведь технология еще активно развивается, так что возможны сбои в работе.
Повышаем отказоусточивость
Во-первых, для повышения отказоустойчивости имеет смысл разделить сервер-маяк и сервер-ретранслятор. Мы можем использовать WebSocket-сервер, который будет раздавать сетевую информацию о клиентах. Сервер-ретранслятор будет общаться с WebSocket-сервером и транслировать через себя медиапоток.
Далее, мы можем организовать возможность быстрого переключения с одного транслирующего сервера на другой в случае отказа приоритетного
Для крайнего случая, можно предусмотреть и возможность установки прямого соединения между пользователями в случае отказа транслирующих серверов, но это годится только на тот случай, когда в нашей конференции участвует немного людей (точно меньше 10).
Отображение медиапотока
Вот пример, как мы можем отобразить поток, которым обменялись между клиентами:
onaddstream — мы добавляем слушателя. Когда добавляется слушатель, создаем элемент video, добавляем этот элемент на нашу страницу, а в качестве источника указываем поток. Т. е. чтобы увидеть все это в браузере, просто указываем source HTML5-видео как поток, который получили. Все очень просто.
В случае завершения звонка (метод endCall) мы проходим по элементам видео, останавливаем эти видео и закрываем peer connection, чтобы не было никаких артефактов и зависаний. В случае ошибки завершаем звонок так же.
Полезные библиотеки
Напоследок — некоторые библиотеки, которые вы можете использовать для работы с WebRTC. Они позволяют написать простой клиент в несколько десятков строк:
- Simple peer — мне очень нравится эта библиотека, которая решает задачу создания соединения между двумя пользователями.
- Easyrtc — позволяет создавать видеоконференции.
- SimpleWebRTC — аналогичная библиотека.
- js-platform/p2p.
- node-webrtc — библиотека позволяет организовать сервер-маяк.
Комментарии (24)
Mugik
28.04.2016 00:29-2https://hangouts.google.com/
Сам гигант google — он же как вы и упомянули — автор этого, пока не довел до ума технологию. Всё лагает, валиться, тормозит, больше 4 человек — всё, это уже не конференция а проверка компьютера на производительность и сеть на пропускную способность.
Если уж google уже с 2010 собирает технологию и всё собрать не может. Что уж нам туда соваться.
aylarov
28.04.2016 09:21Все с WebRTC нормально, проблемы есть, но их количество быстро уменьшается. Лагает и тормозит, потому что видео-кодек по умолчанию VP8 без аппаратной поддержки, скоро можно будет пользоваться H.264 и ситуация станет лучше + пока нет нормального simulcast, не говоря уж о SVC. Про серверные пиры все весьма нетривиально, DTLS/sRTP и вот это все заставляют много кода породить, чтобы с этим жить, но платформы а-ля VoxImplant берут эту проблему на себя.
Wicron
28.04.2016 13:04Опровергните или объясните то, как вы смогли передавать 720p, если класс видеозахвата в браузере кушает всегда 640х480 при подсоединении к web-камере.
DataArt
28.04.2016 13:54+2В Firefox все параметры захвата работают отлично из коробки, специально только что перепроверил. Chrome просит список mandatory-параметров, тогда тоже захватывает все отлично. Проверил на 1.3 mpx камере, ОС — El Capitan, версии браузеров — последние доступные. Захватывает честные 1280х720. Учитывая, что довольно большая доля камер на рынке сами по себе не умеют больше 640х480, думаю, проблема связана именно с этим.
M-A-XG
28.04.2016 16:08У кого есть стабильные источники трансляций с доступом скажем по IP, чтобы они не разлетелись по интернету?
Черканите по контактам с http://football-online2.com/
Или тут в личку, если она работает.
Буду премного благодарен. :)
Возможно и платные источники.
В первую очередь интересуют Футбол 1 и Футбол 2.
zartdinov
29.04.2016 18:30В начале статьи вы написали три проблемы. В итоге не решили ни одну из них, почему-то выбрав архитектуру, в которой p2p используется в последнюю очередь.
AlexMartsinkevich
29.04.2016 18:311. Автор, когда писал про то, что web RTC не поддерживается в Safari, оказался прав, однако есть решение в лице open source библиотеки Adapter.js — она упрощает работу с браузерными префиксами и позволяет транслировать видео даже в таких браузерах, как IE и Safari.
2. Когда автор писал, что web RTC хорошо работает, наверное, погорячился. Разве что только в Chome и когда 2-3 пользователя. Но когда дело доходит хотя до Firefox или у нас более 3-х пользователей, начинаются проблемы: иногда тупо не хватает мощности компьютера или пропускной способности сети — как итог, виснущий браузер и неадекватная передача аудио и видео.
Google предстоит проделать огромный объем работы, чтобы привести эту технологию в порядок. Я около года сижу на проекте, связанным с web RTC и он мне подарил немало седых волос) Но в любом случае технология очень интересная и перспективная.
Wicron
05.05.2016 11:171. По моим сведениям стриминг без надстроек ограничен 255 пользователями в Web-rtc
2, Класс захвата видеоизображения кажется не использует перекодирование MJPEG в VP8/9, а это значит что выбирается несжатый поток, а как многие знают, USB 2.0 не может отдать в таком формате больше 1-5 кадров в секунду при разрешениях выше 800х600 включительно. Что это означает? Это и дает тот убогий эффект качества, что приходится видеть (640х480 25 fps). Для владельцев Mac и мобильных устройств это не критично — там интерфейсы имеют большую ширину канала и могут тянуть выше — лишь бы хватало мощности. Знатоков прошу проверить эту догадку.
3. Потребляемая мощность на работу web-rtc растет и да, действительно нужен серьезный компьютер.
zartdinov
05.05.2016 14:38Я бы посоветовал попробовать на транслирующем сервере nginx-rtmp модуль, вся работа будет напоминать работу со статикой (ретрансляции, балансировки, cdn… )
WebRTC пока же выглядит немного монструозным, все решения на стороне сервера быстро теряют актуальность, живут в основном обертки над openwebrtc и те, у которых браузерный движок внутри, chromium там и др. Желания не возникает на каждом легком воркере кормить почти целый браузер из-за казалось бы, каких то там udp коннектов.
Ну технологии и цели, конечно, тут разныеaylarov
05.05.2016 17:46WebRTC пока же выглядит немного монструозным, все решения на стороне сервера быстро теряют актуальность, живут в основном обертки над openwebrtc и те, у которых браузерный движок внутри, chromium там и др. Желания не возникает на каждом легком воркере кормить почти целый браузер из-за казалось бы, каких то там udp коннектов.
Извините, но это полный бред.zartdinov
06.05.2016 10:46Здесь уже отмечали проблему серверных решений и самого стандарта, поэтому не стал расписывать.
К примеру, меня интересуют реализации webrtc на go и js. Замечу, кстати, что это вроде бы далеко не последние языки для работы с сетью и протоколами и должны иметь не последнее значение для тех же google и других.
Возьмем полезную библиотеку simple-peer из статьи и просто установим для него актуальный wrtc модуль для nodejs, немного подождем (20-30 мин всего), заметим как в проект (к примеру, это был микросервис, которые вместе с ОС занимал 5-20 МБ) добавился зоопарк технологий на 256 МБ
Для golang достаточно открыть топовую либу go-webrtc и увидеть следующую строчку, чтобы понять, что все на том же уровне
«The hard way is to build from scratch, which involves Google's depot_tools and chromium stuff, gclient syncing, which takes a couple hours, and possibly many more if you run into problems...»
Не расписываю остальной вагон мертвых и любительских библиотек этого чудо стандарта, который гиганты дорабатывают годы (одновременно приторговывать turn серверами за углом), позволяющий в 21 веке передавать данные (на 5 компов без лагов) «прямо в браузере» (типа чудо вообще, не то что скайп или ртс-шутер какой нить на 100 человек)aylarov
06.05.2016 10:49См. предыдущий комментарий. Если вы не способны разобраться в том как работает sRTP, DTLS, SDP, ICE, то почему кто-то вам обязан за вас это сделать и выложить, да еще и на языке, который вам типа нравится. Возьмите и сделайте свое. А Opensource приличного на эту тему хватает, тот же Jitsi
pixelcube
Окей, но вот жаль, что про серверные пиры тема нисколько не раскрыта
DataArt
Эта тема заслуживает отдельной статьи.