Аудио конференции бывают разные, как и задачи, которые они решают: централизованные (на сервере), клиентские, распределенные. В нашем случае мы рассмотрим первые два варианта — централизованные на стороне облака 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) — тут всех желающих просто подключает к одной конференции. Всегда рады ознакомиться с вашими мыслями и комментариями, всем удачного конференсинга!
Автор: Voximplant