Империи зла нередко получают лучи ненависти со стороны конечных пользователей. Не смотря на это, Uber частично оплачивает наши поездки, хоть и временно, а Google придал значительное ускорение технологии WebRTC, которая бы так и оставалась проприетарной и сильно платной софтиной для узких целей b2b, если бы не ИЗ.

После появления WebRTC, видеочаты стало делать проще. Появились различные API и сервисы, серверы и фреймворки. В данной статье мы подробно опишем еще один способ разработки видеочата между веб-браузером и нативным Android-приложением

Видеочат в браузере

Классический WebRTC видеочат между браузерами начинается с обмена SDP (session description protocol). Алиса отправляет свой SDP по сети Борису, а Борис отвечает своим. SDP, который представляет собой вот такой конфиг:

o=- 1468792194439919690 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS 9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:kSrQ
a=ice-pwd:4uIyZd/mbVSVe2iMFgC1i3qn
a=fingerprint:sha-256 6B:29:2F:47:EB:38:64:F3:25:CE:BD:E6:B0:3F:A6:FA:55:57:A9:EA:44:0B:7C:45:D2:0D:F4:96:8D:B2:9F:BA
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:3525514540 cname:drYey7idt605CcEG
a=ssrc:3525514540 msid:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4 09bdb6e7-b4b3-437b-945e-771f535052e3
a=ssrc:3525514540 mslabel:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
a=ssrc:3525514540 label:09bdb6e7-b4b3-437b-945e-771f535052e3
m=video 9 UDP/TLS/RTP/SAVPF 96 98 100 102 127 97 99 101 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:kSrQ
a=ice-pwd:4uIyZd/mbVSVe2iMFgC1i3qn
a=fingerprint:sha-256 6B:29:2F:47:EB:38:64:F3:25:CE:BD:E6:B0:3F:A6:FA:55:57:A9:EA:44:0B:7C:45:D2:0D:F4:96:8D:B2:9F:BA
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=sendonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtpmap:100 H264/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:102 red/90000
a=rtpmap:127 ulpfec/90000
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:125 rtx/90000
a=fmtp:125 apt=102
a=ssrc-group:FID 2470936840 2969787502
a=ssrc:2470936840 cname:drYey7idt605CcEG
a=ssrc:2470936840 msid:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4 ce9235c5-f300-466a-aadd-b969dc2f3664
a=ssrc:2470936840 mslabel:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
a=ssrc:2470936840 label:ce9235c5-f300-466a-aadd-b969dc2f3664
a=ssrc:2969787502 cname:drYey7idt605CcEG
a=ssrc:2969787502 msid:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4 ce9235c5-f300-466a-aadd-b969dc2f3664
a=ssrc:2969787502 mslabel:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
a=ssrc:2969787502 label:ce9235c5-f300-466a-aadd-b969dc2f3664

Например из этого SDP-конфига можно сказать, что предлагается использовать кодеки H.264 и VP8, для видео и Opus для аудио. Кроме этого получить много другой полезной информации для созвона, такой как приоритет кодеков, использование фидбеков fir, nack, pli, профиль и level для кодека H.264 42e01f — Baseline 3.1, и т.д.

В процессе реализации видеочата на нативном WebRTC API желательно понимать что такое SDP, кандидаты (candidates), кодеки, ICE, STUN, TURN и много других страшных слов.

2_video_chat_in_browser

WebRTC, Websockets и SIP

Достаточно часто путаются понятия относительно WebRTC и Websocket. Иногда к этой путанице добавляется еще и SIP.

3_WebRTC_Websockets

Точно можно сказать, что WebRTC не имеет прямого отношения ни к Websockets ни к SIP.

Websockets — это лишь удобная возможность передать SDP от Бориса к Алисе. Вместо Websockets мы могли бы использовать plain HTTP или отправить SDP текст по почте. Обмен SDP сообщениями является сигнальной информацией и эти данные могут быть переданы по какому угодно протоколу. Для браузеров дефолтные протоколы передачи данных это: Websockets и HTTP. Поэтому используется Websockets, как более реалтаймовый вариант по сравнению с HTTP. Через Websockets не передается аудио и видео. Только сигнальная информация: текст и команды.

4_WebRTC_SIP

SIP — это текстовый протокол обмена сообщениями. WebRTC иногда незаслуженно называют SIP-ом в браузере, скорее всего за то, что в SIP сообщениях также используется SDP для конфигурации кодеков и установки соединения.

С другой стороны, когда мы говорим, например SIP-телефон, мы имеем в виду устройство, которое наряду с протоколом SIP (RFC3261) поддерживает еще десяток другой сетевых спецификаций и протоколов: RTP, SDP, AVPF, и т.д.

Действительно, WebRTC на сетевом уровне использует похожие строительные блоки с теми, что использует SIP-телефон (SRTP, STUN, и.т.д.). Поэтому можно сказать что и WebRTC и SIP-устройства / ПО используют схожую базу технологий. Но называть WebRTC, SIP-ом в браузере было бы некорректно, хотя бы потому, что SIP-а в браузерах из-коробки нет.

5_WebRTC_Websockets_SIP

WebRTC — это технология, которая имеет три основных функции в части передачи аудио / видео:

  • Захват, кодирование и отправка
  • Прием декодирование и воспроизведение
  • Преодоление NAT и Firewall

И много вспомогательных функций, таких как борьба с джиттером, адаптивный битрейт, контроль перегрузок сети, и т.д.

Как было описано выше, чтобы передача медиа по WebRTC состоялась, Алиса и Борис должны обменяться SDP, которые содержат подробную информацию о форматах видеопотоков, пакетизации и другие параметры, определяющие как именно сторона будет принимать видео.

Кроме обмена SDP может потребоваться TURN-сервер, который будет пропускать через себя видеотрафик в том случае если соединение Peer-to-Peer не будет установлено, например если у Алисы или у Бориса выявился достаточно недружелюбный (например симметричный) NAT.

Предположим, мы захотели добавить к чату третьего активного участника или просто зрителя. Хороший пример в данном случае — дебаты. Два участника разговаривают — остальные смотрят. Еще один пример — чат на трех и более участников.

При появлении третьего участника, схема усложняется тем, что каждому из участников приходится захватывать и жать уже по два видеопотока вместо одного, а также попарно устанавливать соединения, пытаясь преодолеть NAT. В этом случае может резко увеличиться время установки соединения и уменьшается его стабильность. Одновременное сжатие и отправка двух и более видеопотоков создает уже серьезную нагрузку на процессор и сеть и влияет на качество в худшую сторону, в особенности на мобильных устройствах:

6_WebRTC_Websockets_SIP

Подобные задачи, такие как

  • подключение трех и более участников
  • подключение дополнительных зрителей видеочата
  • запись видеочата

выходят за рамки успешной применимости peer-to-peer и требуют центрального WebRTC-сервера, управляющего этими подключениями.

7_WebRTC_server

Как было упомянуто выше, существуют сервисы и серверы, удобные и неудобные API поверх WebRTC API, которые позволяют ускорить разработку видеочатов и работать с более удобными абстракциями, например видеопоток (Stream), комната (Room), публикующий (Publisher), зритель (Subscriber), и т.д.

Например, для создания самого простого видеочата было бы достаточно обменяться названиями потоков. Борис знает поток Алисы. Алиса знает поток Бориса — и видеочат готов:

8_server_streamname

Пример видеочата в браузере

В этой статье мы покажем как работает Streaming API с Web Call Server 5 — WebRTC сервером для видеочатов и онлайн-трансляций.

Видеочат в действии можно проиллюстрировать в двух следующих скриншотах. Первый участник Alice будет видеть видеочат так:

9_video_chat

Второй участник Edward будет видеть видеочат так:

10_video_chat_in_browser

В данном примере происходит следующее:

  1. Alice отправила на сервер видеопоток из браузера с именем Alice.
  2. Edward отправил на сервер видеопоток из браузера с именем Edward.
  3. Alice забрала и проиграла видеопоток с именем Edward.
  4. Edward забрал и проиграл видеопоток с именем Alice.

Как видно из этого примера, мы построили видеочат, основываясь лишь на том, что Alice и Edward знают имена потоков друг друга. Нам не пришлось работать напрямую с SDP, PeerConnection, NAT, TURN, и т.д.

Таким образом, видеочат можно реализовать просто передавая имена потоков тем, кто должен их проиграть.

В этой простой концепции вы можете использовать любые front-end и back-end технологии, такие как Jquery, Bootstrap, React, Angular, PHP, Java, .Net, и т.д. И хорошая новость в том, что встраивание поддержки видеопотоков и видеочата никак не влияет на существующее веб-приложение. Вы управляете вашим видеочатом, просто позволяя (или не позволяя) его участникам играть нужные видеопотоки.

Код видеочата в браузере

Теперь покажем как это выглядит в коде. HTML-страница с видеочатом содержит два основных div-элемента:

  • localVideo — видео, которое захватывается с веб-камеры
  • remoteVideo — видео, которое воспроизводится с сервера

11_source_code_of_video_chat

Можно дать этим элементам любые произвольные идентификаторы, например id=»captureVideo» или id=»playbackVideo», но важно, чтобы эти элементы присутствовали на странице.

Рабочая HTML-страница с блоками localVideo и remoteVideo выглядит так:

<html>
<head>
    <script language="javascript" src="https://flashphoner.com/downloads/builds/flashphoner_client/wcs_api-2.0/current/flashphoner.js"></script>
    <script language="javascript" src="video-chat.js"></script>
</head>
<body onLoad="init()">
<h1>Video Chat</h1>
<div id="localVideo" style="width:320px;height:240px;border: 1px solid"></div>
<div id="remoteVideo" style="width:320px;height:240px;border: 1px solid"></div>
<input type="button" value="connect" onClick="connect()"/>
<input type="button" value="publish" onClick="publish('Alice')"/>
<input type="button" value="play" onClick="play('Edward')"/>
<p id="status"></p>
</body>
</html>

Теперь приведем код, который отвечает непосредственно за отправку и воспроизведение видео.

Отправка потока с вебкамеры

При отправке мы используем метод API session.createStream().publish() и указываем для этого потока HTML div-элемент, в котором будет отображаться захваченное с камеры видео localVideo, а также название видеопотока Alice, зная которое можно будет воспроизвести этот видеопоток другим подключившимся клиентом.

session.createStream({
        name: "Alice",
        display: localVideo,
        cacheLocalResources: true,
        receiveVideo: false,
        receiveAudio: false
    }).on(Flashphoner.constants.STREAM_STATUS.PUBLISHING, function (publishStream) {
        setStatus(Flashphoner.constants.STREAM_STATUS.PUBLISHING);
    }).on(Flashphoner.constants.STREAM_STATUS.UNPUBLISHED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.UNPUBLISHED);
    }).on(Flashphoner.constants.STREAM_STATUS.FAILED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.FAILED);
    }).publish();

Воспроизведение видеопотока с сервера

При воспроизведении мы указываем название потока, который будем воспроизводить и HTML div-элемент remoteVideo, в котором будет идти воспроизведение потока, полученного с сервера. Используется метод API session.createStream().play().

    session.createStream({
        name: "Edward",
        display: remoteVideo,
        cacheLocalResources: true,
        receiveVideo: true,
        receiveAudio: true
    }).on(Flashphoner.constants.STREAM_STATUS.PLAYING, function (playStream) {
        setStatus(Flashphoner.constants.STREAM_STATUS.PLAYING);
    }).on(Flashphoner.constants.STREAM_STATUS.STOPPED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.STOPPED);
    }).on(Flashphoner.constants.STREAM_STATUS.FAILED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.FAILED);
    }).play();

Во время работы с сервером, HTML-страница будет получать различные статусы, например PLAYING, STOPPED для воспроизведения и PUBLISHING, UNPUBLISHED для публикации потока.

Таким образом, основное, что требуется для работы видеочата — это расположить на веб-странице два div-блока и подключить соответствующие скрипты, которые выполняют stream.play() и stream.publish() по названию потока.

Полный исходный код примера Two Way Streaming доступен для скачивания здесь.

Пример WebRTC видеочата в приложении под Android

Видеочат для Android будет работать в точности по тем же принципам, что и видеочат в браузере. Приложение будет устанавливать соединение с сервером, отправлять видеопоток с камеры Android-устройства,принимать и воспроизводить другой видеопоток с сервера.

Ниже показано как выглядит Android-приложение Streaming Min (аналог примера Two Way Streaming для видеочата в браузере), которое позволяет обмениваться видеопотоками.

12_video_chat_in_android

Из скриншота видно, что ничего не изменилось. У нас есть два видео окна. Слева отображается видео, захваченное с вебкамеры, а справа видео, которое заходит с сервера. Обмен видеопотоками осуществляется точно также, с использованием имён. Публикуем один поток и проигрываем другой.

Код видеочата для приложения под Android

Если для создания видеочата для браузера мы использовали Web SDK, включающее в себя скрипт API flashphoner.js, то для создания полноценного нативного приложения под Android нужно импортировать aar-файл Android SDK в проект.

Чтобы разобраться как это работает, проще всего собрать и запустить пример Streaming Min на базе Android SDK. Все примеры доступны в репозитории github.

1. Скачиваем все примеры

git clone https://github.com/flashphoner/wcs-android-sdk-samples.git

2. Скачиваем SDK

wget https://flashphoner.com/downloads/builds/flashphoner_client/wcs-android-sdk/aar/wcs-android-sdk-1.0.1.25.aar

3. Подцепляем SDK в виде aar-файла к примерам.

cd export
./export.sh /tmp/wcs-android-sdk-1.0.1.25.aar

Обратите внимание, что мы указали скрипту export.sh путь к скачанному файлу wcs-android-sdk-1.0.1.25.aar — Android SDK

В результате в папке export/output будет полностью сконфигурированный проект, который можно открыть в Android Studio

Осталось только собрать примеры с помощью gradle.

1 — Создаём конфигурацию запуска

13_source_code_android_application

2 — Выбираем Gradle скрипт

14_source_code_of_video_chat

3 — Запускаем сборку

15_video_chat_for_android

В результате сборки мы должны получить apk-файлы, которые уже можно устанавливать на Android устройство.

В этом примере мы обменялись видеопотоками с браузером. Видеопоток test33 был отправлен с Android устройства на сервер и воспроизведен в браузере. Видеопоток 8880 был отправлен браузером и воспроизведен на Android — устройстве. Таким образом мы получили двухстороннюю аудио и видеосвязь между браузером и приложением для Android.

16_video_chat_android_application

Если в Web-версии видеочата мы использовали HTML div-элементы для видео, то в Android мы используем рендереры

private SurfaceViewRenderer localRender;
private SurfaceViewRenderer remoteRender;

17_div_elements_for_video

В localRenderer отображается видео, захваченное с камеры Android-устройства. В remoteRenderer отображается видео, которое пришло с сервера.

1. Создаем подключение к серверу и указываем, что будут использоваться рендереры.

sessionOptions = new SessionOptions(mWcsUrlView.getText().toString());
sessionOptions.setLocalRenderer(localRender);
sessionOptions.setRemoteRenderer(remoteRender);
...
session = Flashphoner.createSession(sessionOptions);
…
session.connect(new Connection());

2. Создаем поток с произвольным именем и публикуем поток на сервер.

StreamOptions streamOptions = new StreamOptions(mPublishStreamView.getText().toString());
publishStream = session.createStream(streamOptions);
...
publishStream.publish();

3. Указываем имя потока при воспроизведении и забираем поток с сервера.

StreamOptions streamOptions = new StreamOptions(mPlayStreamView.getText().toString());
playStream = session.createStream(streamOptions);
...
playStream.play();

Полный код класса StreamingMinActivity.java доступен здесь. А код всего примера Streaming Min для Android доступен в репозитории по этой ссылке.

Web Call Server

В результате мы показали как организовать простой обмен видеопотоками между HTML-страницей в браузере и приложением под Android.

Видеопотоки проходят через Web Call Server, который является одновременно сигналинг-сервером и проксирует через себя аудио и видео трафик.

18_web_call_server

Web Call Server — это серверный софт, который может быть установлен на Linux — системе на виртуальном сервере или физическом (dedicated) сервере. WCS является WebRTC сервером потокового видео и может обслуживать видеопотоки с браузеров, iOS и Android устройств.

Ссылки

Технологии и протоколы

WebRTC — технология WebRTC
SDP — Session description protocol, RFC
Websocket — Websocket протокол, RFC

Сервер и API для разработки видеочата

Web Call Server — WebRTC сервер потокового видео для видеочатов
Download Web Call Server — установка сервера
Web Call Server on EC2 — запуск образа сервера на Amazon EC2
Web SDK — Web SDK для разработки видеочатов с поддержкой WebRTC
Android SDK — Android SDK для разработки видеочатов с поддержкой WebRTC

Рабочие примеры

Web Two Way Streaming — пример обмена видеопотоками для Web
Android Two Way Streaming — пример приложения обмена видеопотоками для Android

Исходный код примеров

Web Two Way Streaming — код примера обмена видеопотоками для Web
Android Two Way Streaming — код примера обмена видеопотоками для Android