JACK и несколько звуковых карт

2012.02.10

JACK — единственная звуковая система для UNIX систем, позволяющая коммутировать звуковые потоки на уровне приложений (самая распространённая на сегодняшний день система — Pulse Audio — умеет коммутировать звук только на уровне устройств, то есть можно перекинуть MP3-проигрыватель со внутренней звуковой карты на внешнюю, но нельзя перекинуть в Skype).  Поэтому JACK является основой рабочей станции любого интернет-радиовещателя, использующего Linux.

Основная проблема JACK в том, что он умеет работать только с одной звуковой картой.  Для вещания обычно используется внешнее звуковое устройство, подключаемое по USB, или USB микрофон, потому что встроенные в ноутбуки или другие компьютеры звуковые карты, какими крутыми бы они ни были, дают отвратительный звук с сильными шумами.  Для переключения между внешним и внутренним устройствами нужно перезапускать JACK, а в случае с использованием внешнего USB микрофона вообще не ясно: проигрывать в него нельзя и проигрывать, получается, не во что.

Уточнение: при постоянном использовании JACK со внешним USB микрофоном можно использовать ключи -P и -C командной строки; использовать разные карты для ввода и вывода звука JACK умеет.  Однако если нужно отключать микрофон на ходу, то без описанной здесь схемы не обойтись.

Пара слов об архитектуре JACK

Здесь надо сделать отступление и вкратце объяснить, как работает коммуникация в JACK.  JACK оперирует портами.  У звуковой карты обычно есть два выходных порта: левый и правый, называются они "system:playback_1" и "system:playback_2", и есть два входных порта: "system:capture_1" и "system:capture_2" (это микрофон или линейный вход, я не знаю как они делятся между собой).

Включение внешней звуковой карты в JACK

Для того, чтобы JACK увидел ещё одну звуковую карту, используются утилиты alsa_in и alsa_out, которые создают новые порты и тупо ретранслируют туда вход или выход указанной звуковой карты.  (Этот подход оказался для меня несколько неожиданным: я долго пытался найти способ «скрестить» две звуковые карты средствами ALSA — и нашёл, но он оказался слишком сложным для моего понимания.)

Итак, чтобы подружить JACK и внешнюю звуковую карту, достаточно запустить alsa_out, указав её имя:

$ alsa_out -d hw:USB

Чтобы узнать имя звуковой карты, используется утилита aplay:

$ aplay -L
front:CARD=Intel,DEV=0
    HDA Intel, ALC269 Analog
    Front speakers
...
front:CARD=USB,DEV=0
    E-MU 0202 
    Front speakers

(В этом примере видны две карты: "Intel" и "USB", эти названия и следует указывать в качестве значений параметра -d.)

Теперь в списке портов видны два новых:

$ jack_lsp
system:playback_1
system:playback_2
...
alsa_out:playback_1
alsa_out:playback_2

В эти порты можно играть звук, и он пойдёт на внешнюю звуковую карту (причём можно в обе сразу, хотя это мне никогда не бывает нужно).  Можно на ходу отправить какую-нибудь программу играть звук во внешнюю карту:

$ jack_connect "MPlayer .*:out_0" "alsa_out:playback_1"
$ jack_connect "MPlayer .*:out_1" "alsa_out:playback_2"

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

Автоматическая коммутация

JACK не умеет сам определять что с чем коммутировать (философия UNIX: JACK не этим занимается).  Для автоматизации этого процесса используется утилита jack.plumbing, которая обычно висит в фоне и коммутирует потоки в соответствии с правилами из специального текстового файла, который называется ~/.jack.plumbing.  Автоматическая отправка MPlayer во внешнюю и внутреннюю звуковые карты выглядит так:

# внутренняя карта
(connect "MPlayer .*:out_0" "system:playback_1")
(connect "MPlayer .*:out_1" "system:playback_1")
# внешняя карта
(connect "MPlayer .*:out_0" "alsa_out:playback_1")
(connect "MPlayer .*:out_1" "alsa_out:playback_1")

Теперь при подключении внешней звуковой карты MPlayer сразу будет начинать играть через неё, в дополнение ко внутренней.  (Можно использовать во втором блоке директиву connect-exclusive, тогда при подключении ко внешней карте он будет отключен от внутренней, однако на практике при проведении прямых эфиров эксклюзивное подключение бесполезно, т.к. MPlayer должен играть и в звуковую карту, и в Skype, и в эфир радиостанции — это как минимум три неэксклюзивных направления; на практике проще временно заглушить внутреннюю карту с помощью утилит alsamixer или amixer.)

Примерный алгоритм работы

Для начала прямого эфира у меня есть скрипт, который выполняет следующие действия:

  1. Глушит внутреннюю карту утититой amixer.
  2. Запускает в фоновом режиме утилиты jack.plumbing, alsa_out и alsa_in, если они не запущены.
  3. Запускает в фоновом режиме darkice (вещание в радиостанцию).
  4. Запускает утилиту jack_meter для наблюдения за уровнем записи.

Следить за развитием событий можно через RSS ленту или почтовую рассылку.