Аудио конференции для бедных и для богатых

от автора

image
Аудио конференции бывают разные, как и задачи, которые они решают: централизованные (на сервере), клиентские, распределенные. В нашем случае мы рассмотрим первые два варианта — централизованные на стороне облака VoxImplant и клиентские, сделанные прямо в браузере с использованием WebAudio и WebRTC (да-да, и такое уже стало возможно!). У обоих вариантов есть свои плюсы и минусы, которые мы рассмотрим подробнее под катом, а также расскажем о том как их использовать и о подводных камнях (куда же без них!).

Серверные конференции

Из названия следует, что микширование аудио потоков происходит на стороне сервера. Для каждого участника конференции создается свой микс, в котором есть все участники кроме него самого (вы же не хотите слушать свое эхо). К тому же, у конференций есть еще ряд параметров, которые влияют на качество звука. Например, частота дискретизации, на которой она работает. В случае VoxImplant у нас есть 2 варианта — обычные и HD. В обычных частота 8KHz и они лучше всего подходят для объединения звонков из телефонной сети, там выше 8KHz все равно не получится. В случае HD мы пошли по пути создания максимального качества, и поэтому в данном случае миксуем уже на 48KHz (максимум для WebRTC в браузере). Так как используются серверные ресурсы, то сделать такие конференции бесплатными сложно, железо и трафик пока еще что-то стоят 🙂

Во время создания серверных конференций пришлось использовать всякие разные инновационные технологии, которые хорошо давят шум (NR), эффективно определяют говорящих (VAD) и так далее, все это самым прямым образом влияет как на качество звука, так и на масштабируемость: кодирование и декодирование потоков никто не отменял (микширование и ресэмплинг — не самые сложные задачи). Мы в первую очередь ориентируемся на WebRTC, поэтому основной ходовой кодек у нас Opus, но подключиться можно и из SIP с любым из следующих на выбор: G.711, Speex (и Opus).

Конференция на стороне VoxImplant создается следующим образом (сценарий VoxEngine):

// Подключаем модуль конференций require(Modules.Conference);  var conf = null,     calls = [];  // При старте сессии создаем конференцию VoxEngine.addEventListener(AppEvents.Started, function(e) {   if (conf === null) {     // hd_audio определяет будет HD или не-HD конференция     conf = VoxEngine.createConference({ hd_audio: true });   } });  // Входящие звонки подключаем к конференции VoxEngine.addEventListener(AppEvents.CallAlerting, function(e) {   e.call.addEventListener(CallEvents.Connected, handleCallConnected);   e.call.addEventListener(CallEvents.Disconnected, handleCallDisconnected);   e.call.answer(); });  // Соедияем аудио поток конференции и звонка function handleCallConnected(e) {   VoxEngine.sendMediaBetween(conf, e.call);   calls.push(e.call); }  // Если все звонки отключатся, то можно убить сессию function handleCallDisconnected(e) {   var index = calls.indexOf(e.call);   if (index > -1) calls.splice(index, 1);   if (calls.length === 0) VoxEngine.terminate(); } 

Звонки туда направляются с помощью функции callConference, поэтому придется сделать отдельный сценарий, который форвадит звонки в конференцию из разных источников (PSTN, WebSDK, MobileSDK или SIP) и прописать соответствующее правило (Pattern) приложения. Более подробно про работу с конференциями в VoxImplant можно прочитать по данной ссылке.

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

Poor man’s conferencing: конференции на клиенте

Все мы знакомы со Skype и его прекрасной возможностью аудио-конференций. Это тот самый client-side conferencing, хостом выступает создающий конференцию пользователь, соответственно на его компьютере все будут микшироваться. Если интернет или железо у этого товарища окажется не очень, то все будут страдать, но зато это бесплатно! 🙂

После недавних значительных обновлений WebRTC и Web Audio в Chrome и Firefox стало возможно такой же сценарий реализовать прямо на уровне браузера. Я был очень воодушевлен, когда приступил к реализации этой идеи. Но мой пыл несколько поугас после того, как пришлось изрядно повозиться, чтобы это все завелось без лишних эффектов и независимо от браузеров участников (WebRTC пока есть в Chrome/Chromium и Firefox). Начнем с теории…

RTCPeerConnection

Этот прекрасный класс (далее по тексту будем называть его PC) от WebRTC дает нам возможность передавать звук (и видео, но в этот раз без него) в реальном времени, подключая к нему стрим (local stream) от микрофона, через сеть кому-то на другом конце и оттуда получать чужой стрим (remote stream). Изначально в WebRTC все крутилось около MediaStream’ов (тот самый локальный стрим от микрофона это объект данного класса), но сейчас стандарт немного эволюционировал и все сдвинулось в сторону Audio/VideoTracks (для более лучших видео конференций, но про это в другой раз). Что не отменяет работы с классом MediaStream, когда мы переходим в плоскость Web Audio. Мы не будем рассматривать как сделать P2P звонок с помощью WebRTC, про это есть много других статей + на VoxImplant это делается совсем уж просто. Итак, что же мы должны сделать, чтобы смиксовать звук из разных PC и своего микрофона? Начнем с простого:

// предположим, что у нас есть MediaStream от микрофона - такой код будет воспроизводить наш локальный поток средствами Web Audio function gotStream(stream) {     // А вот и Web Audio - создаем контекст     window.AudioContext = window.AudioContext || window.webkitAudioContext;     var audioContext = new AudioContext();     // Web Audio работает с так называемыми audio nodes , которые можно стыковать разными способами     var mediaStreamSource = audioContext.createMediaStreamSource( stream );     // Отправляем поток на проигрывание аудио устройству     mediaStreamSource.connect( audioContext.destination ); } navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia; navigator.getUserMedia( {audio:true}, gotStream,function(){}); 

Для того, чтобы объединять разные потоки нам потребуется ChannelMergerNode, это и есть наш микшер, нам таких потребуется столько сколько у нас участников в конференции и каждый участник будет получать микс остальных за исключением себя, выглядит оно как-то так:

    window.AudioContext = window.AudioContext || window.webkitAudioContext;     var audioContext = new AudioContext();     var mediaStreamSource = audioContext.createMediaStreamSource( local_stream ), participant1 =  audioContext.createMediaStreamSource( participant1_stream ), participantN = audioContext.createMediaStreamSource( participantN_stream );     // Микшер     var merger = audioContext.createChannelMerger();     // Отправляем поток в MediaStream, который надо подключить к PC     var destination_participant1 = audioContext.createMediaStreamDestination();     mediaStreamSource.connect(merger, 0, 0); // Отправляем локальный стрим в микшер     participantN.connect(merger, 0, 0); // добавляем в микс всех участников кроме того для кого этот микс и сделан     mediaStreamSource.connect(merger, 0, 1); // необходимо для стерео в FF     participantN.connect(merger, 0, 0);  // необходимо для стерео в FF     // Микс отправляем в destination_participant1     merger.connect( destination_participant1 );     // Добавляем в PC для participant1 результирующий поток , скорее всего, предыдущий надо будет отключить через removeStream     pc.addStream( destination_participant1.stream ); 

Ничего гениального, но поверьте, что разработчикам браузеров пришлось изрядно повозиться, чтобы это работало. Вам не показалось, что все как-то слишком просто? 🙂 Вот и мне так казалось, пока дело не дошло до тестирования. Проверка отправки микса с Chrome на Firefox выявила, что проигрывается только 1 из всех медиа-потоков, отправленных в микс, при том что в случаях Chrome->Chrome, Firefox->Chrome, Firefox->Firefox все работает нормально. Попытка осмыслить причину такого поведения пока не привела к успеху, мы написали об этом коллегам в Google и Mozilla, но на момент написания текущей статьи ответа еще не получили. Как только появится понимание проблемы или способ решения проблемы, то мы обязательно напишем об этом в P.S.

Демки

Напоследок предлагаем вам ознакомиться с демками, быстро собранными нами на VoxImplant: первая использует клиентский подход (+github) — в ней нужно выбрать кому звоним для подключения к клиентской конференции, а вторая использует серверные конференции (+github) — тут всех желающих просто подключает к одной конференции. Всегда рады ознакомиться с вашими мыслями и комментариями, всем удачного конференсинга!

ссылка на оригинал статьи https://habrahabr.ru/post/279313/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *