LXF126:Phonon

Материал из Linuxformat.

Перейти к: навигация, поиск
Phonon Введение в создание кросс-платформенных мультимедиа-приложений

Содержание

Phonon: И снова видеоплейер

GStreamer из LXF125 показался вам недостаточно высокоуровневым? Не беда – Андрей Боровский предложит еще одну надстройку над ним (и не только) для тех, кто недолюбливает Gnome.

Всвоих «Записках нечаянного революционера» Линус отмечал, что современные компьютеры хуже справляются с задачей наглядных обучающих пособий для хакеров, нежели те машины, на которых он сам начинал работу над Linux. В начале 90‑х «систему» еще можно было окинуть одним взглядом. Любой достаточно квалифицированный программист не только мог, но и должен был перемещаться между уровнями оборудования и прикладного ПО. В наши дни такое вряд ли удастся кому-то на практике. Любой современный инструмент прикладного программиста – это несколько уровней обобщения и абстракции. Интересоваться, что скрывается там, под капотом, совершенно необязательно (а многообразие устройств и технологий обработки данных таково, что никто не сможет удерживать в голове сколько-нибудь значительную часть этой картины).

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

В нагромождении абстрактных слоев «виноваты» не только усложняющиеся технологии, но и пользователи. Сегодня они хотят, чтобы все источники мультимедиа открывались и контролировались одной программой, причем последняя должна предоставлять однородный интерфейс для управления такими разными мультимедиа-потоками, как аудиофайлы на диске, интернет-трансляции, простые видеоролики и данные DVD, разделенные на главы и имеющие по нескольку звуковых дорожек. В дополнение ко всему этому современная мультимедиа-программа должна быть легко расширяемой, то есть без проблем (и без перекомпиляции!) «брать на борт» новые кодеки и устройства. Можно сказать, что в то время, когда мультимедиа-технологии становятся сложнее, работающие с ними программы должны становиться проще.

Теория фононов

Рис. 1 Рис. 1. Граф Phonon для видеоплейера.

Система Phonon, которая продвигается проектом KDE и на данный момент является самым удобным мультимедиа-каркасом для Qt, представляет собой наглядный пример объединения нескольких слоев обобщений и абстракций. Основой Phonon (на платформе Linux) являются такие технологии, как ALSA и GStreamer (LXF125), которые сами представляют собой набор из нескольких уровней. Над этими технологиями Phonon надстраивает еще один уровень абстракции, оперирующий понятиями «поток данных» и «источник данных». В результате, от программиста, желающего написать с помощью Phonon, например, видеоплейер, требуется несколько строчек кода и не требуется понимания того, что при этом происходит с данными. Ситуация для меня лично грустная, но неизбежная.

В основе Phonon лежит идея, которая используется в большинстве современных мультимедиа-систем (например, DirectX): приложение представляет собой граф, в вершинах которого расположены компоненты обработки данных (источники данных, преобразователи, устройства ввода и вывода). Потоки данных передаются между компонентами по путям, определенным ребрами графа. Следует отметить, что интерфейсы Phonon еще не обрели окончательной стабильности. Даже документация Qt не всегда соответствует тому, что мы видим в библиотеке.

Помимо поддержки Phonon, Qt Software разрабатывает собственную систему, QtMultimedia. В настоящее время ее работоспособность оставляет желать лучшего, и документация признает, что система Phonon более удобна для программистов.

Чтобы не делать абстрактную модель еще более абстрактной, рассмотрим работу Phonon с точки зрения интерфейса подсистемы Qt-Phonon в Qt 4.5–4.6. Основными элементами графа Phonon являются объекты-источники данных (Phonon::MediaObject), компоненты-«стоки» [sinks], представляющие на уровне приложения устройства вывода данных (например, класс Phonon::VideoWidget, отображающий видео), и компоненты, выполняющие обработку данных, полученных из источника, перед передачей их компоненту-стоку (класс Phonon::Effect). Классы, предназначенные для создания узлов мультимедиа-графа, являются потомками Phonon::MediaNode.

Более подробно схема типичного мультимедиа-приложения Phonon (видеоплейер) показана на рис. 1. Компоненты, которые являются частью графа Phonon, обведены синей каймой. Кроме компонентов, входящих в граф, приложение использует вспомогательные. Объекты MeidaSource играют роль посредника между низкоуровневым источником данных и объектами MediaObject (интерфейс между интерфейсами!). Потомки MediaController позволяют выполнять сложное управление источником данных (например, навигацию по DVD).

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

Перейдем к практике

Чтобы программировать для Phonon и Qt, вам, помимо библиотеки Qt, понадобятся библиотека libphonon и файлы разработчика для нее, библиотеки и файлы разработчика ALSA (как минимум), а также компоненты мультимедийной системы, которые Phonon применяет для обработки данных. Любой разработчик серьезных мультимедиа-приложений для Linux сталкивается с одной специфической проблемой: в то время как в Linux есть программы и кодеки, позволяющие воспроизводить практически любой мультимедиа-контент, у кодеков наиболее популярных коммерческих форматов есть проблемы с лицензированием. Разработчики полностью открытых приложений могут просто игнорировать эти проблемы (практика показывает, что никто не станет их за то преследовать), но для разработчиков коммерческих продуктов (таких как Qt) подобный образ действий не подходит. Система Phonon решает проблему лицензирования просто: обработка мультимедиа-форматов перекладывается на другие компоненты системы, которые в терминологии Phonon именуются движками [backends]. Возможности Phonon в конкретной системе определяются возможностями этих движков (без корректно установленных движков система Phonon вообще работать не будет). В качестве движков Phonon может использовать, например, GStreamer и кодеки Xine. Документация по Qt упоминает только движки GStreamer и только открытые мультимедиа-форматы, намекая при этом, что возможно подключение и других форматов данных. На практике Phonon, при правильной настройке, поддерживает все форматы мультимедиа, которые понимают остальные мультимедиа-компоненты Linux.

Помимо самих компонентов Xine и GStreamer нужно установить библиотеки phonon_gstreamer и phonon_xine. Если у вас установлена новая версия KDE (4.2.2+) то, скорее всего, в вашей системе движки Phonon уже настроены.

Для подключения системы Qt-Phonon к проекту приложения Qt в файл *.pro необходимо добавить строку

QT += phonon

Базовые классы Phonon определены в пространстве имен Phonon, которое становится доступным при включении в проект заголовочного файла <Phonon>.

Одно из важнейших преимуществ, которое мы получаем при использовании Phonon – кроссплатформенность. Система Phonon реализована для Windows (на основе DirectShow) и для Mac (на основе QuickTime). Как ни странно, заставить Phonon работать под Windows проще, чем под Linux: Qt SDK for Windows содержит все необходимые компоненты.

Привет, плейер!

Рис. 2 Рис. 2. Воспроизведение аудио-файлов с помощью Phonon.

Чтобы наделить приложение способностью воспроизводить аудиофайлы с помощью Phonon, в него достаточно добавить буквально три строчки кода. На диске вы найдете программу SimpleAudioPlayer, которая показывает, как это сделать. Все волшебство творит функция Phonon::createPlayer(), которая возвращает указатель на объект класса Phonon::MediaObject. Как мы знаем из вышеизложенного, его одного для воспроизведения звука недостаточно, но функция Phonon::createPlayer() создает вокруг данного объекта всю необходимую оснастку. Первый ее аргумент – категория устройства вывода, которое должен использовать плейер. Мы указываем Phonon::MusicCategory, то есть устройство, предназначенное для воспроизведения музыки. Вторым аргументом должна быть ссылка на объект Phonon::MediaSource, который поставляет объекту Phonon::MediaObject аудиоданные.

Получив объект класса Phonon::MediaObject, мы вызываем его метод play() для воспроизведения аудио. Вместе все перечисленное выглядит так:

Phonon::MediaSource ms(fn);
m_player = Phonon::createPlayer(Phonon::MusicCategory, ms);
m_player->play();

В этом фрагменте переменная fn типа QString содержит имя аудиофайла, предназначенного для воспроизведения, а m_player представляет собой указатель на объект Phonon::MediaObject.

Это почти все, что требуется для создания плейера. Осталась одна меленькая деталь. Поскольку Phonon использует для передачи данных между компонентами системы DBus, мы должны задать нашему приложению определенное имя с помощью метода setApplicationName() класса QApplication (что мы и делаем в функции main() нашей программы). Для остановки воспроизведения данных в нашем плейере можно воспользоваться методом stop() (а можно просто удалить объект-плейер).

Создание простейшего видеоплейера выглядит ненамного сложнее. Класс Phonon::VideoPlayer представляет собой виджет, который умеет воспроизводить видео. Его можно создать так:

videoPlayer = new Phonon::VideoPlayer(Phonon::VideoCategory,parent);

Здесь parent – указатель на родительский виджет. Видео запускается на воспроизведение с помощью метода play() объекта Phonon::VideoPlayer. Для загрузки данных пригодится метод load(), аргументом которого является ссылка на объект Phonon::MediaSource, а можно вызвать перегруженный метод play() с тем же аргументом. Источник данных для воспроизведения аудио и видео может быть указан также с помощью объектов класса QUrl. Обратите внимание на то, что объект класса Phonon::VideoPlayer заботится о выводе не только изображения, но и звука.

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

Можно также реализовать простейшие списки воспроизведения. На уровне программы они представляются объектами QList; их элементы – объекты Phonon::MediaSource или QUrl (но не оба типа сразу, что не допускается самим синтаксисом C++).

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

Построение графов

В состав Qt SDK входит программа qmusicplayer, которая представляет собой пример использования Phonon для создания аудиоплейера. В проигрывателе qmusicplayer мультимедиа-граф создается явным образом. Граф состоит из двух элементов: объекта-источника Phonon::MediaObject и объекта-стока Phonon::AudioOutput:
audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory,this);
mediaObject = new Phonon::MediaObject(this);

Для создания связи между двумя объектами используется функция Phonon::createPath():

Phonon::createPath(mediaObject, audioOutput);

У этой функции есть два аргумента-указателя на объекты Phonon::MediaNode: первый задает источник данных, второй – их приемник. После создания графа работа с ним выполняется так же, как и в рассмотренном выше случае. Помимо явного представления графа, в программе qmusicplayer есть еще один новый момент – использование объектов классов Phonon::SeekSlider и Phonon::VolumeSlider. Класс Phonon::SeekSlider представляет собой специальный виджет–ползунок, который одновременно управляет позицией воспроизведения в открытом файле и указывает текущую позицию (ни один современный мультимедиа-плейер не обходится без подобного элемента). Класс Phonon::VolumeSlider реализует, как вы, конечно, догадались, виджет-регулятор громкости. Объект Phonon::SeekSlider связывается с источником данных, поскольку именно он управляет навигацией в мультимедиа-файле. Для этой цели используется метод setMediaObject():

seekSlider = new Phonon::SeekSlider(this);
seekSlider->setMediaObject(mediaObject);

В свою очередь, Phonon::VolumeSlider связывается со стоком с помощью метода setAudioOutput():

volumeSlider = new Phonon::VolumeSlider(this);
volumeSlider->setAudioOutput(audioOutput);

Рис. 3 Рис. 3. Воспроизведение видео. «Каждый компонент-процессор отвечает только за один эффект.»

Поскольку объекты классов Phonon::VolumeSlider и Phonon::SeekSlider являются виджетами, для них необходимо выполнить все те действия, который обычно производятся для настройки виджетов (то есть определить геометрию и указать параметры компоновки).

Простой граф программы qmusicplayer можно усложнить, добавив в него компонент-процессор. Последние, как мы знаем, преобразуют данные, добавляя к ним эффекты. Каждый компонент-процессор, добавленный в граф, отвечает только за один эффект. Для добавления компонента-процессора между источником данных и стоком нам понадобится объект, представляющий связь между компонентами графа. Его-то и возвращает функция Phonon::createPath():

Phonon::Path audioPath;
audioPath = Phonon::createPath(&mediaObject, &audioOutput);

Обратите внимание, что функция возвращает сам объект, а не ссылку и не указатель. Для добавления процессора мы используем метод insertEffect() класса Phonon::Path. Он существует в нескольких перегруженных вариантах, в каждом из которых, помимо прочего, передается объект-процессор класса Phonon::Effect. Метод insertEffect() можно вызвать несколько раз подряд, добавив, таким образом, несколько компонентов-процессоров последовательно. Метод removeEffect() удаляет указанный процессор из графа.

Qt ничего не знает о том, какие именно эффекты доступны в вашей системе – с точки зрения библиотеки все они представляются одним и тем же объектом класса Phonon::Effect.

Чтобы создать его, нужно получить описание требуемого эффекта. Перечень описаний всех доступных эффектов в виде списка QList <Phonon::EffectDescription> возвращает метод Phonon::BackendCapabilities::availableAudioEffects(). Каждый объект класса Phonon::EffectDescription содержит описание определенного эффекта. Метод name() этого класса возвращает описание эффекта на человеческом языке, и оно является единственным идентификатором, позволяющим определить тип аудиоэффекта.

Иначе говоря, выбирать необходимый эффект должен пользователь, а не программа. Для этого можно создать виджет «раскрывающийся список» и заполнить его описаниями эффектов:

QList<Phonon::EffectDescription> availableEffects =
Phonon::BackendCapabilities::availableAudioEffects();
foreach(Phonon::EffectDescription desc, availableEffects) {
 ui->audioEffectsCombo->addItem(desc.name());
}

Теперь, если пользователь выберет элемент из списка, мы можем создать соответствующий объект-процессор и добавить его в граф:

int currentIndex = ui->audioEffectsCombo->currentIndex();
Phonon::EffectDescription chosenDesc =
availableEffects[currentIndex – 1];
Phonon::Effect * newEffect = new Phonon::Effect(chosenDesc);
audioPath.insertEffect(newEffect);

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

Аналогично строится граф для видеоплейера (пример qmediaplayer из Qt SDK). Хотя граф для видеоплейера должен включать в себя два стока, на практике его построение ненамного сложнее. Программа qmediaplayer из набора примеров Qt SDK демонстрирует создание графа для одновременного воспроизведения видео и звука. Чтобы связать два компонента-стока с источником Phonon::MediaObject, мы дважды вызываем функцию Phonon::createPath(). Важно отметить, что при создании компонента-стока для вывода звука в видеоплейере указывается категория Phonon::VideoCategory. В созданном таким образом графе потоки видео- и аудиоданных синхронизированы.

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

Полезные дополнения

Рис. 4 Рис. 4. Перечисление устройств вывода звука.

Программа qmusicplayer использует еще один объект класса Phonon::MediaObject (переменная metaInformationResolver). С помощью свойства metadata() этого объекта программа получает информацию о тэгах файлов, загруженных в список воспроизведения (для этой цели нельзя использовать объект mediaObject, так как получение подобных сведений не должно зависеть от состояния воспроизводимого файла). Метод metadata() возвращает контейнер, содержащий пары «ИМЯ–ЗНАЧЕНИЕ». Phonon использует одинаковые имена тэгов для всех аудиоформатов: они соответствуют определенным для Ogg Vorbis, так что при работе с другими аудиофайлами тэги будут приводиться в этот формат.

Любая серьезная мультимедиа-программа должна предоставлять пользователю возможность выбора систем вывода данных. У Phonon есть своя система описания аудиоустройств, основанная на парадигме «модель–вид–контроллер». Для создания модели данных с описаниями аудиоустройств можно использовать и следующую тяжеловесную конструкцию:

Phonon::ObjectDescriptionModel<Phonon::AudioOutputDevice
Type> *model =new Phonon::ObjectDescriptionModel<Phonon::
AudioOutputDeviceType>();

В качестве параметра шаблона используется перечисление (enum) Phonon::ObjectDescriptionType. На данный момент оно может принимать два значения: Phonon::AudioOutputDeviceType – для описания устройств вывода звука и Phonon::EffectType – для описания эффектов. Получив объект-модель, мы связываем его с данными:

model->setModelData(Phonon::BackendCapabilities::
availableAudioOutputDevices());

Теперь модель и данные можно связать с объектом, отвечающим за представление, с помощью метода setModel() соответствующего объекта. Список объектов, представляющих сами устройства, можно получить с помощью той же функции, которая предоставляет данные для модели – Phonon::BackendCapabilities::availableAudioDevices(). Она возвращает список объектов Phonon::AudioOutputDevice, элементы которого содержат описания устройств вывода. Для установки выбранного устройства используется метод setOutputDevice() класса Phonon::AudioOutput. Как и в случае с эффектами, выбор устройства осуществляется пользователем на основе описания, составленного на естественном языке. На уровне программы Phonon мало что может сообщить нам о параметрах выбранного устройства.

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

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию