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

в 10:30, , рубрики: conferencing, hd аудио, p2p, voximplant, Web Audio, WebRTC, Блог компании Voximplant, Веб-разработка, Программирование, Работа со звуком, разработка

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

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

Из названия следует, что микширование аудио потоков происходит на стороне сервера. Для каждого участника конференции создается свой микс, в котором есть все участники кроме него самого (вы же не хотите слушать свое эхо). К тому же, у конференций есть еще ряд параметров, которые влияют на качество звука. Например, частота дискретизации, на которой она работает. В случае VoxImplant у нас есть 2 варианта — обычные и HD. В обычных частота 8KHz и они лучше всего подходят для объединения звонков из телефонной сети, там выше 8KHz все равно не получится. В случае HD мы пошли по пути создания максимального качества, и поэтому в данном случае миксуем уже на 48KHz (максимум для WebRTC в браузере). Так как используются серверные ресурсы, то сделать такие конференции бесплатными сложно, железо и трафик пока еще что-то стоят :)
Аудио конференции для бедных и для богатых - 2
Во время создания серверных конференций пришлось использовать всякие разные инновационные технологии, которые хорошо давят шум (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, хостом выступает создающий конференцию пользователь, соответственно на его компьютере все будут микшироваться. Если интернет или железо у этого товарища окажется не очень, то все будут страдать, но зато это бесплатно! :)
Аудио конференции для бедных и для богатых - 3
После недавних значительных обновлений 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) — тут всех желающих просто подключает к одной конференции. Всегда рады ознакомиться с вашими мыслями и комментариями, всем удачного конференсинга!

Автор: Voximplant

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js