Низкоуровневое программирование звука в Windows
Уведомления, передаваемые программе звуковой подсистемой
По желанию программы звуковая подсистема может использовать три варианта уведомлений: установку объекта программного события (event), вызов заданной программной функции (callback) либо посылку сообщения заданному окну или задаче (thread). В первом варианте программа получает информацию лишь о самом факте некоторого события в звуковой подсистеме и сама должна выяснить, что именно произошло; во втором и третьем вариантах передаются код события и уточняющая информация.
Звуковая подсистема передает программе уведомления о событиях трех видов: успешное открытие устройства, успешное его закрытие и завершение обработки очередного буфера из очереди драйвера. Строго говоря, полностью асинхронным является только последнее событие; первые два возникают сразу же после успешного открытия и закрытия устройства и передаются в программу еще до возврата из соответствующих интерфейсных функций.
Уведомление установкой программного события
Звуковая подсистема переводит в установленное состояние (set event) заданный объект события. В частности, объект события будет находиться в установленном состоянии сразу после возврата из функций Open и Close, так что до передачи драйверу первого звукового буфера объект события необходимо сбросить.
Уведомление посылкой сообщения окну или задаче
Звуковая подсистема посылает заданному окну или задаче сообщение Windows, код которого отражает наступившее событие, передавая в параметрах сообщения ключ открытого звукового устройства и дополнительную информацию о событии. Сообщение посылается асинхронно при помощи функций PostMessage или PostThreadMessage и выбирается из очереди окна или задачи наравне с прочими сообщениями Windows.
Имена констант для кодов сообщений имеют вид MM_WxM_event, где x - тип устройства (буква I — для устройства ввода или O — для устройства вывода), а event — тип события:
OPEN |
Устройство успешно открыто |
CLOSE |
Устройство успешно закрыто |
DATA |
Завершена запись звукового буфера для устройства ввода |
DONE |
Завершено воспроизведение звукового буфера для устройства вывода |
Сообщения MM_WxM_OPEN и MM_WxM_CLOSE посылаются устройствам обоих типов, а MM_WIM_DATA и MM_WOM_DONE — только устройствам ввода или вывода соответственно.
Во всех сообщениях параметр wParam передает ключ устройства, породившего событие. В сообщениях DATA/DONE параметр lParam передает указатель заголовка возвращаемого звукового буфера.
Сообщения звуковой подсистемы не требуют возврата значения обрабатывающей их функцией.
Уведомление вызовом программной функции
Звуковая подсистема вызывает заданную программную функцию, передавая в ее аргументах код и параметры события. Прототип вызываемой функции имеет следующий вид:
void CALLBACK CallbackProc ( HWAVEx Handle, UINT Msg, DWORD Instance, DWORD Param1, DWORD Param2 );
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.
Перечень интерфейсных функций
GetNumDevs |
Запрос количества устройств |
GetDevCaps |
Запрос параметров и возможностей устройства |
Open |
Открытие устройства |
Close |
Закрытие устройства |
Prepare |
Подготовка (фиксация в памяти) звукового буфера |
Unprepare |
Освобождение (снятие фиксации) звукового буфера |
AddBuffer / Write |
Передача очередного буфера драйверу устройства |
Stop / Pause |
Остановка записи/воспроизведения |
Start / Restart |
Запуск записи/воспроизведения |
Reset |
Сброс потока |
SetVolume / GetVolume |
Установка/запрос громкости воспроизведения |
SetPitch / GetPitch |
Установка/запрос высоты тона при воспроизведении |
SetPlaybackRate / GetPlaybackRate |
Установка/запрос скорости воспроизведения |
GetID |
Запрос номера устройства по ключу |
GetErrorText |
Запрос текста сообщения об ошибке по коду |
Message |
Передача драйверу нестандартного сообщения |
Значения, возвращаемые интерфейсными функциями
За редким исключением, все функции звукового интерфейса возвращают результат типа MMRESULT, эквивалентный типу UINT. Значение MMSYSERR_NOERROR, в текущей реализации равное нулю, означает успешное выполнение функции, любое другое значение указывает на ошибку. Константы для кодов ошибок имеют префиксы MMSYSERR_ (общая ошибка мультимедийной подсистемы) и WAVERR_ (ошибка драйвера Wave-устройства):
MMSYSERR_BADDEVICEID |
Недопустимый номер устройства |
MMSYSERR_NOTENABLED |
Драйвер не активизирован |
MMSYSERR_ALLOCATED |
Устройство занято другим приложением |
MMSYSERR_INVALHANDLE |
Недопустимый ключ открытого устройства |
MMSYSERR_NODRIVER |
Драйвер отсутствует |
MMSYSERR_NOMEM |
Недостаточно памяти |
MMSYSERR_NOTSUPPORTED |
Запрошенная функция не поддерживается |
MMSYSERR_BADERRNUM |
Код ошибки вне допустимого диапазона |
MMSYSERR_INVALFLAG |
Недопустимый флаг |
MMSYSERR_INVALPARAM |
Недопустимый параметр |
MMSYSERR_HANDLEBUSY |
Над ключом выполняется операция от другой задачи |
MMSYSERR_ERROR |
Неопределенная ошибка |
MMSYSERR_NODRIVERCB |
Драйвер не выполнил уведомления (callback) |
WAVERR_BADFORMAT |
Неверный или неподдерживаемый формат потока |
WAVERR_STILLPLAYING |
Идет запись или воспроизведение |
WAVERR_UNPREPARED |
Буфер не подготовлен |
WAVERR_SYNC |
Устройство работает только в синхронном режиме |
GetNumDevs — запрос количества устройств
UINT xxxGetNumDevs (void);
Возвращает количество установленных в системе устройств ввода или вывода.
GetDevCaps — запрос параметров и возможностей устройств
MMRESULT xxxGetDevCaps ( UINT DevId, LPWAVExCAPS Caps, UINT CapsSize );
Служит для определения параметров и возможностей устройства.
DevId — номер устройства начиная с нуля, либо ключ ранее открытого устройства, либо константа WAVE_MAPPER. В последнем случае возвращаются параметры стандартного системного устройства.
Caps — указатель структуры типа WAVEINCAPS или WAVEOUTCAPS (имеются специальные типы LPWAVEINCAPS и LPWAVEOUTCAPS).
CapsSize — размер структуры в байтах.
При успешном завершении функция заполняет поля переданной указателем структуры параметрами устройства. Если были запрошены параметры Wave Mapper, то в качестве имени устройства возвращается название службы переназначения.
Open - открывание устройства
MMRESULT xxxOpen ( LPHWAVEx ForHandle, UINT DevId, LPCWAVEFORMATEX Format, DWORD Callback, DWORD Instance, DWORD OpenFlags );
ForHandle — указатель переменной типа HWAVEIN или HWAVEOUT (тип указателя - LPHWAVEIN или LPHWAVEOUT), в которую при успешном завершении операции записывается ключ открытого устройства.
DevId — номер устройства начиная с нуля, либо ключ ранее открытого устройства, либо значение WAVE_MAPPER. В последнем случае службой переназначения выбирается устройство, поддерживающее заданный формат, причем поиск начинается со стандартного системного устройства.
Format — указатель структуры типа WAVEFORMATEX, описывающей требуемый формат потока.
Callback — объект, которому будут передаваться уведомления драйвера о выполнении запрошенных операций. Задается ключом (handle) окна или события, указателем функции либо идентификатором задачи (thread id).
Instance — 32-разрядное информационное слово, которое будет передаваться драйвером в параметрах вызова функции уведомления. Например, при разработке универсального интерфейса со звуковыми устройствами это может быть указатель описателя устройства (структуры или объекта класса).
OpenFlags — флаги режимов открывания и работы устройства:
CALLBACK_NULL |
Драйвер не будет уведомлять программу о выполнении операций. Этот режим используется по умолчанию |
CALLBACK_EVENT |
Параметр Callback является ключом объекта события (event handle) |
CALLBACK_THREAD |
Параметр Callback является идентификатором задачи (thread id) |
CALLBACK_WINDOW |
Параметр Callback является ключом окна (window handle) |
CALLBACK_FUNCTION |
Параметр Callback является указателем функции |
WAVE_FORMAT_QUERY |
Режим опроса формата. Драйвер только проверяет, может ли указанное устройство быть открыто с запрошенным форматом и в заданных режимах, и возвращает соответствующий код результата. В этом режиме параметр ForHandle может быть нулевым (NULL) |
WAVE_FORMAT_DIRECT |
Запрещает Wave Mapper и ACM принимать участие в преобразовании формата потока. Весь обмен данными производится только между драйвером и приложением |
WAVE_ALLOWSYNC |
Разрешает открытие устройства в синхронном режиме. Для полностью синхронных устройств этот флаг должен быть указан обязательно |
WAVE_MAPPED |
Разрешает Wave Mapper и ACM вмешиваться в обмен звуковыми данными между программой и драйвером устройства |
В случае успешного открытия устройства (если не был задан флаг опроса формата) звуковая подсистема возвращает в переменную, на которую ссылается указатель ForHandle, ключ (handle) открытого устройства.
Устройства ввода открываются в режиме «стоп», и передача драйверу звуковых буферов не приводит к автоматическому запуску записи — для этого необходимо вызвать функцию Start. Устройства вывода открываются сразу в режиме воспроизведения, и при передаче драйверу первого же звукового буфера автоматически начинается его проигрывание.
При завершении работы с устройством его необходимо закрыть функцией Close, в противном случае открытое устройство может «зависнуть». В отличие от файловой, звуковая подсистема в Windows гораздо более чувствительна к ошибкам и не всегда в состоянии отследить завершение программы, чтобы аварийно закрыть звуковые устройства.
Close — закрытие устройства
MMRESULT xxxClose (HWAVEx Handle);
Закрывает звуковое устройство. Закрытие допустимо только после завершения обмена данными и возврата драйвером всех переданных ранее буферов, иначе подсистема возвращает код ошибки WAVERR_STILLPLAYING. Поэтому перед вызовом функции необходимо либо дождаться возврата всех буферов в обычном порядке, либо вызвать функцию Reset, в результате чего буферы будут возвращены немедленно.
PrepareHeader — подготовка буфера и его заголовка к передаче драйверу
MMRESULT xxxPrepareHeader (HWAVEx Handle, LPWAVEHDR Hdr, UINT HSize);
Hdr — указатель заголовка звукового буфера.
HSize — размер структуры заголовка.
Подготавливает звуковой буфер к передаче драйверу. Обычно подготовка заключается в фиксации буфера в памяти, чтобы во время внепроцессорной передачи (DMA) он не оказался вытесненным (откачанным) на диск. В заголовке подготовленного буфера звуковой подсистемой устанавливается флаг WHDR_PREPARED.
Перед вызовом функции в заголовке буфера должны быть заполнены поля lpData, dwBufferLength, dwFlags.
Для уже подготовленного буфера функция не выполняет никаких действий и завершается успешно.
UnprepareHeader — отмена подготовительных действий для буфера
MMRESULT xxxUnprepareHeader (HWAVEx Handle, LPWAVEHDR Hdr, UINT HSize);
Hdr — указатель заголовка звукового буфера.
HSize — размер структуры заголовка.
Отменяет подготовительные действия, выполненные ранее функцией PrepareHeader. Обычно эта отмена состоит в снятии режима фиксации буфера в памяти.
При успешном выполнении функции в заголовке буфера сбрасывается флаг WHDR_PREPARED.
Для неподготовленного буфера функция не выполняет никаких действий и завершается успешно.
Write/AddBuffer — передача звукового буфера драйверу
MMRESULT waveOutWrite (HWAVEx Handle, LPWAVEHDR Hdr, UINT HSize); MMRESULT waveInAddBuffer (HWAVEx Handle, LPWAVEHDR Hdr, UINT HSize);
Hdr — указатель заголовка звукового буфера.
HSize — размер структуры заголовка.
Передает звуковой буфер драйверу для воспроизведения (Write) или для записи (AddBuffer). Буфер должен быть подготовлен функцией Prepare, иначе драйвер откажется его принять.
Получив буфер, драйвер сбрасывает в его заголовке флаг WHDR_DONE, включает заголовок во внутреннюю очередь и устанавливает флаг WHDR_INQUEUE. После этого асинхронный драйвер возвращает управление приложению, продолжая параллельную обработку очереди буферов по прерываниям от устройства; синхронный драйвер возвращает управление лишь после завершения обработки буфера.
Завершив обработку очередного буфера, драйвер изымает его из очереди, сбрасывает флаг WHDR_INQUEUE, затем устанавливает флаг WHDR_DONE, после чего выполняет уведомление приложения, если это было запрошено при открытии устройства. Затем драйвер продолжает обработку следующего буфера из очереди.
Приложение не имеет права изменять какие-либо поля заголовка до тех пор, пока обработка буфера драйвером не будет завершена.
Поскольку заголовок буфера имеет только одно поле для связывания в список, повторная передача драйверу буфера, уже помещенного в очередь, приводит к ошибке.
Stop/Pause — остановка записи/воспроизведения
MMRESULT waveInStop (HWAVEx Handle); MMRESULT waveOutPause (HWAVEx Handle);Останавливает запись или воспроизведение в текущей позиции потока. Не до конца заполненный буфер устройства записи немедленно возвращается программе; не полностью проигранный буфер устройства воспроизведения остается в очереди драйвера.
На деле реальная позиция, в которой был остановлен процесс, может несколько отличаться от полученной только что с помощью функции GetPosition.
При остановленном потоке функция не выполняет никаких действий и завершается успешно.
Start/Restart — запуск записи/воспроизведения
MMRESULT waveInStart (HWAVEx Handle); MMRESULT waveOutRestart (HWAVEx Handle);Запускает запись/воспроизведение с текущей позиции потока.
При активном потоке функция не выполняет никаких действий и завершается успешно.
Reset — уничтожение (сброс) потока
MMRESULT xxxReset (HWAVEx Handle);
Уничтожает текущий звуковой поток. Все буферы, находящиеся в очереди драйвера, немедленно возвращаются программе; позиция потока обнуляется.
При отсутствии буферов в очереди функция не выполняет никаких действий и завершается успешно.
BreakLoop — прерывание текущего цикла
MMRESULT waveOutBreakLoop (HWAVEx Handle);
Сбрасывает режим цикла воспроизведения, если он установлен. Текущий проход цикла проигрывается до конца, далее воспроизведение продолжается линейно, без возврата к первому буферу цикла. Группа буферов цикла возвращается программе по мере завершения воспроизведения каждого из них.
При остановленном потоке или отсутствии цикла функция не выполняет никаких действий и завершается успешно.
GetPosition — запрос текущей позиции потока
MMRESULT xxxGetPosition (HWAVEx Handle, LPMMTIME Time, UINT TSize);
Time — указатель структуры типа MMTIME. В поле wType должен быть установлен код единиц, в которых запрашивается позиция.
TSize — размер структуры в байтах.
Возвращает текущую позицию потока, заполняя поля переданной структуры в соответствии со значением поля wType. Если драйвер не в состоянии вернуть позицию в требуемых единицах, он по своему усмотрению устанавливает значение поля wType и заполняет структуру в выбранных им единицах. Обычно в таком случае позиция возвращается в терминах байтов (TIME_BYTES) или звуковых блоков (TIME_SAMPLES).
SetVolume — установка громкости воспроизведения
MMRESULT waveOutSetVolume (HWAVEx Handle, DWORD Volume);
Volume — громкость по левому и правому каналу. Младшее слово задает громкость левого канала, старшее — правого. Значение 0xFFFF задает максимальную громкость, 0 — минимальную. Для адаптеров, не поддерживающих независимую регулировку громкости по каналам, младшее слово задает громкость в обоих каналах тракта.
Функция устанавливает выходной уровень воспроизводимого сигнала. Несмотря на то, что функцией допускается 65 536 уровней громкости, большинство адаптеров поддерживает лишь от 8 до 256 уровней. В таких случаях значимыми являются только от трех до восьми старших разрядов значения громкости, младшие разряды игнорируются. Такая трактовка позволяет использовать одну и ту же шкалу громкости, изменяя лишь степень ступенчатости регулировки.
Функция поддерживается только адаптерами, в свойствах которых установлен флаг WAVECAPS_VOLUME. Раздельная регулировка по каналам поддерживается только при наличии флага WAVECAPC_LRVOLUME.
GetVolume — запрос текущей громкости воспроизведения
MMRESULT waveOutGetVolume (HWAVEx Handle, LPDWORD ForVolume);
ForVolume — указатель переменной типа DWORD, в которую заносятся текущие уровни громкости.
Функция опрашивает текущий установленный уровень выходного сигнала. Трактовка переменной, на которую ссылается указатель ForVolume, аналогична используемой в функции SetVolume.
SetPitch / SetPlaybackRate - установка высоты тона / скорости воспроизведения
MMRESULT waveOutSetPitch (HWAVEx Handle, DWORD Multiplier); MMRESULT waveOutSetPlaybackRate (HWAVEx Handle, DWORD Multiplier);Multiplier — множитель высоты тона / скорости воспроизведения. Старшее слово задает целую часть множителя, младшее — дробную. Если имеется значение множителя f типа double, то преобразовать его в тип DWORD можно по формуле:
Multiplier = (DWORD)(f * 0x10000)
Функции изменяют высоту тона или скорость воспроизведения потока, не изменяя частоты дискретизации, на которой воспроизводится поток. Значение множителя должно быть положительным. По умолчанию установлен множитель 1.0, что означает воспроизведение потока с естественной высотой и скоростью.
При изменении высоты тона все звуки в потоке становятся выше или ниже, однако длительность каждого звука и общее время воспроизведения сохраняются. Изменение скорости воспроизведения подобно изменению скорости ленты в магнитофоне: все звуки становятся выше или ниже, а общее время воспроизведения соответственно уменьшается или увеличивается.
Эту пару функций поддерживают далеко не все звуковые адаптеры; как правило, она реализуется на специализированных сигнальных процессорах (DSP). Технически проще всего реализуется изменение скорости, для чего в точках между имеющимися отсчетами путем интерполяции вычисляются промежуточные отсчеты, следующие друг за другом чаще или реже, которые и поступают на схему ЦАП. Изменение высоты требует гораздо более сложных вычислений: фрагменты потока разлагаются в ряд Фурье, образуя спектр звука, затем спектр сдвигается в сторону высоких или низких частот, после чего из измененного спектра снова формируется фрагмент нового звукового потока.
Для адаптеров, поддерживающих изменение высоты и/или скорости, функция GetDevCaps устанавливает флаги WAVECAPS_PITCH и WAVECAPS_PLAYBACKRATE соответственно.
GetPitch/GetPlaybackRate — запрос высоты тона/скорости воспроизведения
MMRESULT waveOutGetPitch (HWAVEx Handle, LPDWORD ForPitch); MMRESULT waveOutGetPlaybackRate (HWAVEx Handle, LPDWORD ForRate);ForPitch/ForRate — указатель переменной, в которую заносится текущий множитель высоты тона или скорости воспроизведения.
Функция опрашивает текущие установки множителя высоты тона или скорости воспроизведения потока. Интерпретация переменных, на которые ссылаются указатели ForPitch/ForRate, — как в функциях SetPitch/SetPlaybackRate.
GetID — запрос номера устройства по ключу
MMRESULT xxxGetID (HWAVEx Handle, LPUINT ForID);
ForID — указатель переменной типа UINT, в которую заносится номер устройства.
Функция определяет номер устройства, при открытии которого системой был возвращен заданный ключ. В том случае, если при открывании была использована служба переназначения (значение WAVE_MAPPER вместо номера или флаг WAVE_MAPPED), функция возвращает значение WAVE_MAPPER.
Документация Microsoft утверждает, будто эта функция поддерживается только для совместимости, и что для получения номера достаточно привести ключ к нужному типу, однако это совсем не так. Ключ открытого устройства является адресом описателя, принадлежащего звуковой подсистеме, и в Win32 размещается в общей области памяти. Единственный способ получить номер устройства по ключу — использование функции GetID.
GetErrorText - запрос текстового сообщения об ошибке по коду
MMRESULT xxxGetErrorText (MMRESULT Error, LPSTR Text, UINT TextSize);
Error — код ошибки, возвращенный одной из интерфейсных функций;
Text — указатель текстового буфера (массива типа char);
TextSize — размер текстового буфера в байтах.
Функция заносит в заданный буфер текстовое описание ошибки с заданным кодом. Записанный текст завершается нулевым байтом. Если буфер недостаточно велик, то конец текста обрезается; нулевой байт записывается в буфер в любом случае. Размер буфера, способного вместить любое сообщение об ошибке, определяется константой MAXERRORLENGTH.
Сообщения об ошибках не имеют разделения по типам устройств, поэтому для запроса текста любой ошибки достаточно любой из возможных функций, например waveOutGetErrorText.
Message - передача сообщения драйверу
MMRESULT xxxMessage (HWAVEx Handle, UINT Msg, DWORD P1, DWORD P2);
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), которой присваивается максимальное приращение приоритета. Звуковые устройства открываются в режиме уведомления рабочей задачи.
Вследствие буферизации выводимый программой звук несколько отстает от исходного. Общее время буферизации и количество звуковых буферов задается в секции параметров программы. Для непрерывного переноса звука количество буферов не должно быть меньше двух; приемлемая стабильность достигается уже при использовании трех-четырех буферов.
Для работы программы необходим полнодуплексный звуковой адаптер, допускающий одновременную работу своего АЦП и ЦАП. Большинство современных адаптеров удовлетворяет этому условию.
ПримерКомпьютерПресс 6'2000