wave mapper что это
waveOutOpen function (mmeapi.h)
The waveOutOpen function opens the given waveform-audio output device for playback.
Syntax
Parameters
Pointer to a buffer that receives a handle identifying the open waveform-audio output device. Use the handle to identify the device when calling other waveform-audio output functions. This parameter might be NULL if the WAVE_FORMAT_QUERY flag is specified for fdwOpen.
Identifier of the waveform-audio output device to open. It can be either a device identifier or a handle of an open waveform-audio input device. You can also use the following flag instead of a device identifier:
Value | Meaning |
---|---|
WAVE_MAPPER | The function selects a waveform-audio output device capable of playing the given format. |
Pointer to a WAVEFORMATEX structure that identifies the format of the waveform-audio data to be sent to the device. You can free this structure immediately after passing it to waveOutOpen.
Specifies the callback mechanism. The value must be one of the following:
User-instance data passed to the callback mechanism. This parameter is not used with the window callback mechanism.
Flags for opening the device. The following values are defined.
Value | Meaning |
---|---|
CALLBACK_EVENT | The dwCallback parameter is an event handle. |
CALLBACK_FUNCTION | The dwCallback parameter is a callback procedure address. |
CALLBACK_NULL | No callback mechanism. This is the default setting. |
CALLBACK_THREAD | The dwCallback parameter is a thread identifier. |
CALLBACK_WINDOW | The dwCallback parameter is a window handle. |
WAVE_ALLOWSYNC | If this flag is specified, a synchronous waveform-audio device can be opened. If this flag is not specified while opening a synchronous driver, the device will fail to open. |
WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE | If this flag is specified and the uDeviceID parameter is WAVE_MAPPER, the function opens the default communication device. This flag applies only when uDeviceID equals WAVE_MAPPER. |
WAVE_FORMAT_DIRECT | If this flag is specified, the ACM driver does not perform conversions on the audio data. |
WAVE_FORMAT_QUERY | If this flag is specified, waveOutOpen queries the device to determine if it supports the given format, but the device is not actually opened. |
WAVE_MAPPED | If this flag is specified, the uDeviceID parameter specifies a waveform-audio device to be mapped to by the wave mapper. |
Return value
Returns MMSYSERR_NOERROR if successful or an error otherwise. Possible error values include the following.
Return code | Description |
---|---|
MMSYSERR_ALLOCATED | Specified resource is already allocated. |
MMSYSERR_BADDEVICEID | Specified device identifier is out of range. |
MMSYSERR_NODRIVER | No device driver is present. |
MMSYSERR_NOMEM | Unable to allocate or lock memory. |
WAVERR_BADFORMAT | Attempted to open with an unsupported waveform-audio format. |
WAVERR_SYNC | The device is synchronous but waveOutOpen was called without using the WAVE_ALLOWSYNC flag. |
Remarks
Use the waveOutGetNumDevs function to determine the number of waveform-audio output devices present in the system. If the value specified by the uDeviceID parameter is a device identifier, it can vary from zero to one less than the number of devices present. The WAVE_MAPPER constant can also be used as a device identifier.
The structure pointed to by pwfx can be extended to include type-specific information for certain data formats. For example, for PCM data, an extra UINT is added to specify the number of bits per sample. Use the PCMWAVEFORMAT structure in this case. For all other waveform-audio formats, use the WAVEFORMATEX structure to specify the length of the additional data.
If you choose to have a window or thread receive callback information, the following messages are sent to the window procedure function to indicate the progress of waveform-audio output: MM_WOM_OPEN, MM_WOM_CLOSE, and MM_WOM_DONE.
Callback Mechanism
If fdwOpen contains the CALLBACK_FUNCTION flag, dwCallback is a pointer to a callback function. For the function signature, see waveOutProc. The uMsg parameter of the callback indicates the progress of the audio output:
If fdwOpen contains the CALLBACK_EVENT flag, dwCallback is a handle to an event. The event is signaled whenever the state of the waveform buffer changes. The application can use WaitForSingleObject or WaitForMultipleObjects to wait for the event. When the event is signaled, you can get the current state of the waveform buffer by checking the dwFlags member of the WAVEHDR structure. (See waveOutPrepareHeader.)
If fdwOpen contains the CALLBACK_NULL flag, dwCallback must be NULL. In that case, no callback mechanism is used.
Передача звука по сети. Прототип VoIP телефона
Posted by bullvinkle under Журнал, Статьи
Данная статья будет полезна начинающим программистам, которые никогда не имели дело со звуком и его передачей по сети. Смысл этой статьи заключается в изучении и применении: WINAPI функций ввода и вывода звука WaveIn() и WaveOut() в среде разработки Delphi 7.0, самих компонентов TIdUDPServerSocket и TIdUDPClientSocket. Данные компоненты можно найти в библиотеке Indy, которая в свою очередь находится в свободном распространении на просторах Internet’а.
Передача звука по сети. Прототип VoIP телефона
Уколов Александр Владимирович
Если вы никогда не программировали в Delphi 7.0, версиями ниже или выше, если вы вообще никогда не программировали на подобных ЯВУ, то эта статья не для вас.
Введение
К написанию программы для передачи звука по сети меня побудило желание получить-таки зачет по УИРС (это что-то вроде НИР – научно исследовательской работы студента) у преподавателя, ведущего мой основной предмет, и являющимся моим дипломным руководителем. Перед тем как сесть за Delphi и начать набирать код, предварительно, я изучил кучу литературы в бумажном и электронном виде о принципах упаковки звука и его передачи, о функциях ввода и вывода в самом Delphi и многом другом [1, 2]. Именно ввод и вывод заставил меня задуматься о сложности преподносимого материала. Для человека, никогда не имевшего с этим дело, разобраться в этой области очень сложно, имея под рукой множество кода без комментариев с непонятными процедурами и функциями непонятного WIN API, а если эти процедуры и функции описаны, то это описание предназначено не для начинающих программистов, приходилось все додумывать самому: смотреть подноготную каждой процедуры, и методом проб и ошибок идти медленно, но уверенно к вершине созидания. Но в конечном итоге я добился поставленной цели. И сейчас, разложив всю информацию, предоставленную мне в кашеобразном виде, по полочкам, я готов поделиться своими знаниями с вами, дорогие читатели! Итак, приступим…
Средства разработки
Прежде всего, для работы нам понадобится:
. IDE Delphi версии 7.0 и выше
. Библиотека Indy для Delphi 7.0 (TIdUDPServerSocket и TIdUDPClientSocket) [3, 4]
. колонки и микрофон
Сразу же перейдем к практической части. По мере появления неизвестных функций и процедур в листинге, они будут незамедлительно описываться…
Практическая часть. Создадим клиента
Передача звука в моей программе осуществляется с клиента на сервер, т.е. в одном направлении. Клиент может только писать и передавать, сервер – только принимать и воспроизводить. Первым делом начнем писать клиент.
Для этого, создадим новый проект в Дельфи, разместим на форме кнопку TButton и изменим ее свойство Caption на «начать отправку». После чего, разместим на форме компонент из библиотеки Indy TIdUDPClientSocket (см. рисунок 1):
Рис. 1. Режим проектирования формы тестового клиента
Так как тестирование программы будет проводиться на локальном компьютере, то изменим значение свойства Host компонента TIdUDPClientSocket на «localhost». Далее я просто перечислю свойства компонента и их значения, что должны быть установлены: Active (false), BroadCastEnabled (false), BufferSize (8192), Name (IdUDPClient1), Port (0), ReceiveTimeOut (-2), Tag (0).
Примечание: описание некоторых вышеуказанных свойств выходит за рамки данной статьи.
Теперь, нажимаем двойным щелчком по вынесенному на форму компоненту TButton и появится обработчик события Button1Click(), где Button1 – это значение свойства Name данного компонента. В этом обработчике пишем или копируем следующий код:
Вы спросите, а что же такое waveInPrepareHeader? Это функция, выполняющая подготовку буфера для операции загрузки данных. Общий вид:
HWaveIn – идентификатор открытого устройства
LpWaveInHdr – адрес структуры WaveHdr
type TWaveHdr = record
lpData – адрес буфера для загрузки данных
dwBufferLength – длина буфера в байтах
dwBytesRecorded – для режима загрузки данных определяет количество загруженных в буфер байт
dwUser – пользовательские данные
dwFlags – флаги. Могут иметь следующие значения: WHDR_DONE устанавливается
драйвером при завершении загрузки буфера данными
WHDR_PREPARED – устанавливается системой. Показывает готовность буфера к загрузке данных
WHDR_INQUEUE – устанавливается системой, когда буфер установлен в очередь
dwLoops – используется только при воспроизведении. При записи звука всегда 0
uSize – размер структуры WaveHdr в байтах
Функция waveInPrepareHeader вызывается только один раз для каждого устанавливаемого в очередь загрузки буфера. Что такое waveInAddBuffer()? Функция waveInAddBuffer() ставит в очередь на загрузку данными буфер памяти. Когда буфер заполнен, система уведомляет об этом приложение:
hWaveIn – идентификатор открытого Waveform audio устройства ввода
lpWaveInHdr – адрес структуры TWaveHdr
uSize – размер WaveHdr в байтах
Что такое waveInStart(), waveInStop(), waveInClose()? Общий вид записи таков:
function waveInStart(hWaveIn: HWAVEIN): MMRESULT; stdcall;
waveInStop(), waveInClose() имеют совершенно одинаковый параметр – как и WaveInStart(), которую описывать не имеет смысла, ибо и так понятно, что она начинает считывать данные с устройства ввода, а вот waveInClose() закрывает устройство для записи, и его снова придется открывать с помощью WaveInOpen(), но об этом ниже… А вот waveInStop(), ставит запись как бы на паузу, и нам не надо повторно использовать WaveInOpen().
Что такое waveInUnprepareHeader? Функция аналогичная waveInPrepareHeader(), однако она возвращает выделенную память на буфер, т.е. как бы «уничтожая» его.
Как узнать, что уже можно передавать данные?
Мы разобрали некоторые функции WIN API, относящиеся к вводу данных. Не устали? Нет? Тогда двигаемся дальше! Создадим собственную процедуру для определения завершения передачи данных в блок памяти посредством WaveInAddBuffer(). А выглядит она так:
В этой процедуре используются уже известные вам функции, по этому второй раз описывать их не будем. Пишем её сразу после строки <$R *.dfm>. А описываем эту процедуру в разделе private класса TForm1 как:
procedure OnWaveMessage(var msg:TMessage); message MM_WIM_DATA;
Эта процедура будет выполняться каждый раз как только передача данных в буфер будет завершена и система сгенерирует сообщение WIM_DATA. Заполним обработчик события формы OnClose():
И конечно же, заполним обработчик события формы OnCreate():
Что же такое WaveInOpen()?
Функция waveInOpen() открывает имеющееся устройство ввода Waveform Audio для оцифровки сигнала. Типичная ее структура выглядит следующим образом:
lphWaveIn – указатель на идентификатор открытого Waveform audio устройства. Идентификатор используется после того, как устройство открыто, в других функциях Waveform audio;
uDeviceID – номер открываемого устройства (см. waveInGetNumDevs). Это может быть также идентификатор уже открытого ранее устройства. Вы можете использовать значение WAVE_MAPPER для того, чтобы функция автоматически выбрала совместимое с требуемым форматом данных устройство;
В этой структуре значения полей следующие:
wFormatTag – формат Waveform audio. Мы будем использовать значение WAVE_FORMAT_PCM
(это означает импульсно-кодовая модуляция) другие возможные значения
смотрите в заголовочном файле MMREG.H;
nChannels – количество каналов. Обычно 1 (моно) или 2(стерео);
nSamplesPerSec – частота дискретизации. Для формата PCM – в классическом смысле, т.е.
количество выборок в секунду. Согласно теореме отсчетов должна вдвое
превышать частоту оцифровываемого сигнала. Обычно находится в диапазоне от
8000 до 44100 выборок в секунду;
nAvgBytesPerSec – средняя скорость передачи данных. Для PCM равна nSamplesPerSec*nBlockAlign;
nBlockAlign – для PCM равен (nChannels*wBitsPerSample)/8;
wBitsPerSample – количество бит в одной выборке. Для PCM равно 8 или 16;
cbSize – равно 0. Подробности в Microsoft Multimedia Programmer’s Reference;
dwCallback – адрес callback-функции, идентификатор окна или потока, вызываемого при
dwInstance – пользовательский параметр в callback-механизме. Сам по себе не используется
dwFlags – флаги для открываемого устройства:CALLBACK_EVENT dwCallback-параметр –
код сообщения (an event handle);
CALLBACK_FUNCTION dwCallback – параметр – адрес процедуры-обработчика
CALLBACK_NULL dwCallback – параметр не используется
CALLBACK_THREAD dwCallback – параметр – идентификатор потока команд;
CALLBACK_WINDOW dwCallback – параметр – идентификатор окна
WAVE_FORMAT_DIRECT если указан этот флаг, ACM-драйвер не выполняет преобразование данных
WAVE_FORMAT_QUERY функция запрашивает устройство для определения
поддерживает ли оно указанный формат, но не открывает его
Мы использовали callback функцию в OnWaveMessage(). В последнюю очередь я опишу переменные, которые использовались:
Так же для работы программы необходимо добавить модуль MMSystem в раздел uses. Клиент готов! Как видите, не так страшен черт, как его малюют! Перед тем как перейти к написанию сервера, я бы вам настоятельно рекомендовал бы покопаться в генофонде всех выше описанных функций и самостоятельно глубже разобраться в том, как они устроены. Так для более углубленного изучения, советую переворошить содержимое таких компонентов из серии ACM как AcmIn, AcmOut. Только самообучением можно чего-нибудь добиться.
А что же сервер?
С чистой перед клиентом совестью, можем приступить к написанию сервера! Возможно, эта процедура покажется вам более сложной, но, разобравшись в ней, вы поймете, что это не так. Единственное, что работать мы будем не с одним буфером, а с восьмью, для удобства воспроизведения звука. В один записываем, воспроизводим, очищаем, готовим, записываем и т.д. по очереди каждый из восьми. Так же будет рассмотрена работа с флагами (dwflags) и приема потока данных (TMemoryStream) на сервер. Приступим, нетерпеливые мои!
Как обычно, создадим новый проект и вынесем на форму компонент TMemo (name=memo1) (опять же-таки я использовал его в целях определения получения потока данных, перегоняя его в шестнадцатиричный формат), кнопку TButton и IdUDPServerSocket (см. рисунок 2):
Рис. 2. Режим проектирования формы тестового сервера
Пожалуй, начнем с простого. Напишем ниже приведенный код в обработчике события OnClose() формы:
Далее займемся обработчиком события OnClick() кнопки TButton1 (см. код):
Теперь напишем процедуру, которую мы будем использовать для воспроизведения принятого звука:
Процедура разобрана, осталось ей воспользоваться… Как это осуществить? Все просто, достаточно в обработчике события OnUDPRead() idUDPServerSocket-a написать следующий код:
И не забыть при создании формы проинициализировать наши аудиоустройства. Для этого в обработчике OnCreate() формы запишем:
Уважаемые читатели, здесь я пишу без комментариев только для того, что дать вам возможность самим додуматься, что здесь к чему, это не так сложно, тем более, что вы это уже все знаете (мы с вами выше подробно разбирали эти аналогичные функции ввода и вывода и работы с сокетами).
Далее осталось описать переменные и константы:
Я не стал описывать процедуру перегонки потока в HEX-формат, так как писал ради передачи данных в TMемо. В конце концов, вы сами запросто можете убрать ненужные строки, относящиеся к ней.
Заключение
Хочу заметить, что размеры буферов сокетов на сервере и клиенте должны быть равны размерам буферов структуры TWaveHdr, иначе вы не получите никаких звуков на выходе, кроме шипения с прерываниями, равными по длительности размеру вашего воспроизводимого буфера. Также для более быстрой реакции на события приема звука используйте меньшие размеры буферов, но и соответственно увеличьте их количество (8-ми вполне хватит). При желании, лучше использовать динамический.
Статья была написана специально для форума Клуба ПРОграммистов www.programmersforum.ru. Исходники тестового проекта (клиента и сервера) прилагаются в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом [5].
Выражаю огромную благодарность человеку, чей ник на вышеуказанном форуме raxp, который активно помогал мне в изучении этого материала кодами и советами.
Ресурсы
. Репозитарий Indy 9: https://svn.atozed.com:444/svn/Indy9 (имя пользователя: Indy-Public-RO)
. Репозитарий Indy 10: https://svn.atozed.com:444/svn/Indy10 (имя пользователя: Indy-Public-RO)
. Обсуждение на форуме разработки прототипа VoIP телефона
Wave mapper что это
Работа со звуковой картой в Windows
Новые статьи
Содержание:
1. Введение
Все современные настольные компьютеры как правило оснащены звуковыми картами и многие разработчики используют звук в своих приложениях. Основными направлениями использования возможностей звуковых карт в программном обеспечении являются:
Одной процедуры дискретизации мало. Для компьютерной обработки звука необходимо получить его цифровое представление. Для этого сигнал квантуют. В отличии от дискретизации при квантовании происходит потеря информации. Эту потерю информации называют шумом квантования. Естественно, что чем больше уровней квантования, тем меньше шум квантования. Считается, что при 16 битном цифрро-аналоговом преобразовании человеческое ухо не слышит шума кванотования.
Вооружившись этими фактами, мы теперь молжем вполне ответственно подойти к выбору частоты дискретизации и количеству уровней квантования сигнала. Заметим, что качественное представление звука требует гораздо больших ресурсов (места на диске, размера буферов в памяти, времени на передачу по сети) и не всегда оправдано. Во многих случаях используется достаточно низкая частота дискретизации. Например, речь, оцифрованная при частоте дискретизации 4кГц, звучит вполне разборчиво.
2. Работа со звуком средствами стандартной мультимедиа библиотеки
Начнем с записи. Для этого на понадобяться следующие функции:
Кроме того понадобятся следующие структуры:
Прежде, чем читать материал далее, читателю настойчиво рекомендуется обратиться к соответствующим разделам SDK, где описаны упомянутые типы.
HWAVEIN hWaveIn;
WAVEFORMATEX WaveFormat;
WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
WaveFormat.nChannels = 1;
WaveFormat.nSamplesPerSec = 16000L;
WaveFormat.nBlockAlign = 2;
WaveFormat.nAvgBytesPerSec = WaveFormat.nSamplesPerSec*WaveFormat.nBlockAlign;
WaveFormat.wBitsPerSample = 16;
WaveFormat.cbSize = 0;
В данном случае будет открыто устройство для записи звука со следующими параметрами:
Если устройство было открыто успешно, можно приступать к записи. Для этого сначала нужно подготовить буфер:
WAVEHDR WaveHdr;
ULONG BufferSize = WaveFormat.nBlockAlign*WaveFormat.nSamplesPerSec*10;
WaveHdr.lpData = malloc(BufferSize);
WaveHdr.dwBufferLength = BufferSize;
В данном случае был подготовлен буфер, достаточный для записи 10с звука. Теперь можно начать запись:
После того, как буфер будет заполнен данными, аудиоустройство вернет буфер приложению, о чем известит его. В нашем случае приложению будет послано сообщение MM_WIM_DATA, параметром которого является указатель на записанный буфер. После записи, необходимо корректно освободить буфер:
По окончанию записи следует закрыть аудиоустройство:
HWAVEOUT hWaveOut;
WAVEFORMATEX WaveFormat;
WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
WaveFormat.nChannels = 1;
WaveFormat.nSamplesPerSec = 16000L;
WaveFormat.nAvgBytesPerSec = 16000L;
WaveFormat.nBlockAlign = 2;
WaveFormat.wBitsPerSample = 16;
WaveFormat.cbSize = 0;
WAVEHDR WaveHdr;
ULONG BufferSize = WaveFormat.nBlockAlign*WaveFormat.nSamplesPerSec*10;
WaveHdr.lpData = malloc(BufferSize);
WaveHdr.dwBufferLength = BufferSize;
//Заполняем буфер WaveHdr.lpData данными
Обработчик сообщения MM_WOM_DONE
waveOutUnprepareHeader(hWaveOut, &WaveHdr, sizeof(WAVEHDR));
free(WaveHdr.lpData);
waveInClose(hWaveIn);
Для недовольных и ленивых: в следующем разделе будет приведен и рассмотрен код работающей программы. А посвящен будет следующий раздел работе с wave файлами.
В заключении этого раздела кратенько познакомимся с довольно полезными функциями:
Назначение этих функций понятно. Но вот использование не так просто. Рекомендую тщательно почесть описание в справке.
Вместо имени файла может буть указан алиас для системного звука (типа щелчка при раскрытии окна). В принципе, этой функцией можно обойтись и не читать следующий раздел. Тем не менее, если вы собираетесь воспроизводить несколько файлов одновременно, следующий раздел будет вам полезен (вместе с разделом, посвященном DirectSound).
3. Работа с wave файлами
Любой файл в формате RIFF имеет один «старший» блок с индификатором «RIFF». Все остальные блоки являются вложенными. Блок RIFF имеет одно дополнительное поле, в котором указан тип хранимых данных.
Для работы с RIFF файлами в мультимедиа библиотеке существуют несколько функций:
Кроме указанных функций, существует несколько других, но их рассмотрение выходит за рамки изложения. Описание всех функций есть в SDK.
Рассмотрим на примере, как работать с RIFF файлами.
Для начала wave-файл (как и любой другой 🙂 ) следует открыть. Мультмедиа функции для работы с файлами имеют практически те же возможности, что и функции базового API. Так функция mmioOpen позволяет с помощью флагов задать режим доступа (совместный или эксклюзивный), отметить открываемый файл как временный и.т.п. Полный список флагов и их описание можно найтив в SDK. Существующий на диске файл можно открыть так:
HMMIO hMmio = mmioOpen(szFileName, NULL, MMIO_READ | MMIO_ALLOCBUF);
Если файл был открыт удачно (дескриптор не нулевой), читаем RIFF заголовок:
MMCKINFO mmCkInfoRiff;
mmCkInfoRiff.fccType = mmioFOURCC(‘W’, ‘A’, ‘V’, ‘E’);
MMRESULT mmRes = mmioDescend(hMmio, &mmCkInfoRiff, NULL, MMIO_FINFRIFF);
Если mmRes = MMSYSERR_NOERROR, значит был удачно открыт RIFF заголовок для аудиопотока (тип WAVE).
Аудиоданные располагаются в таком порядке: блок формата (тип ‘fmt’), непосредственно данные (тип ‘date’). В таком порядке и следует их читать. Для начала прочитаем заголовок блока информации о формате аудиоданных:
MMCKINFO mmCkInfo;
mmCkInfo.ckid = mmioFOURCC(‘f’, ‘m’, ‘t’, ‘ ‘);
mmRes = mmioDescend(hMmio, &mmCkInfo, &mmCkInfoRiff, MMIO_FINDCHUNK);
Если заголовок был прочитан успешно, можно прочитать данные, которые соответствуют структуре WAVEFORMATEX, рассмотренной в предыдущем разделе.
После чтения информации о формате аудиоданных, закрываем блок формата и открываем блок данных:
Теперь остается только прочесть данные. Для этогоследует выделить буфер в оперативной памяти.
LPVOID pBuf = VirtualAlloc(NULL, mmCkInfo.cksize, MEM_COMMIT, PAGE_READWRITE );
if (!pBuf) mmioRead(hMmio, (HPSTR)pBuf, mmCkInfo.cksize);
И еще одно замечание. Функции для работы с файлами в формате RIFF могут оказаться полезными для хранения пользовательских данных, если требуется организовать их хранение в иерархической древовидной стркутуре.
4. Работа со звуком средствами DirectSound
Поговорим не много об архитектуре DirectSound. Эта библиотека предоставляет несколько CОМ объектов, позволяющих работать со звуковой платой. Каждый объект в соответствии с моделью COM доступен через набор предоставляемых интерфейсов. Основными объектами являются:
«устройство воспроизведения», предоставляет интерфейс IDirectSound;
«устройство захвата звука», предоставляет интерфейс IDirectSoundCapture;
Объект «аудиобуфер», предоставляет интерфейс IDirectSoundBuffer для буферов, предназначенных для воспроизведения звука, и IDirectSoundCaptureBuffer для буферов, предназначенных для захвата звука;
Работа с библиотекой DirectSound всегда начинается с создания объекта аудиоустройства. Если в системе установлено различное оборудование, на этом этапе можно выбрать, с каким устройством будет осуществляться работа. Узнать информацию об установленных устройствах можно с помощью функции DirectSoundEnumerate для устройств воспроизведения и DirectSoundCaptureEnumerate для устройств аудиозахвата.
Для каждого аудиоустройства можно создать один или несколько аудиобуферов. Эти буфера используются для воспроизведения, захвата, микширования звуковых потоков.
При работе с устройствами воспроизведения различают первичный и вторичные буфера. Вторичные буфера содержат данные в формате PCM c различными параметрами (частота дискретизации, количество каналов и.т.д). Первичный буфер содержит аудиоданные, воспроизводимые звуковой картой. Перед тем как попасть в первичный буфер, данные из вторичных буферов подвергаются микшированию, что позволяет параллельно воспроизводить несколько аудиопотоков. При микшировании данных производиться автоматическое преобразование форматов данных, содержащихся во вторичных буферах, к установленному формату первичного буфера.
4.1 Как воспроизвести аудиопоток?
Рассмотрим порядок действий при воспроизведении звука средствами DirectSound. Первым делом нужно создать объект аудиоустройства:
HRESULT hRes;
LPDIRECTSOUND pDSound;
if (FAILED( hRes =
DirectSoundCreate(NULL, &pDSound, NULL) ) ) return hRes;
Примечание: с каждой версией DirectX выпускается соответствующий SDK, содержащий кроме всего прочего, последние версии библиотек и соответствующие заголовочные файлы. Для доступа к новым возможностям необходимо использовать последние версии интерфейсов. Обычно они отличаются от базовых цифрой в имени типа интерфейса, указывающей на версию соответствующего интерфейса. Например, IDirectSound8 и соответствующий псевдоним LPDIRECTSOUND8. Мы далее будем использовать только базовые функции и интерфейсы, что сделает наш код независимым от версии DirectX.
При вызове функции DirectSoundCreate первым параметром можно указать GUID (глобально-уникальный идентификатор) требуемого аудиоустройства. Требуемый GUID можно получить, например, с помощью функции DirectSoundEnumerate. Если указать NULL (или псевдоним DSDEVID_DefaultPlayback), будет произведена попытка создать объект, используемый для воспроизведения звука по умолчанию.
if (FAILED(hRes =
pDSound->SetCooperativeLevel(hWnd, DSSCL_NORMAL) ) )
return hRes;
Далее приступим к созданию вторичного аудиобуфера. Для начала зададим его формат:
WAVEFORMATEX waveFmt;
waveFmt.wFormatTag = WAVE_FORMAT_PCM;
waveFmt.nChannels = 1;
waveFmt.nSamplesPerSec = 8000;
waveFmt.wBitsPerSample = 8;
waveFmt.nBlockAlign = (WORD)(waveFmt.wBitsPerSample * waveFmt.nChannels / 8);
waveFmt.nAvgBytesPerSec = waveFmt.nSamplesPerSec * waveFmt.nBlockAlign;
waveFmt.cbSize = 0;
Примечание: если компилятор «ругается» на тип WAVEFORMATEX, включите заголовок
Далее следует заполнить структуру с информацией о создаваемом буфере. В данном случае мы создаем буфер с параметрами, описываемыми структурой waveFmt и размером, достаточным для размещения 4 с звука.
DSBUFFERDESC dsBufDesc;
ZeroMemory(&dsBufDesc, sizeof(dsBufDesc) );
dsBufDesc.dwSize = sizeof(dsBufDesc);
dsBufDesc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY;
dsBufDesc.dwBufferBytes = waveFmt.nAvgBytesPerSec * 4;
dsBufDesc.dwReserved = 0;
dsBufDesc.lpwfxFormat = &waveFmt;
После этих подготовительных операций можно непосредственно создать сам буфер:
LPDIRECTSOUNDBUFFER pDsBuffer;
if (FAILED(hRes =
pDSound->CreateSoundBuffer(&dsBufDesc, &pDsBuffer, NULL) )
) return hRes;
И заполнить его данными:
if (FAILED(hRes =
OnBufferLost(pDsBuffer, 440.0) ) ) return hRes;
HRESULT OnBufferLost(LPDIRECTSOUNDBUFFER lpDSBuffer, float flFreq )
<
HRESULT hRes;
PUCHAR pLockBuf;
DWORD dwBufSize;
WAVEFORMATEX waveFmt;
do hRes = lpDSBuffer->Restore();
while (hRes == DSERR_BUFFERLOST);
if ( FAILED( hRes ) ) return hRes;
for (int i = 0; i Unlock( (LPVOID)pLockBuf, dwBufSize, NULL, 0);
>
Эта функция выполняет следующие действия:
LPDIRECTSOUNDNOTIFY lpDsNotify;
DSBPOSITIONNOTIFY PositionNotify;
//Требуем нужный нам интерфейс IDirectSoundNotify
if (FAILED(
hRes = pDsBuffer->QueryInterface(IID_IDirectSoundNotify,
(LPVOID*)&lpDsNotify))) return hRes;
PositionNotify.dwOffset = DSBPN_OFFSETSTOP;
PositionNotify.hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
hRes = lpDsNotify->SetNotificationPositions(1, &PositionNotify);
lpDsNotify->Release(); //Освобождаем не нужный более интерфейс
Замечание: обратите внимание на флаг, установленный при создании буфера: DSBCAPS_CTRLPOSITIONNOTIFY. Без установки этого флага объект аудиобуфер не вернет указатель на интерфейс IDirectSoundNotify.
Мы задали единственное событие, которое будет установлено при проигрывании всего буфера (на что указывает предопределенный макрос DSBPN_OFFSETSTOP). А вот теперь можно и воспроизвести буфер:
while (pDsBuffer->Play(0, 0, 0 ) == DSERR_BUFFERLOST)
OnBufferLost(pDsBuffer, 440.0);
//Ожидаем окончания воспроизведения буфера
WaitForSingleObject(PositionNotify.hEventNotify, INFINITE);
По окончанию работы культурные люди прибирают за собой:
CloseHandle( PositionNotify.hEventNotify );
pDsBuffer->Release();
pDSound->Release();
4.2 Как воспроизвести несколько аудиопотоков одновременно?
Как уже упоминалось выше, перед тем как попасть в первичный буфер, данные из вторичных буферов подвергаются микшированию. Таким образом, чтобы воспроизвести два звуковых потока параллельно, не нужно никаких дополнительных исхищрений: создаете два (более) аудиобуферов (процедура создания рассмотрена довольно подробно) и воспроизводите их, например, так:
4.3 Как захватить звук?
Процедура захвата звука очень похожа на воспроизведение. Последовательность действий такая:
Вот как это могло бы выглядеть:
//Создаем устройство аудиозахвата
LPDIRECTSOUNDCAPTURE pDSoundCapture;
//Задаем параметры захватываемого потока
WAVEFORMATEX waveFmt;
waveFmt.wFormatTag = WAVE_FORMAT_PCM;
waveFmt.nChannels = 1;
waveFmt.nSamplesPerSec = 8000;
waveFmt.wBitsPerSample = 8;
waveFmt.nBlockAlign = (WORD)
(waveFmt.wBitsPerSample * waveFmt.nChannels / 8);
waveFmt.nAvgBytesPerSec = waveFmt.nSamplesPerSec * waveFmt.nBlockAlign;
waveFmt.cbSize = 0;
//Создаем буфер, достаточный для захвата 4 с звука
DSCBUFFERDESC dscBufDesc;
ZeroMemory(&dscBufDesc, sizeof(dscBufDesc) );
dscBufDesc.dwSize = sizeof(dscBufDesc);
dscBufDesc.dwFlags = 0;
dscBufDesc.dwBufferBytes = waveFmt.nAvgBytesPerSec * 4;
dscBufDesc.dwReserved = 0;
dscBufDesc.lpwfxFormat = &waveFmt;
LPDIRECTSOUNDCAPTUREBUFFER pDSCBuffer;
if ( FAILED (hRes =
pDSoundCapture->CreateCaptureBuffer(&dscBufDesc, &pDSCBuffer,
NULL ) ) ) return hRes;
//Устанавливаем извещение на конец буфера
LPDIRECTSOUNDNOTIFY lpDsNotify;
if (FAILED(
hRes = pDSCBuffer->QueryInterface(IID_IDirectSoundNotify,
(LPVOID*)&lpDsNotify))) return hRes;
DSBPOSITIONNOTIFY PositionNotify;
PositionNotify.dwOffset = DSBPN_OFFSETSTOP;
PositionNotify.hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
hRes = lpDsNotify->SetNotificationPositions(1, &PositionNotify);
lpDsNotify->Release();
//Запускаем процедуру аудиозахвата
pDSCBuffer->Start(0);
//Ждем окончания аудиозахвата
WaitForSingleObject(PositionNotify.hEventNotify, INFINITE);
CloseHandle(PositionNotify.hEventNotify);
//Незабываем разблокировать зафиксированный ранее буфер
pDSCBuffer->Unlock( (LPVOID)pSrcBuf, dwSrcBufSize, NULL, 0 );
//И в заключении освобождаем объекты
pDSCBuffer->Release();
pDSoundCapture->Release();