Мультимедиа для Windows

         

Воспроизведение звуковых данных


Для воспроизведения звуковых данных на низком уровне после определения возможностей устройства вывода необходимо открыть устройство. Это можно сделать с помощью функции waveOutOpen .

Функция waveOutOpen

UINT waveOutOpen( LPHWAVEOUT lphWaveOut, // указатель на идентификатор устройства UINT wDeviceID, // номер открываемого устройства LPWAVEFORMAT lpFormat, // указатель на структуру WAVEFORMAT DWORD dwCallback, // адрес функции обратного вызова // или идентификатор окна DWORD dwCallbackInstance, // данные для функции обратного вызова DWORD dwFlags); // режим открытия устройства

Параметры функции:

lphWaveOut

Дальний указатель на переменную типа HWAVEOUT . В эту переменную будет записан идентификатор устройства вывода, который необходим для выполнения всех операций с устройством. Функция waveOutOpen может быть использована для определения возможности воспроизведения звуковых данных заданного формата (в том числе нестандартного), в этом случае параметр lphWaveOut может иметь значение NULL. Дополнительно в параметре dwFlags следует установить флаг WAVE_FORMAT_QUERY

wDeviceID

Через параметр wDeviceID приложение должно передать функции waveOutOpen номер устройства вывода, которое оно собирается открыть или константу WAVE_MAPPER , определенную в файле mmsystem.h.

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

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

lpFormat

Через параметр lpFormat приложение должно передать функции waveOutOpen адрес заполненной структуры WAVEFORMAT . Эта структура и указатели на нее описаны в файле mmsystem.h:

typedef struct waveformat_tag { WORD wFormatTag; // тип формата WORD nChannels; // количество каналов (моно или стерео) DWORD nSamplesPerSec; // частота дискретизации DWORD nAvgBytesPerSec; // скорость потока данных WORD nBlockAlign; // выравнивание блока данных } WAVEFORMAT; typedef WAVEFORMAT *PWAVEFORMAT; typedef WAVEFORMAT NEAR *NPWAVEFORMAT; typedef WAVEFORMAT FAR *LPWAVEFORMAT;


Мы уже рассказывали вам об этой структуре в разделе, посвященном формату wav-файлов. Там вы сможете найти подробное описание полей структуры

dwCallback



Через параметр dwCallback вы можете передать функции waveOutOpen адрес функции обратного вызова. Эту функцию будет вызывать драйвер устройства вывода при возникновении событий, имеющих отношение к проигрыванию блока данных. При использовании функции обратного вызова в параметре dwFlags следует установить флаг CALLBACK_FUNCTION .

Неудобство использования функции обратного вызова заключается в том, что она должна располагаться в фиксированном сегменте dll-библиотеки, так как вызов функции выполняется во время обработки прерывания. Кроме того, если функция обратного вызова использует какие-либо данные, то для их хранения следует либо использовать память из фиксированного сегмента данных, либо заказывать ее из глобальной области памяти с параметрами GMEM_MOVEABLE и GMEM_SHARE с последующей фиксацией при помощи функций GlobalLock и GlobalPageLock. Функция обратного вызова не может использовать никакие функции программного интерфейса Windows за исключением функции PostMessage и функций из dll-библиотеки mmsystem.dll, имеющих отношение к службе времени.

Другой более простой способ извещения приложения о возникновении события заключается в посылке сообщений функции окна. Для этого параметр dwCallback должен содержать идентификатор окна. Кроме этого, в параметре dwFlags следует установить флаг CALLBACK_WINDOW

dwCallbackInstance

Идентификатор данных, который передается в функцию обратного вызова. Не используется совместно с флагом CALLBACK_WINDOW

dwFlags

Вы можете указывать в этом поле следующие флаги:

Флаг Описание
WAVE_FORMAT_QUERY Функция waveOutOpen вызывается только для проверки возможности использования формата звуковых данных, определенного в структуре WAVEFORMAT, адрес которой передается через параметр lpFormat. Этим способом вы можете проверить, способно ли устройство работать с нестандартным форматом, например, с нестандартной частотой дискретизации
WAVE_ALLOWSYNC Этот флаг необходимо использовать для открытия синхронного устройства вывода, во время работы которого все приложения блокируются
CALLBACK_WINDOW Для извещения о наступлении событий используется окно, идентификатор которого передается через параметр dwCallback
CALLBACK_FUNCTION Для извещения о наступлении событий используется функция обратного вызова, адрес которой передается через параметр dwCallback
<


Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_BADDEVICEID

Указан неправильный номер устройства

MMSYSERR_ALLOCATED

Это устройство уже открыто

MMSYSERR_NOMEM

Для выполнения операции не хватает памяти

WAVERR_BADFORMAT

Указанный формат звуковых данных не поддерживается драйвером устройства вывода

WAVERR_SYNC

Была выполнена попытка открыть синхронное устройство вывода без использования флага WAVE_ALLOWSYNC

Как правило, при проигрывании wav-файлов приложение вызывает функцию waveOutOpen два раза. В первый раз она вызывается для проверки возможности проигрывания звуковых данных заданного формата:

if(waveOutOpen(NULL, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt, NULL, 0L, WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC)) { // Формат не поддерживается }

Если указанный формат поддерживается драйвером, приложение может открыть устройство вывода, например, следующим образом:

rc = waveOutOpen(&hWaveOut, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt, (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);

Такая методика позволяет определить возможность работы с нестандартными форматами.

Что же касается структуры WAVEFORMAT, то проще всего заполнить ее непосредственно из заголовка проигрываемого wav-файла, как это мы сделали в приложении WAVE (см. ниже).

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

Блоки данных, передаваемые драйверу, должны быть заказаны как глобальные с флагами GMEM_MOVEABLE и GMEM_SHARE. Вы можете заказать один такой блок и переписать в него содержимое wav-файла (как мы это сделали в приложении WAVE), либо использовать очередь или массив блоков, отдавая блоки драйверу по мере необходимости.



Перед тем как отдать блок драйверу, его надо подготовить при помощи функции waveOutPrepareHeader .

Функция waveOutPrepareHeader

UINT waveOutPrepareHeader( HWAVEOUT hWaveOut, // идентификатор устройства LPWAVEHDR lpWaveOutHdr, // указатель на структуру WAVEHDR UINT wSize); // размер структуры WAVEHDR

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpWaveOutHdr

Через параметр lpWaveOutHdr приложение должно передать функции waveOutPrepareHeader адрес заполненной структуры WAVEHDR , описывающей передаваемый блок данных. Эта структура и указатели на нее описаны в файле mmsystem.h:

typedef struct wavehdr_tag { LPSTR lpData; // адрес блока данных DWORD dwBufferLength; // размер блока данных DWORD dwBytesRecorded; // количество записанных байт // (используется только при записи) DWORD dwUser; // пользовательские данные DWORD dwFlags; // флаги состояния буфера данных DWORD dwLoops; // кратность проигрывания буфера // (используется только при воспроизведении) struct wavehdr_tag far *lpNext; // зарезервировано DWORD reserved; // зарезервировано } WAVEHDR; typedef WAVEHDR *PWAVEHDR; typedef WAVEHDR NEAR *NPWAVEHDR; typedef WAVEHDR FAR *LPWAVEHDR;

Заказав блок памяти функцией GlobalAlloc с флагами GMEM_MOVEABLE и GMEM_SHARE, вы должны зафиксировать его функцией GlobalLock. Полученный в результате фиксирования адрес блока следует записать в поле lpData структуры WAVEHDR. Размер блока нужно записать в поле dwBufferLength.

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



Структура WAVEHDR используется не только для воспроизведения, но и для записи. В этом случае после завершения записи блока в поле dwBytesRecorded будет находиться количество записанных байт звуковых данных. При воспроизведении это поле не используется.

Через поле dwUser приложение может передать функции обратного вызова или обработчику сообщения для данного устройства вывода любую дополнительную информацию

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

Флаги Описание
WHDR_DONE Работа с буфером данных закончена. Он был успешно проигран или записан, после чего драйвер вернул буфер приложению
WHDR_BEGINLOOP Данный буфер является первым в цикле. Флаг используется только при воспроизведении. Если необходимо проиграть в цикле только один блок, он должен быть отмечен и флагом WHDR_BEGINLOOP, и флагом WHDR_ENDLOOP
WHDR_ENDLOOP Данный буфер является последним в цикле. Флаг используется только при воспроизведении
WHDR_PREPARED Буфер подготовлен для воспроизведения функцией waveOutPrepareHeader или для записи функцией waveInPrepareHeader
Приложение может указать драйверу, что блок необходимо проиграть несколько раз подряд. Для этого следует заполнить поле dwLoops, указав в нем, сколько раз нужно проиграть буфер.

Поля lpNext и reserved зарезервированы и не должны использоваться приложением.

wSize

Поле wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE

Указан неправильный идентификатор устройства

MMSYSERR_NOMEM

Для выполнения операции не хватает памяти

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

Функция waveOutWrite

UINT waveOutWrite( HWAVEOUT hWaveOut, // идентификатор устройства LPWAVEHDR lpWaveOutHdr, // указатель на структуру WAVEHDR UINT wSize); // размер структуры WAVEHDR



Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpWaveOutHdr

Через параметр lpWaveOutHdr приложение должно передать функции адрес заполненной структуры WAVEHDR, которая соответствует подготовленному блоку данных

wSize

Поле wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE

Указан неправильный идентификатор устройства

MMSYSERR_UNPREPARED

Переданный блок данных не был подготовлен функцией waveOutPrepareHeader

Сразу после вызова функции waveOutWrite начинается проигрывание блока.

Если блок будет проигран до конца или если проигрывание блока будет остановлено, функция окна, идентификатор которой был указан при открытии устройства через параметр dwCallback, получит сообщение MM_WOM_DONE .

Через параметр wParam сообщения MM_WOM_DONE передается идентификатор устройства, которое было использовано для проигрывания блока. Параметр lParam содержит адрес структуры WAVEHDR, соответствующей проигранному блоку.

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

После того как приложение получило сообщение MM_WOM_DONE, оно должно передать блок функции waveOutUnprepareHeader, затем разблокировать его функцией GlobalUnlock и освободить (если данный блок памяти больше не нужен) функцией GlobalFree.

Приведем формат вызова функции waveOutUnprepareHeader .

Функция waveOutUnprepareHeader

UINT waveOutUnprepareHeader( HWAVEOUT hWaveOut, // идентификатор устройства LPWAVEHDR lpWaveOutHdr, // указатель на структуру WAVEHDR UINT wSize); // размер структуры WAVEHDR

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpWaveOutHdr

Адрес заполненной структуры WAVEHDR, которая соответствует подготовленному блоку данных

wSize

Поле wSize должно содержать размер структуры WAVEHDR



Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE

Указан неправильный идентификатор устройства

MMSYSERR_STILLPLAYING

Указанный блок все еще находится в очереди для проигрывания

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

Функция waveOutClose

UINT waveOutClose( HWAVEOUT hWaveOut); // идентификатор устройства

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE

Указан неправильный идентификатор устройства

MMSYSERR_STILLPLAYING

Очередь данного устройства еще содержит блоки для проигрывания


Содержание раздела