- PVSM.RU - https://www.pvsm.ru -

Автор: Александр Трищенко
Я расскажу о своем хобби — организации видеотрансляций в браузере по технологии WebRTC (Web Real-Time Communication — веб-коммуникация в режиме реального времени). Этот проект с открытым исходным кодом Google активно развивает с 2012 г., а первый стабильный релиз появился в 2013 г. Сейчас WebRTC уже хорошо поддерживается [1] самыми распространенными современными браузерами, за исключением Safari.
Технология WebRTC позволяет устроить видеоконференцию между двумя или несколькими пользователями по принципу P2P. Таким образом, данные между пользователями передаются напрямую, а не через сервер. Впрочем, сервер нам все равно понадобится, но об этом скажу далее. Прежде всего, WebRTC рассчитана на работу в браузере, но есть и библиотеки для разных платформ, которые тоже позволяют использовать WebRTC-соединение.
Если мы используем WebRTC, мы решаем следующие проблемы:
JavaScript Session Establishment Protocol
Соединение инициализируется по протоколу JavaScript Session Establishment — сейчас есть только черновик [2], который описывает спецификацию этого решения. В нем описывается:
Подключение нового участника к видеоконференции происходит следующим образом: мы отправляем этому пользователю сообщение, что у нас уже идет видеоконференция, пользователь отправляет запрос на подключение, который мы удовлетворяем, выслав ему всю необходимую информацию для подключения. В то же время мы высылаем всем участникам конференции информацию для соединения с новым пользователем.
Как это работает? Есть уровень браузера, который позволяет клиентам непосредственно обмениваться медиаданными, и есть уровень сервера-маяка (signaling server), на котором происходит остальное взаимодействие между клиентами:

Session Description Protocol
Для протокола описания сессий (SDP — Session Description Protocol), который позволяет описать информацию о конкретном пользователе, есть уже утвержденная спецификация RFC 4556 [3].
SDP описывает следующие параметры:

SDP-информация о каком-либо клиенте выглядит примерно так:
Interactive Connectivity Establishment (ICE)
Технология 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 позволяет нам менять аудио- и видеокодеки — их можно указать непосредственно в передаваемой информации 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 [4] (TURN) — это протокол, который позволяет узлу за NAT или межсетевым экраном получать входящие данные через TCP или UDP-соединения. Это уже старая технология, поэтому в приоритете стоит использование Session Traversal Utilities for NAT (STUN) — сетевого протокола, позволяющего установить только UDP-соединение.
Для обеспечения отказоустойчивости есть возможность выбрать несколько STUN-серверов, как это сделать, показано в инструкции [5]. А протестировать подключение к STUN- и TURN-серверам можно здесь [6].
STUN- и TURN-серверы работают следующим образом. Допустим, есть два клиента с внутренними IP и с внешним выходом в сеть через межсетевые экраны:

Чтобы связать эти два клиента и перенаправить все порты, нам необходимо использовать STUN и TURN-серверы, после чего пользователю передается необходимая информация и устанавливается прямое соединение между компьютерами.
Алгоритм работы с ICE-серверами
Производительность и скорость передачи видеопотока
Сильнее всего влияет на производительность 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 на сервер, после чего происходит установление связи между браузерами и начало трансляции.
Как все это выглядит, если мы масштабируем через сервер? Как обычная клиент-серверная архитектура:

Здесь между транслирующим и веб-сервером, ретранслирующим поток, есть WebSocket-соединение и RTCPeerConnection. Остальные устанавливают RTCPeerConnections. Информацию о транслирующем можно получить пулингом, можно сэкономить на количестве WebSocket-соединений.
Способов масштабирования такой архитектуры пока не очень много, и все они похожи. Мы можем:
Во-первых, для повышения отказоустойчивости имеет смысл разделить сервер-маяк и сервер-ретранслятор. Мы можем использовать WebSocket-сервер, который будет раздавать сетевую информацию о клиентах. Сервер-ретранслятор будет общаться с WebSocket-сервером и транслировать через себя медиапоток.
Далее, мы можем организовать возможность быстрого переключения с одного транслирующего сервера на другой в случае отказа приоритетного
Для крайнего случая, можно предусмотреть и возможность установки прямого соединения между пользователями в случае отказа транслирующих серверов, но это годится только на тот случай, когда в нашей конференции участвует немного людей (точно меньше 10).
Вот пример, как мы можем отобразить поток, которым обменялись между клиентами:

onaddstream — мы добавляем слушателя. Когда добавляется слушатель, создаем элемент video, добавляем этот элемент на нашу страницу, а в качестве источника указываем поток. Т. е. чтобы увидеть все это в браузере, просто указываем source HTML5-видео как поток, который получили. Все очень просто.
В случае завершения звонка (метод endCall) мы проходим по элементам видео, останавливаем эти видео и закрываем peer connection, чтобы не было никаких артефактов и зависаний. В случае ошибки завершаем звонок так же.
Напоследок — некоторые библиотеки, которые вы можете использовать для работы с WebRTC. Они позволяют написать простой клиент в несколько десятков строк:
Автор: DataArt
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/119416
Ссылки в тексте:
[1] поддерживается: http://iswebrtcreadyyet.com/
[2] черновик: http://tools.ietf.org/html/draft-ietf-rtcweb-jsep-03
[3] RFC 4556: https://tools.ietf.org/html/rfc4566
[4] Traversal Using Relay NAT: https://tools.ietf.org/html/rfc5766
[5] инструкции: https://www.webrtc-experiment.com/issues/on-iceserver-down.html
[6] здесь: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice
[7] Источник: https://habrahabr.ru/post/282612/
Нажмите здесь для печати.