Мультимедиа. Работа со звуком
Цель лекции: Научиться создавать приложения с поддержкой звука
Изначально в IBM-совместимых персональных компьютерах вся работа со звуком заключалась в подаче сигналов с помощью встроенного динамика, а каких-либо средств для воспроизведения музыки вовсе не было, поскольку предполагалось, что персональные компьютеры будут использоваться исключительно в деловой сфере. Однако такое положение не устраивало конечных пользователей, и вскоре появилось довольно большое число звуковых карт, позволявших обеспечить качественное воспроизведение звука.
В наши дни к мультимедийным возможностям программ предъявляются довольно жёсткие требования. К счастью, библиотека Juce, изначально ориентированная именно на создание подобных приложений, предоставляет разработчику множество классов для воспроизведения и записи звука. Детальный их разбор потребовал бы написания отдельной книги, поэтому в данной главе мы коснёмся лишь базовых принципов взаимодействия некоторых из них на простейших примерах, чтобы дать читателю основу для будущей самостоятельной работы с документацией к библиотеке.
Воспроизведение аудио-файлов базовыми методами
За воспроизведение аудио-файлов в библиотеке Juce отвечает класс AudioTransportSource, который создаёт собственную нить (поток) и позволяет начинать проигрывание с произвольной позиции, а также в любой момент останавливать его.
Для того, чтобы объект класса AudioTransportSource мог "понять" данные аудио-файла, последний должен быть прочитан с помощью объекта класса, унаследованного от AudioFormat. Библиотека Juce предоставляет возможность воспроизведения ряда базовых форматов аудио:
- AiffAudioFormat — читает и записывает файлы несжатого аудио, Audio Interchane File Format (*.aif, *.aiff);
- FlacAudioFormat — читает и записывает файлы аудио, сжатого без потерь качества, Free Lossless Audio Codec (*.flac);
- OggVorbisAudioFormat — читает и записывает файлы аудио, сжатого с потерей качества, Ogg Vorbis (*.ogg);
- WavAudioFormat — читает и записывает файлы несжатого аудио, Waveform Audio File Format (*.wav).
Для хранения информации о доступных форматах аудио и принятия решения о том, какой из них использовать для открытия текущего файла, в библиотеке имеется класс AudioFormatManager, который в том числе создаёт чтеца (класс AudioFormatReader), читающего сэмплы из аудио-потока.
Непосредственно управляет устройством воспроизведения звука (звуковой картой) объект класса AudioSourcePlayer, который служит промежуточным звеном между ним и объектом класса AudioTransportSource.
Рассмотрим взаимодействие всех этих классов на примере простого проигрывателя, воспроизводящего вышеперечисленные форматы аудио.
За основу будущего проекта возьмём пример из "Стандартные диалоги" , в котором для открытия файла использовался FilenameComponent. При запуске программы в её ярлыке будет отображаться название аудио-устройства, используемого по умолчанию, а при открытии аудио-файла — его имя ( рис. 19.1).
Все объекты, отвечающие за воспроизведение аудио-файлов, объявим в качестве закрытых членов компонента содержимого ( пример 19.1).
#ifndef _TCentralComponent_h_
#define _TCentralComponent_h_
//--------------------------------------------------
#include "../JuceLibraryCode/JuceHeader.h"
//--------------------------------------------------
class TCentralComponent : public Component,
public ButtonListener,
public ChangeListener
{
public:
TCentralComponent();
~TCentralComponent();
void paint(Graphics&);
void resized();
// Функция, отслеживающая щелчки по кнопке
void buttonClicked(Button*);
void changeListenerCallback(ChangeBroadcaster*);
private:
// Текущий файл для воспроизведения
File CurrentFile;
// Менеджер аудио- и MIDI-устройств
AudioDeviceManager* pAudioDeviceManager;
// Аудио-устройство
AudioIODevice* pAudioDevice;
// Промежуточное звено между источником звука
// и аудио-устройством
AudioSourcePlayer AudioPlayer;
// Источник воспроизведения звука
AudioTransportSource TransportSource;
// Класс для принятия данных от чтеца аудио-потока
AudioFormatReaderSource* pCurrentAudioFileSource;
// Ярлык для вывода названия аудио-устройства
// при запуске программы, а в последующем -
// названия открытого аудио-файла
Label* pNameLabel;
// Кнопка вызова диалога выбора файла
TextButton* pChooseButton;
// Кнопка начала / остановки воспроизведения
TextButton* pPlayButton;
// Компонент выбора файла
FilenameComponent* pFileNameDialog;
// Предотвращает создание копии конструктора и оператора =
TCentralComponent(const TCentralComponent&);
const TCentralComponent& operator= (const TCentralComponent&);
};
//--------------------------------------------------
#endif
Листинг
19.1.
Объявление класса компонента содержимого TCentralComponent (файл TCentralComponent.h)
Их инициализация и установление связей между ними, необходимых для совместной работы, представлены в листинге 19.2 .
Листинг
#include "TCentralComponent.h"
//----------------------------------------------
#define tr(s) String::fromUTF8(s)
//----------------------------------------------
TCentralComponent::TCentralComponent() : Component("Central Component"),
pNameLabel(0),
pChooseButton(0)
{
pNameLabel = new Label(L"Name Label", tr("Выберите файл..."));
pNameLabel->setFont(Font(15.0000f, Font::bold));
pNameLabel->setJustificationType(Justification::centred);
pNameLabel->setEditable(false, false, false);
pNameLabel->setColour(Label::textColourId, Colours::black);
pNameLabel->setColour(Label::backgroundColourId, Colours::azure);
pNameLabel->setColour(Label::outlineColourId, Colours::black);
addAndMakeVisible(pNameLabel);
pChooseButton = new TextButton(L"Choose Button");
pChooseButton->setButtonText(tr("Выбрать..."));
pChooseButton->addListener(this);
addAndMakeVisible(pChooseButton);
pPlayButton = new TextButton(L"Play Button");
pPlayButton->setButtonText(tr("Играть"));
pPlayButton->setColour(TextButton::buttonColourId, Colours::lightgreen);
pPlayButton->addListener(this);
addAndMakeVisible(pPlayButton);
// Файл для воспроизведения ещё не открыт, аудио-поток пока отсутствует
pCurrentAudioFileSource = 0;
LastOpenedFile = File::getCurrentWorkingDirectory();
// Создаём пустой менеджер аудио-устройств
pAudioDeviceManager = new AudioDeviceManager();
// Инициируем менеджер аудио-устройств
String sError = pAudioDeviceManager->initialise(1, 2, 0, true);
// Сохраняем текущее аудио-устройство в переменной
pAudioDevice = pAudioDeviceManager->getCurrentAudioDevice();
// Если устройство отсутствует, выводим сообщение об ошибке
if(!sError.isEmpty())
{
AlertWindow::showMessageBox(
AlertWindow::WarningIcon,
tr("Ошибка воспроизведения"),
(tr("Не удалось открыть аудио-устройство:\n\n") += sError),
tr("Принять"), 0
);
}
// Устанавливаем связи между менеджером устройств,
// AudioSourcePlayer и AudioTransportSource
pAudioDeviceManager->addAudioCallback(& AudioPlayer);
TransportSource.addChangeListener(this);
AudioPlayer.setSource(& TransportSource);
// Если есть доступное аудио-устройство...
if(pAudioDevice != 0)
{
// ...выводим его название на ярлык программы
String sName = pAudioDevice->getName();
pNameLabel->setText(sName, false);
}
else
{
pNameLabel->setText(tr("Нет устройства!"), false);
}
setSize(400, 150);
// Создаём компонент для выбора файла
pFileNameDialog = new FilenameComponent(
String::empty,
CurrentFile,
true,
false,
false,
"*.wav;*.aif;*.aiff;*.ogg;*.flac",
String::empty,
tr("Выберите файл...")
);
File SearchDir = SearchDir.getSpecialLocation(File::userMusicDirectory);
pFileNameDialog->setDefaultBrowseTarget(SearchDir);
// Задаём его размер (иначе его не будет видно)
pFileNameDialog->setSize(250, 25);
}
Листинг
19.2.
Реализация конструктора класса компонента содержимого TCentralComponent (файл TCentralComponent.cpp)
В конструкторе класса компонента содержимого мы создаём и инициализируем менеджер аудио-устройств.
Метод const String AudioDeviceManager::initialise(int numInputChannelsNeeded, int numOutputChannelsNeeded, const XmlElement* savedState, bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName = String::empty, const AudioDeviceSetup* preferredSetupOptions = 0) пытается открыть все доступные для использования аудио-устройства, начиная с устройства по умолчанию. Возвращаемая строка содержит сообщение об ошибке, если что-то пошло не так. В том случае, если открытие аудио-устройств прошло успешно, возвращается пустая строка.
Метод AudioIODevice* AudioDeviceManager::getCurrentAudioDevice() const throw() возвращает указатель на текущее аудио-устройство. Мы сохраняем его в члене класса компонента содержимого pAudioDevice для последующих обращений.
Затем мы устанавливаем связь между менеджером аудио-устройств и AudioSourcePlayer посредством вызова метода void AudioDeviceManager::addAudioCallback(AudioIODeviceCallback* newCallback). Удалить связь позволяет метод void AudioDeviceManager::removeAudioCallback(AudioIODeviceCallback* callback).
Завершаем установку всех необходимых соединений установкой TransportSource в качестве источника воспроизведения для AudioSourcePlayer. Достигается это вызовом метода void AudioSourcePlayer::setSource(AudioSource* newSource).
Заметим, что в нашей программе используются настройки текущего аудио-устройства по умолчанию. Однако библиотека Juce включает структуру, принадлежащую классу AudioDeviceManager— AudioDeviceSetup — хранящую набор свойств текущего аудио-устройства и дающую возможность пользователю их изменять на этапе выполнения программы. Пример использования AudioDeviceSetup вы можете найти в демонстрационном приложении Juce (выберите в меню Demo > Audio). Посмотреть реализацию примера можно, перейдя в папку <каталог Juce>/juce/extras/JuceDemo/Source/demos и открыв файл AudioDemoSetupPage.cpp.
При первом запуске нашей программы на её ярлыке мы отображаем имя используемого аудио-устройства. Для его получения воспользуемся методом const String& AudioIODevice::getName() const throw().
