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


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


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

    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 — видео, которое воспроизводится с сервера



    Можно дать этим элементам любые произвольные идентификаторы, например 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
    • +16
    • 16,3k
    • 8
    Flashphoner
    94,13
    Компания
    Поделиться публикацией

    Комментарии 8

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

      За статью все равно спасибо, подробно расписали.
        +2
        С тем же сервером, да для iOS — реально?

        Да. iOS просто не влез в статью. В следующей попробуем расписать видеочат на троих: Android, iOS и Web.
          0
          Было бы круто! Ждем!
          0
          Android куда более всеяден относительно видео, нежели iOS.

          т.е.?
        +1

        И как правило Web call server вашей фирмы платный.
        А если бесплатно, то функционал очень урезанный,


        немного устал от такой макароны, начинается с туториала, заканчивается тем что купите у нас сервер конференций!

          +2
          Теоретически, в качестве сервера можно использовать freeSWITCH
          В качестве js клиентов: SipML5, sip-js, jsSIP, SIPjs
          под андроид и IOS тоже хватает библиотек

            0
            Посоветуйте пожалуйста чем лучше воспользоватся при работе с freeSWITCH на андроиде. Я использую freeSWITCH + jssip в веб видео чатах и сейчас подыскиваю информацию как связать веб видео чат с андроид приложением.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое