Новость из категории: Информация

Звуковой сервер JACK. Пишем простейший клиент: теория

Звуковой сервер JACK. Пишем простейший клиент: теория

С ростом популярности звукового сервера JACK (http://jackaudio.org) свободные программы для создания музыки вышли на новый качественный уровень. Раньше было так, что программист, возжелавший написать свой синтезатор, волновой редактор или DAW (Digital Audio Workstation, рабочая станция по обработке цифрового звука), должен был озаботиться еще и вводом-выводом звука, создать свой код для общения с низкоуровневой звуковой подсистемой вроде ALSA или CoreAudio. А JACK намного упростил это дело.

На плечи JACK возложено не только общение со звуковой подсистемой, но и возможность взаимодействия между программами, использующими сервер JACK. Например, можно одновременно запустить драм-машину Hydrogen и DAW Ardour, затем запустить в Ardour песню на воспроизведение - при этом начнется воспроизведение партии ударных в Hydrogen. «Прокручиваем» материал в одной программе - прокрутка передается и в другую. Эту возможность дают транспортные функции JACK. Но более известны другие его функции - предоставление портов ввода-вывода. Эти порты можно связывать между собой, направляя звуковые потоки не только на выходы звуковой карты, но и в другие программы - клиенты сервера JACK. Клиенты тоже имеют порты ввода-вывода.

Сейчас редко встретишь большую «звуковую» программу, которая бы не поддерживала JACK. Судите сами. Ardour работает через JACK - впрочем, немудрено, ведь разработчик-то один - легендарный Пол Дэвис (Paul Davis), который начал писать Ardour десять лет назад в подвале, когда от него ушла жена. Rosegarden - это JACK. Qtractor - это JACK, кроме того, автором Qtractor является rncbc aka Rui Nuno Capela, создатель популярнейшего графического центра управления JACK - qJackCtl. MUSE - снова JACK. Другие киты звукового ПО, работающие через этот сервер: Hydrogen, LMMS. Да что говорить - даже некоторые проигрыватели обзавелись поддержкой JACK.

Велика вероятность, что если вы задумали писать даже небольшую звуковую программу, то решите связать её судьбу с JACK. Ведь это не только облегчит ваш труд, но и автоматически сделает программу пригодной для совместной работы с другими JACK-программами. Допустим, вам лень писать свои индикаторы громкости, а хочется видеть пики воочию. Всего парой щелчков мыши можно будет послать вывод с вашей программы в такую, где эти индикаторы есть. Более того, существует отдельная программа с индикаторами - Meterbridge (http://plugin. org.uk/meterbridge). Даже если ваша программа - консольная, можно использовать для вывода графики другой JACK-клиент, тот же Meterbridge.

Итак, JACK. С чего начать? Со скачивания исходников и документации с http://jackaudio.org. В архиве с исходниками есть примеры. К сожалению, ни один из них не показывает самую важную вещь: как загрузить волновой файл и воспроизвести его через JACK.

В этой статье я восполню пробел, а заодно на примере постараюсь показать основы работы JACK. Предполагается, что вы знаете Си или С++. Пример будет на С++ с использованием библиотеки Qt - впрочем, если вы с ней не работали, не беда: замените вызовы qDebug на cout, и дело в шляпе; плюс небольшая возня с подключением библиотек. Нет в примере ничего такого, что нельзя было бы выразить и на обычном Си.

Кроме Qt нам понадобится, конечно же, сам JACK с заголовочными файлами, а еще библиотека libsndfile (http://www. mega-nerd.com/libsndfile), которая обеспечит загрузку волновых файлов (WAVE). Если у вас нет готовых WAV-файлов - скачайте с freesound.org. Потребуется стереозвук любой разрядности с частотой оцифровки 44100 или 48000 герц.



В зависимости от частоты оцифровки надо будет запустить сервер JACK. Его проще всего запускать через утилиту qJackCtl и частоту оцифровки выбрать в настройках, но можно из консоли:
jackd -d alsa -r 44100

В этом примере мы запускаем сервер с бэкэндом ALSA и частотой оцифровки 44100 герц. Запуск JACK с минимумом параметров выглядит так:
jackd -d alsa

При этом частота оцифровки будет равна 48000 герц. В статье я не трогаю тему запуска сервера в реальном времени и тонкой настройки разных параметров - нам нужен просто работающий сервер. Прежде чем начать собственно изложение примера, напомню, что такое цифровой звук и откуда он берется. Это нужно для введения в дальнейшее изложение некоторых терминов, один из которых - «сэмпл» (в разных областях звукового мира трактуется по-разному). Как оцифрованный звук попадает в компьютер извне? Например, человеческий голос? Как вы знаете, звук, который мы слышим, это колебания воздуха - изменения его плотности. Эти колебания улавливаются микрофоном и тоже преобразуются в колебания, но уже электромагнитные - изменения напряжения. Эти изменения поступают на АЦП - аналого-цифровой преобразователь звуковой карты, задача которого - ставить в соответствие поступающему уровню напряжения определенные числа. Это и есть оцифровка звука.

Итак, числами можно выразить колебания. Какие именно числа и в каком диапазоне - зависит от формата. Например, при формате оцифровки 16 бит со знаком мы имеем для записи уровня сигнала числа от -32768 до +32767. А при беззнаковом восьмибитном формате у нас всего 256 чисел, от 0 до 255. Чем больший диапазон чисел, тем более точно можно выразить ими изменения уровня.

Звуковой сервер JACK. Пишем простейший клиент: теория

АЦП измеряет (и преобразует в числа) поступающие электромагнитные колебания столько-то раз в секунду, опять же - в зависимости от выбранной частоты оцифровки. Например, когда говорят о частоте 44100 герц - это значит, что АЦП каждую секунду преобразует в числа по 44100 «замеров». Кстати, на обычных музыкальных дисках, в большинстве MP3 и Ogg именно такая частота оцифровки. А в фильмах обычно другая - 48 килогерц, или 48000 герц. Так вот, каждое такое «число-замер» и называется сэмплом. Сэмплами также именуются и целые волновые файлы или даже инструменты особых форматов, но то уже сэмпл не из области программирования, не низкого уровня, а из среды музыкантов-клавишников и диджеев.

Кроме целочисленных значений сэмпл может быть представлен дробным числом, в так называемом формате с плавающей точкой - грубо говоря, каждый сэмпл представлен переменной типа float. И при этом число - «замер» - подвергнуто нормализации, то есть положено в диапазон от -1.0 до +1.0. Например, сэмпл при таком раскладе может быть равен 0.324345, 0.2345, и тому подобное. Зачем всё это, если целые числа вроде бы проще?

В цифровой обработке звука значения сэмплов легко выходят за границы диапазонов, охватываемых типами данных. Например, у нас есть два 16-битных сигнала - две последовательности 16-битных сэмплов. Два массива: А и Б. Мы хотим их смикшировать в один сигнал. Допустим также, что A[4] = 30000, Б[4] = 20000.

Как микшируются сигналы? Просто складываются (хотя в настоящих, не условных, цифровых микшерах к сигналам еще применяют кучу операций - в том числе, по управлению уровнем сигнала). Сложив 30000 и 20000, мы получим 50000 - число, выходящее за пределы диапазона, предоставляемого 16 битами (числа от -32768 до +32767). Очевидно, что в этом месте микширования звук будет испорчен.

Подобный эффект называется clipping^ и звучит как неприятный резкий звук. Конечно, есть и целочисленные алгоритмы обработки звука, но в них много ухищрений, а вот алгоритмы с плавающей точкой проще. Там у нас больше свободы действия: складывая числа от -1.0 до +1.0, мы если и вылезем за их пределы, то за пределы самого типа float не выйдем. Да, громкий звук всё равно придется делать тише (это отдельный разговор; вкратце - делением на целое число либо умножением на дробные), но у нас не происходит цифровое зашка-ливание, этот самый clipping.

Вот почему в JACK принят именно этот способ представления звука - в виде сэмплов типа float, с нормализацией. Как вообще работает JACK?

Первым делом стоит рассказать о портах. JACK предоставляет порты: отдельно для ввода (input), отдельно для вывода (output). Порты могут быть связаны как с «железной» звуковой картой (точнее, со звуковой подсистемой вроде ALSA), так и с JACK-клиентами, у которых тоже свои порты.

В контексте JACK входной порт - это порт, куда мы можем посылать звук. Входные порты с именами playback_1 и playback_2 соответствуют, грубо говоря, двум каналам линейного выхода звуковой карты.

Итак, входные порты JACK - это порты, куда мы посылаем звук. А выходные порты - то, откуда мы можем получать звуковой сигнал. Обычные такие порты - capture_1 и capture_2. Это, опять же грубо говоря, два канала линейного входа звуковой карты.

Если, например, соединить capture_1 и playback_1, мы мгновенно перенаправим звук с микрофона на колонки. Та же история и при разработке мобильных приложений (http://www.kingbird.ru/mobilnye-prilozheniya/) данного класса, только роль колонок здесь на себя берет динамик!

Когда мы пишем JACK-клиент, предусматриваем его подключение к портам - при этом создаем в клиенте свои порты, которые становятся доступны через JACK для других клиентов. Простейший JACK-клиент для воспроизведения звукового сигнала действует так:
- подключается к серверу, создав порты для общения с ним;
- Подготавливает звуковые данные (синтезирует сигнал или загружает из волнового файла);
- по частям передает этот сигнал на входной порт JACK.

Все три этапа мы рассмотрим ниже подробно, но вначале - пара слов о третьем этапе. Вы должны предоставить движку JACK callback-функцию вида:
int process (jack _ nframes _ t nframes, void *arg);

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

Допустим, у нас есть двухканальный (стерео) сигнал, каждый - из 777 сэмплов. Число условно! А JACK просит - опять же условно - 3 кадра. Это значит, что серверу надо передать 3 сэмпла левого канала, и 3 сэмпла правого. Текущий кадр обозначает не физическую позицию в буфере, а скорее абстрактную позицию, номер сэмпла, как если бы в сигнале был только один канал. На деле же данные в буфере хранятся так: сэмплы разных каналов идут друг за другом последовательно, т.е. первый сэмпл левого канала, потом первый сэмпл правого, потом второй левого, затем второй правого, и так далее. Этот способ хранения называется «перемежающимся» (interleaved).

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

Рейтинг статьи

Оценка
5/5
голосов: 3
Ваша оценка статье по пятибальной шкале:
 
 
   

Поделиться

Похожие новости

Комментарии

^ Наверх