Разрабатываем видеочат между браузером и мобильным приложением

18b79fc9a8944cf8a505ba68bda5a309.png

Империи зла нередко получают лучи ненависти со стороны конечных пользователей. Не смотря на это, 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 и много других страшных слов.

5aa3a3be9d63468d8a1ec9866d9da4c2.jpg

WebRTC, Websockets и SIP


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

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

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

8305ef9463254abbb3168a97a3d01449.jpg

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

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

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

cf1d4a6a52ed423387361d8ae7a6365a.jpg

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

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

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

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

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

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

b15896e0215c49c0b8ceefa3f4d9bf1f.jpg

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

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

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

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

f41ded169e5a49d1a0053d083bbfa90a.jpg

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


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

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

a3f8631039e64220aa9c46ef7c397c8a.jpg

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

В данном примере происходит следующее:
  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 — видео, которое воспроизводится с сервера

b5113598f9db4ea49d9a8211da94ecf0.jpg

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

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



    
    


Video Chat


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

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


При отправке мы используем метод 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 для видеочата в браузере), которое позволяет обмениваться видеопотоками.

170cbcdd08df4c4ca36f89ce02973b8f.jpg

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

Код видеочата для приложения под 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 — Создаём конфигурацию запуска

383e1af9cb5541a3a5d7728edec4a62f.png

2 — Выбираем Gradle скрипт
7967ee27f2b0438ea42944ae6e58be06.jpg

3 — Запускаем сборку
7d66af2987d34ca088db27ad9c149e63.jpg

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

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

170cbcdd08df4c4ca36f89ce02973b8f.jpg

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

13f9749e1c844105aad58cbab3fddb9e.jpg

В 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, который является одновременно сигналинг-сервером и проксирует через себя аудио и видео трафик.

3a4d68cd25a2489bab9689b820f92805.jpg

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

Комментарии (3)

  • 27 марта 2017 в 18:31

    0

    Android куда более всеяден относительно видео, нежели iOS.
    С тем же сервером, да для iOS — реально?

    За статью все равно спасибо, подробно расписали.

    • 27 марта 2017 в 18:59

      +2

      С тем же сервером, да для iOS — реально?

      Да. iOS просто не влез в статью. В следующей попробуем расписать видеочат на троих: Android, iOS и Web.
      • 28 марта 2017 в 06:59

        0

        Было бы круто! Ждем!

© Habrahabr.ru