Воспроизведение звуковых данных
Для воспроизведения звуковых данных на низком уровне после определения возможностей устройства вывода необходимо открыть устройство. Это можно сделать с помощью функции 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 |
Поля 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
Очередь данного устройства еще содержит блоки для проигрывания