Главная » Программирование звука » Воспроизведение звука в Windows

0

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

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

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

Листинг 6.1. Программа winplayr.h

/*

* Player class for Win32

*/

#ifndef WIN_PLAYER_H_INCLUDED

#define WIN_PLAYER_H_INCLUDED

#include "audio.h"

#include "aplayer.h"

#include <windows.h>

#include <mmsystem.h>

#define winBufferSize 10000 // Количество отсчетов  в буфере.

class WinPlayer : public AbstractPlayer {

private:

HWAVEOUT _device;       // Аудиоустройство  Windows, которое

// будем  открывать.

volatile bool _paused;  // Истина, если устройство

// в состоянии паузы.

int_sampleWidth;        // Разрядность выходных  данных.

int SelectDevice(void); // Открывает  подходящее  устройство.

// Разрешаем  функциям обратного

// вызова  обращаться  к  функциям-членам.

friend void CALLBACK WaveOutCallback(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParaml, DWORD

dwParam2);

// Определенные  выше  функции  обратного

// вызова это  просто упаковщики,

// обращающиеся  к  этому методу.

void NextBuff(WAVEHDR *);

public:

WinPlayer(AudioAbstract *a): AbstractPlayer(a) {

_device = 0;

_paused = true;

_sampleWidth = 0;

};

~WinPlayer() {};

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

};

#endif

Объявив  WaveOutCallback другом  класса,  я  разрешил  этой  функции  обращаться  к  защищенным  данным  класса  WinPlayer (обратите  внимание  на  то,  что функция WaveOutCallback не является членом класса WinPlayer).

Листинг 6.2. Программа winplayr.cpp

#include <windows.h>

#include <mmsystem.h>

#include <iostream>

#include "aplayer.h"

#include "winplayr.h"

Воспроизведение

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

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

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

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

Листинг 6.3. Реализация класса WinPalyer

void WinPlayer::Play(void) {

if (SelectDevice()) return;    // Открываем подходящее

// устройство.

waveOutPause(_device);         // Пока   не   воспроизводим.

_paused = true;

InitializeQueue(128*1024L);    // Выделяем  128 Кб  на   очередь.

WAVEHDR waveHdr[2];

for (int i=0; i<2; i ++ ) {

waveHdr[i].dwBufferLength    // Размер в байтах.

= winBufferSize * _sampleWidth/8;

waveHdr[i].dwFlags = 0; waveHdr[i].dwLoops = 0; waveHdr[i].lpData

= reinterpret_cast<LPSTR>(

new BYTE[waveHdr[i].dwBufferLength *

Channels()]);

waveOutPrepareHeader(_device,&waveHdr[i],sizeof(waveHdr[i])); NextBuff(&waveHdr[i]);       // Заполнение  и  копирование

// буфера  в  выходные данные.

}

_paused = false;

// Ждем, пока воспроизведение

// не   прекратится, и  оба  буфера

// не   освободятся.

waveOutRestart(_device);       // Теперь  начинаем

// воспроизведение.

while(!_endOfQueue              // Очередь  пуста?

|| ((waveHdr[0].dwFlags & WHDR_DONE) == 0) // Данные

// в  буферах закончились?

|| ((waveHdr[1].dwFlags & WHDR_DONE) == 0)) { FillQueue();                 // Конец  заполнения очереди.

if (_paused) {              // Если   поток сервера  находится

// в состоянии паузы,

// перезапускаем  его.

_paused = false;

// Вывод   звука перезапущен.

cerr << "Sound output restarted.\n";

waveOutRestart(_device);

}

Sleep(50 /* ms */);          // Циклические повторения

// приблизительно  20 раз

// в секунду.

}

MMRESULT err = waveOutClose(_device);

while (err == WAVERR_STILLPLAYING) { // Если   воспроизведение

// еще не  закончилось…

Sleep(250);                       // Ждем чуть-чуть…

waveOutClose(_device);            // Пробуем снова…

};

for(int i1=0; i1<2; i1++) {

waveOutUnprepareHeader(_device,&waveHdr[i1] ,sizeof(waveHdr[i1]));

delete [] waveHdr[i1].lpData;

}

}

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

По теме:

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