Главная » Программирование звука » Объекты-проигрыватели

0

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

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

Классы  проигрывателей  отвечают  за  передачу  звуковых  данных  на  громкоговоритель  в  конкретно  заданной  системе.  Их  общие  свойства  описаны  в  классе AbstractPlayer, который мы и будем создавать в этой главе.

Листинг 5.1. Программа aplayer.h

#ifndef ABSTRACTPLAYER_H_INCLUDED

#define ABSTRACTPLAYER_H_INCLUDED

#include "audio.h"

#endif

Проигрыватели: основы

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

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

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

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

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

Независимо  от  того,  каким  именно  методом  вы  воспользуетесь,  вам  понадобится быстро передавать данные системе, как только она их затребует. B классе AbstractPlayer  для  этого  используется  очередь  аудиоданных.  Делается  это  так. Вы  периодически  вызываете  FillQueue,  запрашивающий  данные  у  остальных модулей обработки звука и помещающий их в конец очереди. После этого вызывается  метод  FromQueue,  с  помощью  которого  данные  из  очереди  копируются в произвольную системную структуру данных, используемую вами.

Эта  системная  структура,  безусловно,  может  и  не  хранить  значения  AudioSample.  B  связи  с  этим  в  классе  AbstractPlayer определяется  два  новых  типа данных:  Samplel6,  для  записи  16-битных  отсчетов  и  Sample8  для  8-битных. Метод  FromQueue,  c  помощью  которого  производится  копирование  данных  из очереди, был перегружен так, чтобы возвращать данные в любом из этих двух форматов.

Листинг 5.2. Класс AbstractPlayer

class AbstractPlayer : public AudioAbstract {

protected:

typedef short Sample16;

typedef signed char Sample8;

volatile AudioSample *_queue, *_queueEnd; // Начало конец

// области памяти,

// отведенной

// под   очередь.

volatile AudioSample * volatile _queueFirst;    // Первая

// выборка.

volatile AudioSample * volatile _queueLast;     // Последняя

// выборка.

void InitializeQueue(unsigned long queueSize);  // Создает

// очередь.

void FillQueue(void);                           // Заполняет

// очередь.

long FromQueue(Sample8 *pDest,long bytes); long FromQueue(Sample16 *pDest,long bytes); private:

void DataToQueue(long); // Используется   методом FillQueue.

void DataFromQueue(Sample8 *,long);  // Используется   методом

// FromQueue(Sample8…) . void DataFromQueue(Sample16 *,long); // Используется   методом

// FromQueue(Sample16…).

private:

size_t GetSamples(AudioSample *,size_t) { exit(1); return 0;

};

He  всегда  просто  определить  момент,  когда  звук  уже  закончился.  После  того как  считается  вся  исходная   информация,  данные  будут  сохраняться   в  очереди. Когда  данные  в  очереди  закончатся,  система  может  продолжить  «проигрывать» данные  из  своих  внутренних  буферов.  B  классе  AbstractPlayer используются два  флага,  которые  служат  признаками  того,  что  очередь  опустела  или  закончились данные во внешнем источнике.

Листинг 5.2. Класс AbstractPlayer (продолжение)

protected:

bool _endOfSource;        // Истина, если  из  внешнего источника

// считаны последние  данные.

bool _endOfQueue;         // Истина, если  из  внешнего источника

// считаны последние  данные.

public:

AbstractPlayer(AudioAbstract *a);

~AbstractPlayer() ;

virtual void Play() = 0;  // Собственно воспроизведение  записи.

};

Листинг 5.З. Программа aplayer.cpp

#include "aplayer.h" AbstractPlayer::AbstractPlayer(AudioAbstract *a) : AudioAbstract(a) {

_endOfSource = false;

_endOfQueue = false;

_queue = _queueEnd = _queueFirst = _queueLast = 0;

}

AbstractPlayer::~AbstractPlayer(void) {

if (_queue)

// MS VC++ 5.0 c a n’ t delete a volatile pointer?!?!

// Компилятор  MS VC++ 5.0 не   может  удалить указатель

// типа volatile?!?!

delete [] const_cast<AudioSample *>(_queue);

}

Источник: Кинтцель Т.  Руководство программиста по работе со звуком = A Programmer’s Guide to Sound: Пер. с англ. М.: ДМК Пресс, 2000. 432 с, ил. (Серия «Для программистов»).

По теме:

  • Комментарии