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

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 (f === 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 — тут всіх бажаючих просто підключає до однієї конференції. Завжди раді ознайомитися з вашими думками і коментарями, всім вдалого конференсинга!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.