Pull to refresh

Интернет-радио с множеством ведущих из разных городов и звонками в прямом эфире

Reading time 11 min
Views 16K
С 1 по 4 мая 2014 года в Воронеже уже в пятнадцатый раз пройдёт ежегодный всероссийский фестиваль японской анимации. Фестиваль стал для нас традицией, посетители съезжаются со многих городов России.

У участников и посетителей возникает множество вопросов и идей относительно фестиваля. И хотя у них есть много возможностей задать эти вопросы нам и получить ответы, организаторы придумали способ дать им ещё одну возможность — организовать интернет-радио и возможность позвонить в прямой эфир и задать вопрос там.

Однако, это осложнено тем, что сам оргкомитет фестиваля географически весьма распределён. Мы находимся в разных городах, в числе которых Кобе (Япония), Москва, Ростов-на-Дону, Вальдкирх (Германия), Краснодар, и конечно же, Воронеж, и это весьма долго и дорого — собраться физически в одном месте. (Достаточно того, что на сам фестиваль все съезжаются.) А нужно ещё организовать входящие звонки в эфир, и желательно тоже бесплатно. При этом хотелось бы максимально простых и безопасных инструкций, например, использования уже имеющегося ПО на компьютерах аудитории.

Организаторы достаточно успешно делают фестиваль, используя голосовые конференции в Skype. Естественной идеей было собраться в конференцию и как-то завернуть её в радио. А для приёма звонков — запустить на компьютере второй Skype, с другой учётной записью, и в нужный момент после приёма вызова заворачивать его на радио и в конференцию (и также конференцию в него).

Всё нижесказанное относится к Linux. Я сознательно не привожу точных названий пакетов, поскольку в разных дистрибутивах они могут различаться. Также должен сразу предупредить, что я не работаю с Windows уже много лет, и не имею ни малейшего понятия, как то же самое сделать в ней.

Примечание по поводу скриншотов: они были сделаны не в самый последний момент. Они отражают суть, но не точные настройки, которые использовались. Если что-то отличается, верить следует тексту, а не картинкам.


Программное обеспечение


  • JACK (рекурсивный акроним «JACK Audio Connection Kit») — синхронный звуковой сервер с нулевой задержкой. Он управлялся с помощью qjackctl
  • Skype, два инстанса
  • Виртуальные звуковые карты сделаны через модуль ALSA snd-aloop
  • Программы alsa_in и alsa_out, идущие вместе с JACK
  • Центр коммутации — jack_mixer
  • Гейт и компрессор для микрофона, а также выходной лимитер перед эфиром — из пакета Calf Studio Gear
  • Icecast — сервер трансляции
  • Фидер из JACK в Icecast — Darkice, и фронтэнд к нему — Darksnow
  • Плейер — Audacious


Коммутация и конфигурация


Итак, в центре системы находится сервер JACK, который запускается на моей внешней звуковой карте. В эту карту подключен микрофон и наушники. Никаких особых тонкостей здесь нет.

Skype

Skype поддерживает ALSA и Pulseaudio (чтоб оно сдохло), и не поддерживает JACK. Чтобы завернуть Skype в JACK, приходится строить костыли с помощью модуля виртуальной звуковой карты ALSA — snd_aloop.

Если просто загрузить этот модуль, он создаст одну виртуальную звуковую карту Loopback с двумя устройствами (pcm0 и pcm1), каждое — с восемью потоками (sub0..sub8). Если выводить звук в первый поток первого устройства (pcm0p/sub0), этот звук можно записать с первого потока второго устройства (pcm1c/sub0). При этом, формат данных задаётся первым из приложений, открывших устройство: если, скажем, вы начали захват на pcm1c/sub0 в режиме 44100 Гц signed 32bit little endian, то вы обязаны воспроизводить на pcm0p/sub0 именно в таком же формате. Модуль не умеет ничего конвертировать, он просто прикидывается звуковой картой и тасует данные туда-сюда.

Skype в линуксе в режиме ALSA — довольно капризное приложение. Он открывает звуковую карту непременно в режиме 16 бит 48 кГц, моно на захват, и 16 бит, 48 кГц, стерео — на воспроизведение; если карта откажется работать в таком режиме, Skype или сообщит о том, что невозможно инициализировать звуковое устройство, или звук (обычно, захваченный) будет жёстко искажён.

Нам нужно два Skype. Для удобства, хотелось бы, чтобы каждый из них работал со своей картой — тогда проще будет наладить коммутацию. Если почитать modinfo snd_aloop, можно узнать, что модуль принимает несколько очень полезных параметров, среди которых — индексные номера (да, их можно сделать несколько), названия виртуальных карт и количество потоков в каждой. То есть, загружаем модуль таким образом:

modprobe snd-aloop enable=1,1 index=2,3 id=Chat,Incoming pcm_substreams=1,1

При этом в системе создастся две виртуальных карты, под номерами 2 и 3, с именами соответственно Chat и Incoming, каждая из которых будет содержать по одному потоку. Посмотреть свойства этих карт можно в /proc/asound/card2 и .../card3. (Такие индексные номера на моей системе были продиктованы тем, что у меня уже есть встроенная карта с индексом 0 и USB-карта с индексом 1.)

Первый Skype запускается как обычно. Для запуска второго нужна особая командная строка:

screen skype --dbpath=~/.Skype.vrnfest --secondary

Я запускал его в screen, чтобы отвязать от терминала. Здесь dbpath — путь к профилю Skypeа (по умолчанию это ~/.Skype), а --secondary — чтобы он запустился ещё раз.

Каждый Skype очевидным образом настраивается для работы со своей виртуальной звуковой картой, с устройствами 0. Здесь есть тонкий момент, связанный с тем, как Skype работает с ALSA. Для воспроизведения мелодии вызова и для самого пришедшего из сети звука он может использовать разные устройства. Если в настройках указать для «звонка» и «колонок» одно и то же, он откроет устройство два раза, попадая на разные потоки. Аппаратная карта обычно микширует их, но snd-aloop — нет; кроме того, я ограничил виртуальные карты одним потоком, и в этом случае при нажатии кнопки «принять вызов» произойдёт сброс с комментарием «не удалось инициализировать звуковое устройство». Поэтому в обоих Skype я указал для звонка физическую встроенную звуковую карту.

Теперь, после того, как Skype настроен, требуется завернуть вторые концы виртуальных карт в JACK, для чего используются программы alsa_in и alsa_out. Поскольку Skype капризен, он должен первым занять звуковые устройства, чтобы настроить их под себя. Поэтому, звоним с одного скайпа на другой, и принимаем звонок. Пока вызов висит, можно запускать связки:

screen -dmS chat_in alsa_in -d hw:2,1 -j chat_in
screen -dmS chat_out alsa_out -d hw:2,1 -r 48000 -q 1 -c 1 -j chat_out
screen -dmS incoming_in alsa_in -d hw:3,1 -j incoming_in
screen -dmS incoming_out alsa_out -d hw:3,1 -r 48000 -q 1 -c 1 -j incoming_out

Как видно, здесь тоже указаны имена клиентов JACK — чтобы не путать, какой из alsa_in и alsa_out с чем связан. Используем устройства 1 виртуальных карт.

Итак, теперь скайпы завёрнуты в JACK, причём:
  • из chat_in:capture_1 берём звук из конференции организаторов (есть ещё capture_2, но он не будет использоваться)
  • в chat_out:playback_1 отправляем звук в конференцию организаторов
  • из incoming_in:capture_1 берём звук входящего вызова
  • в incoming_out:playback_1 отправляем звук, предназначающийся позвонившему


Проигрыватель, микрофон, наушники (монитор) и трансляция

С наушниками всё просто — они видны как два канала, system:playback_1 и system:playback_2. Микрофон физически подключен к первому входу карты, на которой запущен JACK, поэтому он появляется как system:capture_1. Однако, нельзя просто так взять и использовать микрофон. Во-первых, хочется гейтом отсечь лишние звуки, а во-вторых — скомпрессировать оставшееся, чтобы быть похожим по динамике на сигнал, приходящий из Skype (сам Skype довольно сильно сжимает динамический диапазон). Для этого запускаем calfjackhost и добавляем в него плагины Gate и Compressor. Для моего случая я подобрал такие настройки:
  • Гейт: threshold -36 dB, ratio 3, knee 9 dB, attack 20 ms, release 450 ms, max reduction -inf dB
  • Компрессор: gain 10 dB, threshold -18 dB, ratio 3.5, attack 20 ms, release 250 ms, knee 9 dB


Трансляция пойдёт в icecast с помощью darkice, который настраивается через darksnow. Ничего там особого нет, кроме выходного лимитера перед darkice, который должен выровнять сигнал по уровню. Это плагин «Limiter» в calfjackhost, с такой настройкой:
  • Лимитер: input gain: 12 dB, lookahead: 10 ms, limit: -1 dB, release: 300 ms



Проигрыватель появится в JACK при запуске первого трека и так и останется. Он сам подключится к физическому выходу (system:playback), нужно его отключить.

jack_mixer и коммутация

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

Запускаем его и добавляем через меню:
  • Три входящих моноканала: mic, chat_in, incoming_in
  • Входящий стереоканал: player
  • Четыре исходящих канала (они всегда стерео): monitor, chat_out, incoming_out, radio


Это приведёт к тому, что jack_mixer объявит в JACK кучу входов и выходов:
  • входы: jack_mixer:chat_in, ...:incoming_in, mic, player_L, player_R
  • выходы: jack_mixer:chat_in Out, ...:chat_out R, chat_out L, incoming_in Out, incoming_out L, incoming_out R, MAIN L, MAIN R, mic Out, Monitor L, Monitor R, monitor L, monitor R, player Out L, player Out R, radio L, radio R


Настройку можно сохранить в файл для повторного использования.

Видно, что для каждого входа есть выход с таким же названием и постфиксом " Out". Это тот же самый сигнал, что и на входе, но после фейдера. Все стереоканалы имеют выходы L и R. Есть два специальных стереовыхода — MAIN и Monitor (с большой буквы). MAIN — обычный стереовыход, а в Monitor попадёт копия того выхода, под которым в интерфейсе нажата кнопка Mon. Эти два выхода я не использовал: функционал монитора мне не был нужен, а MAIN задействовал бы, если бы можно было его переименовать в что-то более конкретное.

Также в каждом канале появляется по кнопке Mute, соответствующей каждому выходу. Из этих кнопок Mute набирается матрица коммутации микшера. Это и есть главный функционал, ради которого используется программа.

В общем, необходимо всё соединить («скоммутировать»):
  • system:capture_1 -> calf:gate_in_l, calf:gate_out_l -> calf:compressor_in_l, calf:compressor_out_l -> jack_mixer:mic
  • chat_in:capture_1 -> jack_mixer:chat_in, jack_mixer:chat_out L -> chat_out:playback_1
  • incoming_in:capture_1 -> jack_mixer:incoming_in, jack_mixer:incoming_out L -> incoming_out:playback_1
  • jack_mixer:radio L -> calf:limiter_in_l, calf_limiter_out_l -> darkice:left, и то же самое для правого канала
  • jack_mixer:monitor L -> system:playback_1, и то же самое для правого канала
  • audacious_jack:out_0 -> jack_mixer:player_L, и то же самое для правого канала


Работа радиомоста

Итак, собираем конференцию в одном из скайпов, убеждаемся, что никто не хрипит, не даёт обратную связь и так далее. Все предупреждают окружающих о том, что идёт эфир и лучше бы они не издавали посторонних звуков.

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

Участникам конференции, а также звонящим в эфир нельзя слушать само вещание. Во-первых, оно будет их сбивать с толку, так как идёт с задержкой на всякие буферизации, и составляет 10-30 секунд; кроме того, крайне желательно использовать наушники, чтобы гарантировать отсутствие обратной связи (радио->микрофон).

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

В конце концов я пришёл к следующей модели. У нас система может находиться в одном из трёх публичных базовых состояний: разговоры ведущих, разговор с дозвонившимся слушателем и воспроизведение музыки. На самом деле, в процессе разговора ведущих или музыки может происходить приём входящего звонка, в процессе которого дозвонившемуся объясняют, что можно и что нельзя, добиваются чтобы он тоже не создавал обратную связь. После этого объявляют ведущим (текстом или голосом), что есть дозвонившийся, и когда необходимо, коммутируют его.

Эти состояния — фактически состояния матрицы коммутации. Сразу видно, что некоторые кнопки матрицы будут сразу приведены в определённое состояние и не будут меняться никогда; другие будут включаться и выключаться. Постоянно будут включены:
  • mic->monitor — чтобы слышать себя в наушниках
  • mic->incoming_out — либо звонящего всё равно нет и сигнал отбрасывается snd-aloop, либо он должен слышать оператора
  • incoming_in->monitor — либо звонящего всё равно нет и с входа идёт тишина, либо оператору необходимо его слышать
  • player->radio — проигрыватель всегда что-то играет, будь то музыка или фон для разговоров
  • player->monitor — чтобы оператор слышал музыку
  • player->chat_out — музыка в конференцию

Уровень входа player регулируется, в зависимости от того, это фон или трек.

Всегда отключены:
  • chat_in->chat_out — конференции не нужна обратная связь
  • incoming_in->incoming_out — входящему не нужна обратная связь
  • player->incoming_out — входящему необязательно слышать фоновую музыку


В процессе ведения эфира оператор может делать комментарии ведущим голосом или текстом, при этом голос не должен быть выведен в эфир (mic->radio в положении Mute).

Слушание музыки

В этом состоянии в радиоэфир идёт только музыка. Оператор и конференция могут общаться, если сумеют её перекричать, потому, что музыка транслируется в конференцию. Кроме постоянных, включены:
  • mic->chat_out — чтобы конференция слышала оператора
  • chat_in->monitor — чтобы оператор слышал конференцию


За некоторое время до окончания трека нужно предупреждать ведущих об этом. Если поступил входящий звонок, переходим к его приёму: отключаем конференцию и музыку в мониторы, принимаем звонок, потом сообщаем в конференцию об этом и ждём окончания трека.

Болтовня в эфире

Здесь в эфир идёт музыка (тихо) и конференция. Маршрутизация такова:
  • mic->chat_out
  • mic->radio — если оператор участвует в дискуссии
  • chat_in->radio

Если поступил входящий звонок, микрофон отключается от конференции и эфира, а от монитора отключается конференция. Стоит предупредить конференцию, что есть входящий, чтобы они не анонсировали новый трек.

Приём входящего звонка

Возможен в случае если играет музыка или происходит беседа в реальном времени (изображение отражает второй вариант). При этом оператор отключает звуковую связь с конференцией и с радио, и принимает вызов Skype. Проинструктировав звонящего, он сообщает в конференцию о входящем и когда ведущие готовы его ввести, коммутирует его в конференцию и на радио.
Включены следующие каналы (кроме тех, которые включены постоянно):
  • chat_in->radio, если звонок поступил в режиме беседы


Разговор с позвонившим

В этом режиме происходит беседа с позвонившим, которая транслируется в эфир. Включены такие каналы:
  • chat_in->radio
  • chat_in->monitor
  • chat_in->incoming_out чтобы звонящий слышал конференцию
  • incoming_in->radio
  • incoming_in->chat_out чтобы конференция слышала звонящего
  • mic->chat_out
  • mic->radio если оператор тоже участвует в беседе


Рабочая среда в процессе

Проще всего это прокомментировать иллюстрацией:

Все остальные окна, не необходимые в процессе ведения, находятся на другом рабочем столе KDE.

Конференцию может собирать любой участник. Проще, если это будет не оператор радиомоста, для его разгрузки. С точки зрения конференции, входящие находятся в ней от имени оператора (например, в Skype подсвечивается иконка оператора, если говорит позвонивший).

Радиоэфир фестиваля, состоявшийся 19 апреля

По этой схеме мы и организовали предфестивальный радиомост. Он начался 19 апреля в 20 часов вечера и планировался на три часа, но в процессе было решено продлить его на час.

До начала эфира были подобраны треки для воспроизведения, написан примерный сценарий эфира (в виде порядка треков). Эфир оказался более многословным, чем предполагалось изначально, поэтому трековый сценарий дошёл не до конца.

За всё время в эфир слушало до ста одновременных слушателей. Со Skype «для входящих звонков» во время эфира управлялись два человека, поскольку в него же присылались текстовые вопросы. Для посетителей также была организована текстовая Skype-конференция для них текстовом скайп-чате. Нагрузка на оператора (которым выступал я) оказалась меньше ожидаемой, поэтому удалось с ними тоже пообщаться в этом чате.

Эфир записывался, после эфира запись была выложена для онлайн-прослушивания, как подкаст (после небольшой подчистки моментов, в которых были разрывы). Её на данный момент скачали триста человек.

У оператора три или четыре раза за всё время прерывался интернет. В это время администратор сервера ретрансляции оперативно подключал резервный поток, или запускал тот трек, который должен был быть дальше по сценарию. Логика встроенного fallback в Icecast нас не устроила.

За первый час не поступило ни одного входящего звонка. Потом начали звонить, и дозвонившиеся люди иногда сами не верили, что дозвонились. Кстати, довольно забавное ощущение, когда позвонивший тебя узнал по голосу в скайпе, а ты не знаешь этого человека.

Выводы и направление дальнейшего развития

Радиоэфир был для нас оказался новым делом, и было сделано несколько ошибок. Можно отметить следующие моменты:

  • Управление оказалось несколько сложнее, чем могло быть. Данная проблема решаема, следует написать программу с пятью кнопками-переключателями, которая будет управлять микшером по OSC или MIDI
  • Audacious — не самый удобный для этого применения проигрыватель. Он легковесный и не содержит вредных функций медиабиблиотеки, однако не хватало и полезной функции простого кроссфейда. Также им неудобно управлять программно, если будет разработана отдельная программа для ведения эфира
  • В процессе воспроизведения трека он идёт в конференцию на полной громкости, что затрудняет общение в ней (без трансляции). Уже к концу эфира я придумал, как это исправить, но было поздно. Нужно добавить ещё один стереовход, «громкий», который будет выводиться в только в эфир и только на время трека; существующий будет объявлен «тихим» и будет выводиться постоянно и везде (в конференцию, на радио и в монитор). JACK является сэмпл-аккуратным сервером, поэтому можно не бояться фазовых comb-эффектов от сложения одинаковых сигналов.
  • Пропадания интернета у оператора — самая заметная из проблем. Уже после эфира придумано и проверено решение, как всё-таки правильно приспособить логику fallback так, чтобы она работала подходящим образом
  • При останове darkice почему-то падал jackd. Это очень неприятно, потому, что при этом приходится перезапускать все jack-приложения и настраивать коммутацию заново. А darkice останавливался при разрыве соединения с icecast в интернете. Чтобы падение интернета не рушило всю систему, был поднят локальный icecast, а основной был настроен как ретранслятор с локального


Однако, в целом мы с задачей справились. Как организаторам, так и аудитории понравилось, и они просят повторить эфир и даже сделать такие эфиры-радиомосты регулярными.

Спасибо за внимание!
Tags:
Hubs:
+20
Comments 24
Comments Comments 24

Articles