Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Audacity

Глава 2 из книги "Архитектура приложений с открытым исходным кодом", том 1.

Оригинал: Audacity, глава из книги "The Architecture of Open Source Applications" том 1.
Автор: James Crook
Дата публикации: 2013 г.
Перевод: Н.Ромоданов
Дата перевода: август 2013 г.

Creative Commons. Перевод был сделан в соответствие с лицензией Creative Commons. С русским вариантом лицензии можно ознакомиться здесь.

Audacity является популярной программой записи звука и популярным аудио-редактором. Эта программа достаточно мощная и, в то же время, проста в использовании. Большинство ее пользователей работают на Windows, но тот же самый исходный код Audacity можно откомпилировать для использования также на Linux и Mac.

Доминик Мазони (Dominic Mazzoni) написал первую версию Audacity в 1999 году, когда он был аспирантом Университета Карнеги-Меллона. Доминик хотел создать платформу для разработки и отладки алгоритмов обработки аудиозаписей. Программа развивалась и расширялась сфера ее применения. Как только Audacity была выпущена с открытым исходным кодом, она привлекла других разработчиков. На протяжении многих лет небольшая не сильно меняющаяся команда энтузиастов модифицировала, сопровождала, обновляла эту программу, писала для нее документацию, помогала пользователям и переводила интерфейс Audacity на другие языки.

Одна из целей создания этой программы состояла в том, что сразу можно обнаружить в ее пользовательском интерфейсе: у людей должна быть возможность сесть и сразу без всяких руководств начать пользоваться программой, постепенное открывая для себя ее новые возможности. Этот принцип оказал гораздо большее влияние на согласованность пользовательского интерфейса Audacity, чем что-либо другое. Для проекта, который у многих всегда под рукой, такой принцип унификации важнее, чем это кажется на первый взгляд.

Было бы хорошо, если архитектура Audacity следовала бы такому же принципу, которому придерживаются в пользовательском интерфейсе. Лучшее, что у нас для этого есть, это - «попробовать и быть последовательными». Когда добавляется новый код, разработчики стараются следовать стилю и соглашениям, которые есть в коде, расположенному по соседству. Однако, на практике база кода Audacity представляет собой смесь хорошо структурированного кода и кода, который структурирован несколько хуже. Скорее всего, вся архитектура в общем похожа на небольшой город: есть несколько впечатляющих зданий, но вы также найдете и захудалые районы, которые больше напоминают трущобы.

2.1. Структура Audacity

Audacity состоит из слоев нескольких библиотек. Хотя для большей части нового кода, который программируется в Audacity, не требуется детального знания того, что именно происходит в этих библиотеках, знакомство с их интерфейсом API, и то, что они делают, является важным. Двумя наиболее важными библиотеками являются библиотека PortAudio, в которой предоставлен низкоуровневый аудио интерфейс, обеспечивающий кросс-платформенность, и библиотека wxWidgets, в которой предоставлен графический компонент кросс-платформенности.

Когда читаете код Audacity, полезно понимать, что важной является только часть кода. Библиотеки добавляют много необязательных функций, хотя те, кто ими пользуется, не считают, что они необязательные. Например, имея свои собственные встроенные звуковые эффекты, Audacity поддерживает LADSPA ( Linux Audio Developer's Simple Plugin API — интерфейс API простых плагинов разработчиков Linux Аудио) для динамически загружаемых плагинов звуковых эффектов. Интерфейс VAMP API в Audacity делает то же самое для плагинов, которые анализируют аудио. Без этих интерфейсов Audacity была бы менее функционально насыщенной, но она абсолютно не зависит от этих возможностей.

Другими необязательным библиотеками, используемыми Audacity, являются libFLAC, libogg и libvorbis. С их помощью реализуются различные форматы сжатия аудиосигнала. Формат MP3 реализуется за счет динамической загрузки библиотеки LAME или Ffmpeg. Лицензионные ограничения не позволяют встраивать эти очень популярные библиотеки сжатия.

Лицензирование стоит за некоторыми другими решениями, относящимся к библиотекам и структурам Audacity. Например, поддержка плагинов VST не встраивается из-за лицензионных ограничений. Нам бы также хотелось в некоторых местах нашего использовать очень эффективный код FFTW , реализующий быстрое преобразование Фурье. Однако, мы предоставляем это только как дополнительный вариант для тех, кто компилирует Audacity самостоятельно, а вместо этого возвращаемся к версии, которая встроена в наш код и работает чуть медленнее. До тех пор, пока в Audacity можно пользоваться плагинами, можно и следует утверждать, что в Audacity не должен использоваться пакет FFTW. Авторы FFTW не хотят, чтобы их код был доступен в виде обычного сервиса в каком-либо другом коде. Так, архитектурное решение о поддержке плагинов приводит к компромиссу, относящемуся к тому, что мы можем предложить. Он позволяет в наших готовых исполняемых файлах использовать плагины LADSPA, но запрещает нам использовать FFTW.

На архитектуре сказались также и наши мысли о том, как лучше использовать наше ограниченное время, уделяемое разработке. С небольшой командой разработчиков, у нас нет, например, ресурсов, чтобы произвести углубленный анализ лазеек, связанных с безопасностью, который делают группы, работающие над Firefox и Thunderbird. Тем не менее, мы не хотим, чтобы Audacity было средством, позволяющем обходить брандмауэр, поэтому у нас есть правило вообще не иметь в Audacity входящих или исходящих соединений TCP/IP. Отсутствие соединений TCP/IP позволяет избежать многих проблем безопасности. Наше понимание того, что мы обладаем ограниченными ресурсами, ведет нас к лучшим проектным решениям. Это помогает нам отказываться от функций, на разработку которых нам придется тратить слишком много времени, и сосредотачиваться на том, что наиболее важно.

Аналогичную озабоченность, касающаяся времени разработки, связана со скриптовыми языками. Мы хотим писать сценарии, но код, реализующий эти языки, не должен быть в Audacity. Нет смысла создавать в Audacity копии всех скриптовых языков лишь для того, чтобы дать пользователям возможность выбрать тот, который им нужен [1]. Вместо этого нами реализована возможность писать скрипты с помощью единственного модуля плагина и конвейера, что мы рассмотрим позже.

Рис.2.1: Слои в Audacity

На рис.2.1 показаны несколько слоев и модулей, имеющиеся в Audacity. На схеме в wxWidgets выделены три важных классах, играющих важную роль в Audacity. Мы, опираясь на абстракциях низкого уровня, строим абстракции более высокого уровня. Например, система блок-файлов BlockFile отображается в объекты wxFile, имеющиеся в wxWidgets. Возможно, на некотором этапе, имеет смысл выделить блок-файлы BlockFile, графическую оболочку ShuttleGUI и команды обработки в промежуточную библиотеку с ее собственными правами. Это должно стимулировать нас сделать их более обобщенными.

Ниже в диаграмме показана узкая полоска «Слои реализации конкретных платформ». Оба слоя wxWidgets и PortAudio являются слоями абстракции ОС. В них обоих есть код, позволяющий, в зависимости от целевой платформы, делать выбор между различными реализациями.

В категорию «Другие библиотеки поддержки» включен широкий набор библиотек. Достаточно интересно то, что в многих из них используются динамически загружаемые модули. Эти динамические модули ничего не знают о wxWidgets.

На платформе Windows мы, как правило, компилируем Audacity в один монолитный исполняемый файл, причем код приложений wxWidgets и Audacity входит в тот же самый исполняемый файл. В 2008 году мы перешли на использование модульной структуры и используем wxWidgets как отдельную DLL. Это позволяет во время выполнения программы загружать дополнительные DLL с необязательными функциями только тогда, когда в этих DLL непосредственно используются возможности wxWidgets. Плагины, которые на схеме подключены выше пунктирной линии, могут использовать wxWidgets.

Решение об использовании DLL для wxWidgets имеет свои минусы. Размер дистрибутива теперь стал больше, частично из-за того, что в DLL есть много неиспользуемых функций, которые раньше были просто удалены. Загрузка Audacity также происходит дольше, поскольку каждая DLL загружается отдельно. Но преимуществ больше. Мы надеемся, что модульность даст нам те же преимущества, какие есть в Apache. Мы видим, что модули позволяют ядру Apache быть очень стабильными, а поддержка экспериментов, специальных возможностей и новых идей происходит в модулях. Модули проходят очень долгий путь, противодействуя искушению изменить направление развития проекта. Мы думаем, что это для нас это было очень важным архитектурным решением. Мы ожидаем от этого выиграть, но еще мы этого не достигли. Предоставление функций wxWidgets в таком виде является лишь первым шагом, и мы должно много что сделать для того, чтобы получить более гибкую модульную систему.

Структура программы такой, как Audacity, заранее четко не разрабатывается. Она разрабатывается по ходу дела. По большому счету, архитектура, которая у нас есть, для нас подходит. Нам приходится сражаться с архитектурой, когда мы пытаемся добавлять новые возможности, которые затрагивают многие файлы с исходным кодом. Например, в настоящее время в Audacity стерео и моно дорожки обрабатываются специальным образом. Если бы вы хотели изменить Audacity так, чтобы обрабатывать объемный звук, то вам бы потребовалось вносить изменения во многие классы в Audacity.

За пределами стереозвука: история о GetLink

Audacity никогда не имела абстракции числа каналов. Вместо этой абстракции имелись ссылки на аудиоканалы. Есть функция GetLink, которая возвращает другой парный звуковой канал в паре, если их два, или возвращает NULL, если дорожка монофоническая. Код, в котором используется GetLink, обычно выглядит точно так, как если бы его первоначально написали для монофонического сигнала, а затем использовали проверку (GetLink() != NULL) с тем, чтобы обрабатывать стереосигнал. Я не уверен, что крд был написан именно так, но подозреваю, что так это и произошло. Нет циклического использования GetLink по всем каналам, находящихся в связанном списке. При рисовании, микшировании, чтении и записи — везде используется проверка случая обработки стереосигнала, а не обобщенный код, который может работать на n каналах, где n, скорее всего, должно быть равно одному или двум. Чтобы перейти к более обобщенному коду, вам потребуется внести изменения приблизительно в 100 мест, где вызывается функция GetLink, изменив, по меньшей мере, 26 файлов.

Легко выяснить, что найти вызовы GetLink и внести необходимые изменения с тем, чтобы исправить эту «проблему», не так уж сложно, как это может показаться на первый взгляд. История с GetLink не о структурных изъянах, которые трудно исправить. Она, скорее всего, иллюстрирует то, как относительно небольшой изъян может переместиться в различные части коде, если этому не воспрепятствовать.

Если оглянуться назад, то было бы хорошо сделать функцию GetLink приватной и предложить итератор для перебора всех каналов в дорожке. Это позволило бы отказаться от большого количества специальных случаев кода, предназначенных для стереосигналов, и, в то же время, создать код, в котором список аудио каналов использовался бы независимо от того, как реализован список.

Более модульный проект, вероятно, позволит нам лучше скрыть внутреннюю структуру. Когда мы определяем и расширяем внешние интерфейсы API, нам требуется более внимательно изучать функции, которые нам предлагаются. В результате наше внимание сосредотачивается на абстракциях, а мы не хотим, чтобы они присутствовали во внешнем API.


Продолжение статьи: Библиотека графического интерфейса wxWidgets.