Опубликован: 13.07.2012 | Доступ: свободный | Студентов: 461 / 9 | Оценка: 5.00 / 5.00 | Длительность: 18:06:00
Специальности: Программист
Лекция 19:

Мультимедиа. Работа со звуком

< Лекция 18 || Лекция 19: 12345 || Лекция 20 >
Аннотация: В этой лекции вы познакомитесь со взаимодействием классов, необходимых для воспроизведения основных форматов музыкальных файлов (AIFF, FLAC, OGG VORBIS, WAV), а также для файлов MIDI.

Цель лекции: Научиться создавать приложения с поддержкой звука

Изначально в 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. Работа программы, демонстрирующей воспроизведение базовых типов аудио-файлов

Все объекты, отвечающие за воспроизведение аудио-файлов, объявим в качестве закрытых членов компонента содержимого ( пример 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().

< Лекция 18 || Лекция 19: 12345 || Лекция 20 >