P2P в браузере

от автора


Автор: Александр Трищенко

Я расскажу о своем хобби — организации видеотрансляций в браузере по технологии 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 — библиотека позволяет организовать сервер-маяк.

ссылка на оригинал статьи https://habrahabr.ru/post/282612/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *