Империи зла нередко получают лучи ненависти со стороны конечных пользователей. Не смотря на это, 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 www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 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 и много других страшных слов.
WebRTC, Websockets и SIP
Достаточно часто путаются понятия относительно WebRTC и Websocket. Иногда к этой путанице добавляется еще и SIP.
Точно можно сказать, что WebRTC не имеет прямого отношения ни к Websockets ни к SIP.
Websockets — это лишь удобная возможность передать SDP от Бориса к Алисе. Вместо Websockets мы могли бы использовать plain HTTP или отправить SDP текст по почте. Обмен SDP сообщениями является сигнальной информацией и эти данные могут быть переданы по какому угодно протоколу. Для браузеров дефолтные протоколы передачи данных это: Websockets и HTTP. Поэтому используется Websockets, как более реалтаймовый вариант по сравнению с HTTP. Через Websockets не передается аудио и видео. Только сигнальная информация: текст и команды.
SIP — это текстовый протокол обмена сообщениями. WebRTC иногда незаслуженно называют SIP-ом в браузере, скорее всего за то, что в SIP сообщениях также используется SDP для конфигурации кодеков и установки соединения.
С другой стороны, когда мы говорим, например SIP-телефон, мы имеем в виду устройство, которое наряду с протоколом SIP (RFC3261) поддерживает еще десяток другой сетевых спецификаций и протоколов: RTP, SDP, AVPF, и т.д.
Действительно, WebRTC на сетевом уровне использует похожие строительные блоки с теми, что использует SIP-телефон (SRTP, STUN, и.т.д.). Поэтому можно сказать что и WebRTC и SIP-устройства / ПО используют схожую базу технологий. Но называть WebRTC, SIP-ом в браузере было бы некорректно, хотя бы потому, что SIP-а в браузерах из-коробки нет.
WebRTC — это технология, которая имеет три основных функции в части передачи аудио / видео:
- Захват, кодирование и отправка
- Прием декодирование и воспроизведение
- Преодоление NAT и Firewall
И много вспомогательных функций, таких как борьба с джиттером, адаптивный битрейт, контроль перегрузок сети, и т.д.
Как было описано выше, чтобы передача медиа по WebRTC состоялась, Алиса и Борис должны обменяться SDP, которые содержат подробную информацию о форматах видеопотоков, пакетизации и другие параметры, определяющие как именно сторона будет принимать видео.
Кроме обмена SDP может потребоваться TURN-сервер, который будет пропускать через себя видеотрафик в том случае если соединение Peer-to-Peer не будет установлено, например если у Алисы или у Бориса выявился достаточно недружелюбный (например симметричный) NAT.
Предположим, мы захотели добавить к чату третьего активного участника или просто зрителя. Хороший пример в данном случае — дебаты. Два участника разговаривают — остальные смотрят. Еще один пример — чат на трех и более участников.
При появлении третьего участника, схема усложняется тем, что каждому из участников приходится захватывать и жать уже по два видеопотока вместо одного, а также попарно устанавливать соединения, пытаясь преодолеть NAT. В этом случае может резко увеличиться время установки соединения и уменьшается его стабильность. Одновременное сжатие и отправка двух и более видеопотоков создает уже серьезную нагрузку на процессор и сеть и влияет на качество в худшую сторону, в особенности на мобильных устройствах:
Подобные задачи, такие как
- подключение трех и более участников
- подключение дополнительных зрителей видеочата
- запись видеочата
выходят за рамки успешной применимости peer-to-peer и требуют центрального WebRTC-сервера, управляющего этими подключениями.
Как было упомянуто выше, существуют сервисы и серверы, удобные и неудобные API поверх WebRTC API, которые позволяют ускорить разработку видеочатов и работать с более удобными абстракциями, например видеопоток (Stream), комната (Room), публикующий (Publisher), зритель (Subscriber), и т.д.
Например, для создания самого простого видеочата было бы достаточно обменяться названиями потоков. Борис знает поток Алисы. Алиса знает поток Бориса — и видеочат готов:
Пример видеочата в браузере
В этой статье мы покажем как работает Streaming API с Web Call Server 5 — WebRTC сервером для видеочатов и онлайн-трансляций.
Видеочат в действии можно проиллюстрировать в двух следующих скриншотах. Первый участник Alice будет видеть видеочат так:
Второй участник Edward будет видеть видеочат так:
В данном примере происходит следующее:
- Alice отправила на сервер видеопоток из браузера с именем Alice.
- Edward отправил на сервер видеопоток из браузера с именем Edward.
- Alice забрала и проиграла видеопоток с именем Edward.
- Edward забрал и проиграл видеопоток с именем Alice.
Как видно из этого примера, мы построили видеочат, основываясь лишь на том, что Alice и Edward знают имена потоков друг друга. Нам не пришлось работать напрямую с SDP, PeerConnection, NAT, TURN, и т.д.
Таким образом, видеочат можно реализовать просто передавая имена потоков тем, кто должен их проиграть.
В этой простой концепции вы можете использовать любые front-end и back-end технологии, такие как Jquery, Bootstrap, React, Angular, PHP, Java, .Net, и т.д. И хорошая новость в том, что встраивание поддержки видеопотоков и видеочата никак не влияет на существующее веб-приложение. Вы управляете вашим видеочатом, просто позволяя (или не позволяя) его участникам играть нужные видеопотоки.
Код видеочата в браузере
Теперь покажем как это выглядит в коде. HTML-страница с видеочатом содержит два основных div-элемента:
- localVideo — видео, которое захватывается с веб-камеры
- remoteVideo — видео, которое воспроизводится с сервера
Можно дать этим элементам любые произвольные идентификаторы, например id=«captureVideo» или id=«playbackVideo», но важно, чтобы эти элементы присутствовали на странице.
Рабочая HTML-страница с блоками localVideo и remoteVideo выглядит так:
<html>
<head>
<script language="javascript" src="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 для видеочата в браузере), которое позволяет обмениваться видеопотоками.
Из скриншота видно, что ничего не изменилось. У нас есть два видео окна. Слева отображается видео, захваченное с вебкамеры, а справа видео, которое заходит с сервера. Обмен видеопотоками осуществляется точно также, с использованием имён. Публикуем один поток и проигрываем другой.
Код видеочата для приложения под 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 — Создаём конфигурацию запуска
2 — Выбираем Gradle скрипт
3 — Запускаем сборку
В результате сборки мы должны получить apk-файлы, которые уже можно устанавливать на Android устройство.
В этом примере мы обменялись видеопотоками с браузером. Видеопоток test33 был отправлен с Android устройства на сервер и воспроизведен в браузере. Видеопоток 8880 был отправлен браузером и воспроизведен на Android — устройстве. Таким образом мы получили двухстороннюю аудио и видеосвязь между браузером и приложением для Android.
Если в Web-версии видеочата мы использовали HTML div-элементы для видео, то в Android мы используем рендереры
private SurfaceViewRenderer localRender;
private SurfaceViewRenderer remoteRender;
В 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, который является одновременно сигналинг-сервером и проксирует через себя аудио и видео трафик.
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