Wave mapper что это

Wave mapper что это

Александр Тарасенко, Санкт-Петербург

Содержание:


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

Для недовольных и ленивых: в следующем разделе будет приведен и рассмотрен код работающей программы. А посвящен будет следующий раздел работе с 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;

После создания буфера, его можно воспроизвести. Но для начала необходимо заполнить его данными. Чуть ниже мы рассмотрим процедуру заполнения буфера данными, которую мы на самом деле уже вызывали под именем OnBufferLost. Но перед этим скажем пару слов о «потере буфера». Некоторые методы интерфейса IDirectSoundBuffer возвращают значение DSERR_BUFFERLOST, означающее, что память, выделенная под аудиоданные, была освобождена. Это можно сказать штатная ситуация. Буфера «теряются» вместе с потерей фокуса вашим приложением. Кроме того, буфер может утеряться при нехватке ресурсов (оперативной памяти) системе. После потери буфера его необходимо восстановить (для этого существует специальный метод – IDirectSoundBuffer::Restore() ) и заново заполнить корректными данными. Поэтому рассмотрим процедуру инициализации буфера данными на примере его восстановления:

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);
>

Эта функция выполняет следующие действия:

А теперь, когда у нас есть буфер, заполненный данными, самое время его запустить на воспроизведение! Сделать это не сложно: достаточно вызвать метод IDirectSoundBuffer::Play. Воспроизведение буфера займет некоторое время. Если ваша программа должна быть извещена об окончании воспроизведения, мы должны об этом позаботиться. Библиотека DirectSound предоставляет специальный интерфейс –IDirectSoundNotify – для реализации механизма извещения. На проигрываемом файле можно установить несколько точек, при достижении которых будет сгенерировано извещение. В качестве механизма извещения используются события (Events). Следующий фрагмент кода иллюстрирует, как использовать механизм извещений от DirectSound:

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);

//Фиксируем аудиобуфер – получаем виртуальный адрес захваченных данных
PUCHAR pCapBuf;
DWORD dwCapBufSize;
pDSCBuffer->Lock(0, 0, (LPVOID*)&pCapBuf, &dwCapBufSize, NULL, 0,
DSCBLOCK_ENTIREBUFFER );

//Незабываем разблокировать зафиксированный ранее буфер
pDSCBuffer->Unlock( (LPVOID)pSrcBuf, dwSrcBufSize, NULL, 0 );

//И в заключении освобождаем объекты
pDSCBuffer->Release();
pDSoundCapture->Release();

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

Как одновременно захватить и воспроизвести поток?

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

Источник

Низкоуровневое программирование звука в Windows

Уведомления, передаваемые программе звуковой подсистемой

По желанию программы звуковая подсистема может использовать три варианта уведомлений: установку объекта программного события (event), вызов заданной программной функции (callback) либо посылку сообщения заданному окну или задаче (thread). В первом варианте программа получает информацию лишь о самом факте некоторого события в звуковой подсистеме и сама должна выяснить, что именно произошло; во втором и третьем вариантах передаются код события и уточняющая информация.

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

Уведомление установкой программного события

Звуковая подсистема переводит в установленное состояние (set event) заданный объект события. В частности, объект события будет находиться в установленном состоянии сразу после возврата из функций Open и Close, так что до передачи драйверу первого звукового буфера объект события необходимо сбросить.

Уведомление посылкой сообщения окну или задаче

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

Устройство успешно открыто

Устройство успешно закрыто

Завершена запись звукового буфера для устройства ввода

Завершено воспроизведение звукового буфера для устройства вывода

Сообщения MM_WxM_OPEN и MM_WxM_CLOSE посылаются устройствам обоих типов, а MM_WIM_DATA и MM_WOM_DONE — только устройствам ввода или вывода соответственно.

Во всех сообщениях параметр wParam передает ключ устройства, породившего событие. В сообщениях DATA/DONE параметр lParam передает указатель заголовка возвращаемого звукового буфера.

Сообщения звуковой подсистемы не требуют возврата значения обрабатывающей их функцией.

Уведомление вызовом программной функции

Звуковая подсистема вызывает заданную программную функцию, передавая в ее аргументах код и параметры события. Прототип вызываемой функции имеет следующий вид:

Handle — ключ звукового устройства. Имеет тип HWAVEIN или HWAVEOUT; допустимо использование универсального типа HWAVE.

Msg — код события. Константы для кодов событий имеют те же имена, что и константы кодов сообщений для окон/задач, но без префикса MM_ (WIM_OPEN, WOM_DONE и т.п.). Фактически сейчас они определяются в MMSYSTEM.H как эквивалентные константам с префиксом MM_, однако в будущем на это рассчитывать не стоит.

Instance — 32-разрядное информационное слово, указанное программой при открытии устройства. Звуковая подсистема никак не использует это значение, а лишь передает его при каждом вызове функции.

Param1, Param2 — параметры события. Для событий OPEN и CLOSE значение Param1 равно нулю; для событий DATA и DONE этот параметр передает указатель заголовка возвращаемого звукового буфера. Значение Param2 в текущей реализации всегда равно нулю.

Функция может вызываться в контексте обработчика прерывания, поэтому безопасно может использовать лишь ограниченный набор функций Windows: EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, timeSetEvent. Обращение к другим системным функциям, как и к функциям звуковой подсистемы, может вызвать непредсказуемые последствия.

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

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

Набор интерфейсных функций звуковых устройств

В дальнейшем мы будем придерживаться универсальной системы именования функций, указывая лишь смысловую часть имени и опуская префикс, содержащий тип и «ориентацию» устройства. Например, говоря о функции GetDevCaps, мы будем подразумевать две функции — waveInGetDevCaps и waveOutGetDevCaps, объясняя только их различия для устройств ввода и вывода. Это потребует от читателя «конструирования» полного имени функции в каждом конкретном случае, однако позволит сделать описание более широким и систематическим. В прототипе функции префикс будет обозначаться последовательностью «xxx».

Первым параметром большинства функций указывается ключ (handle) открытого звукового устройства, имеющий тип HWAVEIN или HWAVEOUT; в прототипе его тип обозначается HWAVEx. Как уже говорилось, ключи звуковых устройств можно хранить в переменных совместимого типа HWAVE.

Перечень интерфейсных функций

Запрос количества устройств

Запрос параметров и возможностей устройства

Открытие устройства

Закрытие устройства

Подготовка (фиксация в памяти) звукового буфера

Освобождение (снятие фиксации) звукового буфера

Передача очередного буфера драйверу устройства

Остановка записи/воспроизведения

Запуск записи/воспроизведения

SetVolume / GetVolume

Установка/запрос громкости воспроизведения

SetPitch / GetPitch

Установка/запрос высоты тона при воспроизведении

SetPlaybackRate / GetPlaybackRate

Установка/запрос скорости воспроизведения

Запрос номера устройства по ключу

Запрос текста сообщения об ошибке по коду

Передача драйверу нестандартного сообщения

Значения, возвращаемые интерфейсными функциями

За редким исключением, все функции звукового интерфейса возвращают результат типа MMRESULT, эквивалентный типу UINT. Значение MMSYSERR_NOERROR, в текущей реализации равное нулю, означает успешное выполнение функции, любое другое значение указывает на ошибку. Константы для кодов ошибок имеют префиксы MMSYSERR_ (общая ошибка мультимедийной подсистемы) и WAVERR_ (ошибка драйвера Wave-устройства):

MMSYSERR_BADDEVICEID

Недопустимый номер устройства

MMSYSERR_NOTENABLED

Драйвер не активизирован

MMSYSERR_ALLOCATED

Устройство занято другим приложением

MMSYSERR_INVALHANDLE

Недопустимый ключ открытого устройства

Драйвер отсутствует

Недостаточно памяти

MMSYSERR_NOTSUPPORTED

Запрошенная функция не поддерживается

MMSYSERR_BADERRNUM

Код ошибки вне допустимого диапазона

MMSYSERR_INVALFLAG

MMSYSERR_INVALPARAM

Недопустимый параметр

MMSYSERR_HANDLEBUSY

Над ключом выполняется операция от другой задачи

Неопределенная ошибка

MMSYSERR_NODRIVERCB

Драйвер не выполнил уведомления (callback)

Неверный или неподдерживаемый формат потока

WAVERR_STILLPLAYING

Идет запись или воспроизведение

Буфер не подготовлен

Устройство работает только в синхронном режиме

GetNumDevs — запрос количества устройств

Возвращает количество установленных в системе устройств ввода или вывода.

GetDevCaps — запрос параметров и возможностей устройств

Служит для определения параметров и возможностей устройства.

DevId — номер устройства начиная с нуля, либо ключ ранее открытого устройства, либо константа WAVE_MAPPER. В последнем случае возвращаются параметры стандартного системного устройства.

Caps — указатель структуры типа WAVEINCAPS или WAVEOUTCAPS (имеются специальные типы LPWAVEINCAPS и LPWAVEOUTCAPS).

CapsSize — размер структуры в байтах.

При успешном завершении функция заполняет поля переданной указателем структуры параметрами устройства. Если были запрошены параметры Wave Mapper, то в качестве имени устройства возвращается название службы переназначения.

DevId — номер устройства начиная с нуля, либо ключ ранее открытого устройства, либо значение WAVE_MAPPER. В последнем случае службой переназначения выбирается устройство, поддерживающее заданный формат, причем поиск начинается со стандартного системного устройства.

Format — указатель структуры типа WAVEFORMATEX, описывающей требуемый формат потока.

Callback — объект, которому будут передаваться уведомления драйвера о выполнении запрошенных операций. Задается ключом (handle) окна или события, указателем функции либо идентификатором задачи (thread id).

Instance — 32-разрядное информационное слово, которое будет передаваться драйвером в параметрах вызова функции уведомления. Например, при разработке универсального интерфейса со звуковыми устройствами это может быть указатель описателя устройства (структуры или объекта класса).

OpenFlags — флаги режимов открывания и работы устройства:

Драйвер не будет уведомлять программу о выполнении операций. Этот режим используется по умолчанию

Параметр Callback является ключом объекта события (event handle)

Параметр Callback является идентификатором задачи (thread id)

Параметр Callback является ключом окна (window handle)

Параметр Callback является указателем функции

Режим опроса формата. Драйвер только проверяет, может ли указанное устройство быть открыто с запрошенным форматом и в заданных режимах, и возвращает соответствующий код результата. В этом режиме параметр ForHandle может быть нулевым (NULL)

WAVE_FORMAT_DIRECT

Запрещает Wave Mapper и ACM принимать участие в преобразовании формата потока. Весь обмен данными производится только между драйвером и приложением

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

Разрешает Wave Mapper и ACM вмешиваться в обмен звуковыми данными между программой и драйвером устройства

В случае успешного открытия устройства (если не был задан флаг опроса формата) звуковая подсистема возвращает в переменную, на которую ссылается указатель ForHandle, ключ (handle) открытого устройства.

Устройства ввода открываются в режиме «стоп», и передача драйверу звуковых буферов не приводит к автоматическому запуску записи — для этого необходимо вызвать функцию Start. Устройства вывода открываются сразу в режиме воспроизведения, и при передаче драйверу первого же звукового буфера автоматически начинается его проигрывание.

При завершении работы с устройством его необходимо закрыть функцией Close, в противном случае открытое устройство может «зависнуть». В отличие от файловой, звуковая подсистема в Windows гораздо более чувствительна к ошибкам и не всегда в состоянии отследить завершение программы, чтобы аварийно закрыть звуковые устройства.

Close — закрытие устройства

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

PrepareHeader — подготовка буфера и его заголовка к передаче драйверу

Hdr — указатель заголовка звукового буфера.

HSize — размер структуры заголовка.

Подготавливает звуковой буфер к передаче драйверу. Обычно подготовка заключается в фиксации буфера в памяти, чтобы во время внепроцессорной передачи (DMA) он не оказался вытесненным (откачанным) на диск. В заголовке подготовленного буфера звуковой подсистемой устанавливается флаг WHDR_PREPARED.

Перед вызовом функции в заголовке буфера должны быть заполнены поля lpData, dwBufferLength, dwFlags.

Для уже подготовленного буфера функция не выполняет никаких действий и завершается успешно.

UnprepareHeader — отмена подготовительных действий для буфера

Hdr — указатель заголовка звукового буфера.

HSize — размер структуры заголовка.

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

При успешном выполнении функции в заголовке буфера сбрасывается флаг WHDR_PREPARED.

Для неподготовленного буфера функция не выполняет никаких действий и завершается успешно.

Write/AddBuffer — передача звукового буфера драйверу

Hdr — указатель заголовка звукового буфера.

HSize — размер структуры заголовка.

Передает звуковой буфер драйверу для воспроизведения (Write) или для записи (AddBuffer). Буфер должен быть подготовлен функцией Prepare, иначе драйвер откажется его принять.

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

Завершив обработку очередного буфера, драйвер изымает его из очереди, сбрасывает флаг WHDR_INQUEUE, затем устанавливает флаг WHDR_DONE, после чего выполняет уведомление приложения, если это было запрошено при открытии устройства. Затем драйвер продолжает обработку следующего буфера из очереди.

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

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

Stop/Pause — остановка записи/воспроизведения

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

При остановленном потоке функция не выполняет никаких действий и завершается успешно.

Start/Restart — запуск записи/воспроизведения

При активном потоке функция не выполняет никаких действий и завершается успешно.

Reset — уничтожение (сброс) потока

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

При отсутствии буферов в очереди функция не выполняет никаких действий и завершается успешно.

BreakLoop — прерывание текущего цикла

Сбрасывает режим цикла воспроизведения, если он установлен. Текущий проход цикла проигрывается до конца, далее воспроизведение продолжается линейно, без возврата к первому буферу цикла. Группа буферов цикла возвращается программе по мере завершения воспроизведения каждого из них.

При остановленном потоке или отсутствии цикла функция не выполняет никаких действий и завершается успешно.

GetPosition — запрос текущей позиции потока

Time — указатель структуры типа MMTIME. В поле wType должен быть установлен код единиц, в которых запрашивается позиция.

TSize — размер структуры в байтах.

Возвращает текущую позицию потока, заполняя поля переданной структуры в соответствии со значением поля wType. Если драйвер не в состоянии вернуть позицию в требуемых единицах, он по своему усмотрению устанавливает значение поля wType и заполняет структуру в выбранных им единицах. Обычно в таком случае позиция возвращается в терминах байтов (TIME_BYTES) или звуковых блоков (TIME_SAMPLES).

SetVolume — установка громкости воспроизведения

Volume — громкость по левому и правому каналу. Младшее слово задает громкость левого канала, старшее — правого. Значение 0xFFFF задает максимальную громкость, 0 — минимальную. Для адаптеров, не поддерживающих независимую регулировку громкости по каналам, младшее слово задает громкость в обоих каналах тракта.

Функция устанавливает выходной уровень воспроизводимого сигнала. Несмотря на то, что функцией допускается 65 536 уровней громкости, большинство адаптеров поддерживает лишь от 8 до 256 уровней. В таких случаях значимыми являются только от трех до восьми старших разрядов значения громкости, младшие разряды игнорируются. Такая трактовка позволяет использовать одну и ту же шкалу громкости, изменяя лишь степень ступенчатости регулировки.

Функция поддерживается только адаптерами, в свойствах которых установлен флаг WAVECAPS_VOLUME. Раздельная регулировка по каналам поддерживается только при наличии флага WAVECAPC_LRVOLUME.

GetVolume — запрос текущей громкости воспроизведения

ForVolume — указатель переменной типа DWORD, в которую заносятся текущие уровни громкости.

Функция опрашивает текущий установленный уровень выходного сигнала. Трактовка переменной, на которую ссылается указатель ForVolume, аналогична используемой в функции SetVolume.

Multiplier = (DWORD)(f * 0x10000)

Функции изменяют высоту тона или скорость воспроизведения потока, не изменяя частоты дискретизации, на которой воспроизводится поток. Значение множителя должно быть положительным. По умолчанию установлен множитель 1.0, что означает воспроизведение потока с естественной высотой и скоростью.

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

Эту пару функций поддерживают далеко не все звуковые адаптеры; как правило, она реализуется на специализированных сигнальных процессорах (DSP). Технически проще всего реализуется изменение скорости, для чего в точках между имеющимися отсчетами путем интерполяции вычисляются промежуточные отсчеты, следующие друг за другом чаще или реже, которые и поступают на схему ЦАП. Изменение высоты требует гораздо более сложных вычислений: фрагменты потока разлагаются в ряд Фурье, образуя спектр звука, затем спектр сдвигается в сторону высоких или низких частот, после чего из измененного спектра снова формируется фрагмент нового звукового потока.

Для адаптеров, поддерживающих изменение высоты и/или скорости, функция GetDevCaps устанавливает флаги WAVECAPS_PITCH и WAVECAPS_PLAYBACKRATE соответственно.

GetPitch/GetPlaybackRate — запрос высоты тона/скорости воспроизведения

Функция опрашивает текущие установки множителя высоты тона или скорости воспроизведения потока. Интерпретация переменных, на которые ссылаются указатели ForPitch/ForRate, — как в функциях SetPitch/SetPlaybackRate.

GetID — запрос номера устройства по ключу

ForID — указатель переменной типа UINT, в которую заносится номер устройства.

Функция определяет номер устройства, при открытии которого системой был возвращен заданный ключ. В том случае, если при открывании была использована служба переназначения (значение WAVE_MAPPER вместо номера или флаг WAVE_MAPPED), функция возвращает значение WAVE_MAPPER.

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

Error — код ошибки, возвращенный одной из интерфейсных функций;

Text — указатель текстового буфера (массива типа char);

TextSize — размер текстового буфера в байтах.

Функция заносит в заданный буфер текстовое описание ошибки с заданным кодом. Записанный текст завершается нулевым байтом. Если буфер недостаточно велик, то конец текста обрезается; нулевой байт записывается в буфер в любом случае. Размер буфера, способного вместить любое сообщение об ошибке, определяется константой MAXERRORLENGTH.

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

Msg — код передаваемого сообщения.

P1, P2 — параметры сообщения.

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

Недостатки звуковой подсистемы MME

В Windows 95/98 подсистема MME и ее драйверы так и остались 16-разрядными, как и в Windows 3.x. Из-за этого каждое обращение к звуковому драйверу из Win32–приложения сопровождается двойной сменой режима исполнения (thunking), что, увы, приводит к дополнительным накладным расходам, доходящим до единиц миллисекунд на процессорах Celeron-366. Кроме того, многие драйверы ограничивают частоту обновления кольцевого буфера, через который идет обмен между компьютером и адаптером, до нескольких десятков раз в секунду, отчего в процессе передачи звука возникает отставание (latency). У драйверов для адаптеров ISA это отставание может достигать десятков миллисекунд, у драйверов для адаптеров PCI оно обычно ограничивается единицами миллисекунд.

Для более оперативного вывода звука, особенно с модификацией его в реальном времени, Microsoft разработан более новый интерфейс — DirectSound. Этот интерфейс призван «приблизить» аппаратуру адаптера к прикладной программе и позволяет ей практически напрямую записывать звук в системный кольцевой буфер, сводя максимальные задержки к единицам миллисекунд для любого адаптера. При работе с DirectSound программа обращается непосредственно к 32-разрядному системному драйверу адаптера (VxD), минуя переключения между 32- и 16-разрядным режимом исполнения.

В целях эффективной работы интерфейс DirectSound должен поддерживаться системным драйвером адаптера. Для устройств, драйверы которых не поддерживают DirectSound, Windows эмулирует новый интерфейс «поверх» обычного MME–драйвера, но в этом случае все задержки даже возрастают из-за накладных расходов на эмуляцию.

К сожалению, Microsoft разработала спецификацию расширения DirectSound для звуковых VxD только в части воспроизведения звука, действуя прежде всего в интересах производителей игр. Запись звука через DirectSound до сих пор ведется путем эмуляции поверх MME.

Надо сказать, что звуковая подсистема Windows 3.x и 95/98, равно как и подсистема удаленного доступа к сети (RAS), обладает низкой устойчивостью к ошибкам. Это чаще всего проявляется в том, что при аварийном завершении программы, открывшей звуковые устройства и работающей с ними, система не выполняет корректного закрытия (cleanup) используемых устройств. В результате этого в ряде случаев после такого аварийного завершения может потребоваться перезагрузка, а до тех пор незакрытые устройства будут недоступны другим приложениям. Кроме того, 16-разрядные подсистемы защищены от ошибок гораздо меньше 32-разрядных, так что серьезные ошибки в звуковых программах могут приводить к сбоям и «зависаниям» всей системы Windows.

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

Пример программы, использующей интерфейс MME

В качестве иллюстрации приводится программа, реализующая в реальном времени эффект задержки (delay). Суть эффекта состоит в сложении исходного звукового сигнала с его копией, задержанной во времени на небольшую величину (единицы-сотни миллисекунд). Задержка на величину до 15-20 мс воспринимается на слух, как «дробление» источника звука; на этом принципе основано создание хорового эффекта. Задержка на величину 20-50 мс воспринимается как реверберация (ощущение объема), а большие величины задержки — как обычное эхо.

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

Программа реализована на языке C++. Фактически от C++ в ней использованы лишь общие расширения (определение переменных в заголовках циклов, использование имен структур в качестве имен типов и т.п.), в остальном же можно считать, что в ней использовался обычный язык ANSI C.

Разработка программы выполнялась в среде MS VC++ 4.2. Использован только стандартный интерфейс Windows, без каких-либо расширений из среды разработки.

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

Для управления циркуляцией буферов создается отдельная рабочая задача (worker thread), которой присваивается максимальное приращение приоритета. Звуковые устройства открываются в режиме уведомления рабочей задачи.

Вследствие буферизации выводимый программой звук несколько отстает от исходного. Общее время буферизации и количество звуковых буферов задается в секции параметров программы. Для непрерывного переноса звука количество буферов не должно быть меньше двух; приемлемая стабильность достигается уже при использовании трех-четырех буферов.

Для работы программы необходим полнодуплексный звуковой адаптер, допускающий одновременную работу своего АЦП и ЦАП. Большинство современных адаптеров удовлетворяет этому условию.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *