Империи зла нередко получают лучи ненависти со стороны конечных пользователей. Не смотря на это, 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 и много других страшных слов.
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="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 для видеочата в браузере), которое позволяет обмениваться видеопотоками.
Из скриншота видно, что ничего не изменилось. У нас есть два видео окна. Слева отображается видео, захваченное с вебкамеры, а справа видео, которое заходит с сервера. Обмен видеопотоками осуществляется точно также, с использованием имён. Публикуем один поток и проигрываем другой.
Код видеочата для приложения под 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