Доброго времени суток, хабравчане.
Сегодня я расскажу вам о работе sip-телефонии, а именно о том, как я организовывал звуковой сеанс между мобильными рациями (или ИКН) о которых вы слышали ранее из других статей нашей компании и web-клиентом через webRTC с использованием sipML5 в качестве библиотеки и asterisk 11 в качестве АТС.
image
Всем кому небезразлична данная тема — добро пожаловать под кат.

Немного предыстории


К нам в отдел поступило оперативное задание разработать sip-клиент, который работал бы на asterisk, и как минимум на google chrome (как мобильной, так и десктопной его версии). С этого и закрутилось.

В системе локального позиционирования RealTrac существуют мобильные устройства типа рация, или, как мы их называем, ИКН (интерком носимый), который обеспечивает использование как дуплексной связи, так и широковещательной полудуплексной связи с другими устройствами. Эти устройства взаимодействуют с коммуникационным сервером INCPd, основная задача которого – обработка пакетов протокола INCP, посредством которого обеспечивается обмен информацией с устройствами системы RealTrac. В том числе, данный сервер обрабатывает поступающий голосовой трафик, и перепаковывает его в sip-пакеты, для дальнейшей работы со сторонним софтом.
В качестве коммутатора выступает asterisk.

image

Инструментарий


SipML5 был выбран нами неслучайно. Во-первых, данная библиотека хорошо подходит для интеграции с asterisk. Во-вторых, мы уже имели опыт работы с подобными системами, однако, из-за того, что наша компания поддерживала Debian 7 в качестве ОС для нашего программного обеспечения имелись определенные сложности. В дистрибутиве Debian 7 доступен только asterisk 1.8 в то время как webRTC без танцев с бубном, в виде webrtc2sip, работает только с 11 версии asterisk, что нас несильно устраивало, так как это могло бы стать полигоном проблем для будущих инсталляций.
С релизом Debian 8 было решено продолжить движение в направлении данного функционала.
В качестве инструментария создания ui-части выступал React.js, так как он удобен для отображения различных динамических объектов, имеющих большое количество внутренних состояний.

Небольшое введение по sipML5


В основе клиента используется две основные сущности, это SIPml.Stack и SIPml.Session.

SIPml.Stack является управляющим потоком данных. Данный объект будет выступать в качестве основного при создании сессий с сервером.

SIPml.Session является потоком sip-сеанса с сервером. Используется непосредственно для передачи данных между сервером и клиентской частью.

Алгоритм работы с sipML5:

Инициализация библиотеки:

SIPml.init(readyCallback, errorCallback);


Создание Stack

Stack является ключевым источником данных для работы клиента.
RtlsSip.stack = new SIPml.Stack({
   realm: Data.property.realm,
   impi: Data.property.impi,
   impu: Data.property.impu,
   password: Data.property.password,
   display_name: Data.property.display_name,
   websocket_proxy_url: Data.property.websocket_proxy_url,
   events_listener: { events: '*', listener: eventsListener },
   sip_headers: [
       { name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.0.0' },
       { name: 'Organization', value: 'RTLS' }
   ]
})


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

После создания stack-объекта необходимо его запустить:

RtlsSip.stack.start();


Важно отметить, что функция start() является асинхронной, поэтому, перед тем как начать совершать звонки, необходимо дождаться события запуска stack.
Полный перечень событий представлен тут много, и перечислять их все не имеет смысла.

После события создания stack необходимо создать сессию регистрации:

RtlsSip.stack.newSession('register', {
   events_listener: { events: '*', listener: registerListeners} });
RtlsSip.sessions.register();


В ходе этой операции происходит sip запрос для входа пользователя в систему.

После выполнения данных действий мы готовы совершать или принимать звонки от пользователей.

Прием звонков

Входящий звонок в sipML5 инициализирует событие ”i_new_call” объекта SIPml.Stack.
В общем случае обработчик ответа на звонок выглядит следующим образом:

var eventCallback = function(е){
RtlsSip.callSession =  e.newSession;
RtlsSip.callSession.events_listener({events: '*', listener:      RtlsSip.sessionEventListener})
RtlsSip.callSession.accept(
{audio_remote: document.getElementById('audio_remote'),
   	 events_listener: { events: '*', listener:  RtlsSip.sessionEventListener}})

}


e.newSession — содержит в себе дополнительный объект сеанса связи, для события “i_new_call” это будет объект SIPml.Session.Call.

Функция accept() необходима для принятия звонка. В качестве параметра принимает объект SIPml.Session.Configuration

Инициализация звонков

Инициализация звонка сводится к созданию новой сессии типа SIPml.Session.Call

RtlsSip.callSession = RtlsSip.stack.newSession('call-audio', {
   audio_remote: document.querySelector('#audio_remote'),
   events_listener: { events: '*', listener: RtlsSip.sessionEventListener }
});


После инициализации можно приступать к вызову абонента, через его идентификатор, номер или url (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000').:

RtlsSip.callSession.call(number);


Управление звонком
Управление звонком это методы класса SIPml.Session.Call

Основные из них:
? .hangup() — завершение звонка
? .hold() / .resume() — удержание/возобновление звонка
? .mute(media, mute) — отключение входящего звука, где media — тип контента для отключения; mute — boolean — активация/деактивация mute.
RtlsSip.callSession.mute('audio', true);

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

Схема приложения:


Схема приложения имеет достаточно простую архитектуру. В качестве идеологии однонаправленность данных и отсутствия взаимных зависимостей между элементами.

image

Интеграция в систему


Настройки для asterisk сервера я здесь описывать не буду, так как их несложно найти на просторах сети.
Для абонентов типа web-client был выделен собственный список номеров. Номера из пула устанавливаются диспетчерам при создании аккаунта пользователя.
Диспетчер может совершать звонки как в полудуплексном, так и в дуплексном режиме, а также связываться с другими диспетчерами в случае необходимости.
Устройство по умолчанию звонит в полудуплексном режиме. Диспетчер, которому звонит устройство выбирается в зависимости от геосегмента в котором находится устройство в текущий момент времени.
Все это позволяет добиться максимального комфорта в использовании системы.
Итоги:
На данный момент, эта технология проходит у нас процесс интеграции и тестирования. Уверен, что в процессе работы найдется немало помех и проблем.
Вполне возможно, что в будущем мы попробуем решение на основании других технологий, предоставляющих тот же функционал.

by Sinires
Поделиться с друзьями
-->

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


  1. Emily_Rose
    09.06.2016 15:42
    +1

    Я вот чесно не понимаю, почему люди выбираю sipML5, а не sip.js? и да, какая версия 11 астериска в дебиан 8 репозитории?


    1. sinires
      09.06.2016 16:15

      Я вот чесно не понимаю, почему люди выбираю sipML5, а не sip.js?

      Философский вопрос =)
      какая версия 11 астериска в дебиан 8 репозитории?

      # apt-cache madison asterisk
      asterisk | 1:11.13.1~dfsg-2+b1 | http://ftp.debian.org/debian/ jessie/main amd64 Packages
      asterisk | 1:11.13.1~dfsg-2+b1 | http://httpredir.debian.org/debian/ jessie/main amd64 Packages
      asterisk | 1:11.13.1~dfsg-2+b1 | http://ftp.ru.debian.org/debian/ jessie/main amd64 Packages
      asterisk | 1:11.13.1~dfsg-2 | http://ftp.debian.org/debian/ jessie/main Sources
      asterisk | 1:11.13.1~dfsg-2 | http://ftp.ru.debian.org/debian/ jessie/main Sources


      1. Ovoshlook
        09.06.2016 18:15

        Не филосовский
        sipML5 как библиотека довольно криво реализует SIP стэк клиента
        Но ради справеливости стоит сказать что sip.js это всего лишь форк jssip.
        А вот jssip очень удачный клиент и со стороны sip и со стороны websocket


        1. sinires
          09.06.2016 18:20

          А вот jssip очень удачный клиент и со стороны sip и со стороны websocket

          Спасибо, глянем повнимательнее в его сторону =)


  1. Emily_Rose
    09.06.2016 16:45

    11.13 это очень давно.
    https://issues.asterisk.org/jira/browse/ASTERISK-24146 Эту лично я чинил, тестируйте, должно ломаться на 11.13, почти все на это уже продакшине натыкаються.
    https://issues.asterisk.org/jira/browse/ASTERISK-25265 Это входящие в фоксе лечит.

    Но это кончно проблемы в chan_sip, а на pjsip а в 11 только chan_sip.


    1. sinires
      09.06.2016 17:41

      Действительно давно, но debian не шибко стремится обновлять пакеты. В случае необходимости всегда можно собрать из исходников, благо манов полно.
      Большое спасибо за подводные камни =)


  1. Normic
    09.06.2016 18:54

    На практике большие ли задержки такой связи?


    1. sinires
      10.06.2016 08:38

      Нет, 100-200 мс. Не ощущается и разговор весьма комфортный.
      Есть небольшие задержки в установке связи с мобильными рациями, но это специфичная часть, не связанная с этой частью системы.


  1. EminH
    10.06.2016 11:22

    Заранее извиняюсь за простой вопрос, объясните а Html5 клиент должен где то логиниться?
    Можно ли сделать просто звонилку с браузера на Sip (или может есть уже такой виджет)?
    я не хочу ничего интегрировать, просто хочу кнопку чтобы посетитель сайта мог кликнуть и позвонить мне на sip:user@myvoipserver.tld


    1. Emily_Rose
      10.06.2016 11:45

      Можно. «клиент должен где то логиниться?» это в телефонии называется регистрация, делается это для того чтоб сервер знал какой юзер за какими апйи: портом и т.д. для входящих звонков к этому юзеру. Вы же собираетесь делать клик ту колл, там просто нужно достаточно посылать инвайт. У Voximplant вроде как что то есть, zingaya… погуглите в эту сторону, уже не помню.


      1. EminH
        10.06.2016 11:53

        zingaya
        они просят регистрироваться и платить поминутно, то есть там явно привязка к серверам.
        Voximplant посмотрю


    1. sinires
      10.06.2016 12:30

      Имхо, если sip-сервер сконфигурирован, то проблем быть не должно.
      Наверно (не уверен) можно настроить пул гостевых пользователей, а даже если нельзя всегда можно вшить регистрацию «внутри» логики, и регистрировать пользователя в момент нажатия кнопки/перехода на страницу.


      1. Emily_Rose
        10.06.2016 12:33

        Зачем их регистрировать?


        1. sinires
          10.06.2016 12:40

          Имелось ввиду подключение к sip-серверу


          1. Mendel
            10.06.2016 13:34
            +1

            Насколько я понял изначальный вопрос, то задача в том, чтобы зарегистрироваться у любого провайдера у которого «звонки с сипа на сип бесплатно» (большинство вроде как) и который принимает звонки и извне, и разместить на сайте клиента который без всяких логино-паролей (которые не хочется светить в паблик если они не от гостевого пользователя на своем сервере), без поднятий астерисков и т.п.

            Я вот если честно не уверен что это возможно, недостаточно в теме, но вроде как все хтмл2сип вариации используют относительно нестандартные транспорты типа того же вебсокета, и могут не поддерживаться софтом провайдера. (могут и поддерживаться, но кто ж знает как повезет?)