Сообщений 13    Оценка 235        Оценить  
Система Orphus

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

Автор: Евгений Музыченко
Источник: Журнал Компьютер Пресс #6-2000
Опубликовано: 10.07.2003
Исправлено: 10.12.2016
Версия текста: 1.0

Краткие сведения об устройстве звукового адаптера
Основные понятия и возможности звукового интерфейса MME
Потоковая модель
Звуковой буфер
Синхронные и асинхронные устройства
Полу- и полнодуплексные звуковые устройства
Уведомление о завершении обработки буфера
Подготовка буферов
Способы кодирования цифрового звука
Формат потока
Структура потока
Позиция в потоке
Паузы, сброс и зацикливание
Поддержка нескольких процессов
Служба переназначения устройств и форматов
Подсистема сжатия звука
Прозрачное для приложения преобразование форматов
Номера звуковых устройств
Идентификаторы (ключи) открытых устройств
Разделение интерфейсных функций по типу устройств
Общая схема взаимодействия программы и звуковой подсистемы
Средства разработки, включаемые файлы и библиотеки
Структуры, используемые в звуковом интерфейсе
Структура WAVEFORMATEX
Структуры WAVEINCAPS и WAVEOUTCAPS
Структура WAVEHDR
Структура MMTIME
Уведомления, передаваемые программе звуковой подсистемой
Уведомление установкой программного события
Уведомление посылкой сообщения окну или задаче
Уведомление вызовом программной функции
Набор интерфейсных функций звуковых устройств
Перечень интерфейсных функций
Значения, возвращаемые интерфейсными функциями
GetNumDevs - запрос количества устройств
GetDevCaps - запрос параметров и возможностей устройств
Open - открывание устройства
Close - закрывание устройства
PrepareHeader - подготовка буфера и его заголовка к передаче драйверу
UnprepareHeader - отмена подготовительных действий для буфера
Write/AddBuffer - передача звукового буфера драйверу
Stop/Pause - остановка записи/воспроизведения
Start/Restart - запуск записи/воспроизведения
Reset - уничтожение (сброс) потока
BreakLoop - прерывание текущего цикла
GetPosition - запрос текущей позиции потока
SetVolume - установка громкости воспроизведения
GetVolume - запрос текущей громкости воспроизведения
SetPitch / SetPlaybackRate - установка высоты тона / скорости воспроизведения
GetPitch / GetPlaybackRate - запрос высоты тона / скорости воспроизведения
GetID - запрос номера устройства по ключу
GetErrorText - запрос текстового сообщения об ошибке по коду
Message - передача сообщения драйверу
Недостатки звуковой подсистемы MME
Пример программы, использующий интерфейс MME

Демонстрационная программа - Delay

В отличие от MS DOS, термин "низкоуровневое" в Windows означает не манипуляцию регистрами, прерываниями и каналами DMA, а просто самый элементарный уровень сервиса, обычно предоставляемого непосредственно драйвером устройства. В нашем случае таким устройством обычно является звуковой адаптер (карта), хотя Windows совершенно безразлично, откуда драйвер берет вводимый звук и куда девает звук выводимый. Существует, например, драйвер для вывода произвольного звука на встроенный динамик (PC Speaker), а также виртуальные драйверы, которые просто перенаправляют звуковые потоки другим драйверам или программам.

Первоначально интерфейс со звуковыми устройствами был введен в Windows 3.x под названием MME (MultiMedia Extension - мультимедийное расширение). При переносе на платформу Win32 он практически не претерпел изменений, поэтому все описанное здесь, с точностью до нескольких непринципиальных моментов, может быть использовано и в 16-разрядных программах для Windows 3.x.

Звуковые устройства в Windows относятся к классу Multimedia/Audio; в этот класс входят два типа устройств:

В Win32 дополнительно введен тип Aux - вспомогательные звуковые устройства (например - микшеры), при помощи которых реализуется управление параметрами звука, регулировки, настройки и т.п.

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

Каждый из типов включает устройства ввода (In) и вывода (Out). Первые служат для записи звука от внешнего источника в приложение, вторые - для воспроизведения звука, порожденного приложением, извлеченного из звукового файла, либо полученного любым другим способом.

Описание звуковой подсистемы есть во встроенной системе помощи любой современной среды программирования, а также в MSDN SDK. Microsoft поддерживает в Internet справочную систему MSDN Online, звуковая подсистема описана в разделе http://msdn.microsoft.com/library/psdk/multimed/wave_7jcf.htm.

Краткие сведения об устройстве звукового адаптера

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

Микшер расположен в аналоговой части адаптера. В его задачу входит регулировка входных уровней различных источников звука - микрофона, линейного входа, компакт-диска, модема и т.п., сведение всех источников в единый звуковой сигнал, поступающий на АЦП, а также - регулировка выходного сигнала адаптера, снимаемого с ЦАП.

В режиме записи схема АЦП с равными интервалами времени опрашивает входной сигнал и формирует последовательность мгновенных значений амплитуды, называемых отсчетами. В зависимости от заданного режима разрядность отсчета (sample width) может быть разной - 8 или 16 бит для простых адаптеров, и от 18 до 24 - для сложных и качественных. Чем больше разрядность отсчета, тем выше точность цифрового представления сигнала и меньше уровень шумов и помех, вносимых АЦП при оцифровке.

Частота, с которой АЦП опрашивает входной сигнал, называется частотой дискретизации (sample rate). Для точного цифрового представления сигнала частота дискретизации должна быть как минимум вдвое выше максимальной частоты сигнала; на практике обычно выбирается небольшой запас для компенсации погрешностей. Например, для представления сигналов с полосой частот до 10 кГц выбирается частота около 22 кГц.

Последовательность отсчетов, сформированная АЦП, передается управляющим процессором в основную память компьютера при помощи внепроцессорного доступа к памяти (DMA на шине ISA, или Bus Mastering - на шине PCI). После заполнения части (обычно половины) выделенной для обмена области памяти адаптер подает сигнал аппаратного прерывания, по которому драйвер адаптера извлекает накопленные в памяти данные и переносит их в буфер программы, которая запросила запись звука. После заполнения буфера программы драйвер подает ей программный сигнал, по которому программа переносит данные в нужное ей место - в другую область памяти для обработки, на диск, отображает на экране и т.п.

При воспроизведении звука происходит обратный процесс: программа записывает последовательность звуковых отсчетов в буфер, передает его драйверу, который по частям переносит данные в область памяти для DMA. Управляющий процессор адаптера последовательно извлекает из памяти отсчеты и направляет их на ЦАП, где они преобразуются в обычный электрический звуковой сигнал, который, пройдя через регуляторы микшера, попадает на выходной разъем адаптера.

Некоторые адаптеры для ISA (например - Turtle Beach Tahiti/Fiji) обходятся без DMA, используя вместо этого свою собственную память, доступную для чтения и записи со стороны центрального процессора. Это позволяет сэкономить один или два канала DMA, если те окажутся дефицитным ресурсом. Однако, из-за относительно низкой скорости передачи звука по шине (100-200 килобайт в секунду) никакой видимой разницы в быстродействии это не дает.

Для удобства буфер обмена компьютера и звукового адаптера делается циклическим (кольцевым). Это означает, что пока одна сторона (адаптер или ЦП) ведет запись первой половины буфера, другая сторона должна успеть прочитать данные из второй половины, и наоборот. Если быстродействия ЦП или драйвера не хватает, или нарушается правильная работа системы аппаратных прерываний - записываемый звук теряется, а воспроизводимый - зацикливается. Зацикливание короткого фрагмента воспроизводимого звука - типичный признак неверного выбора линии прерывания для адаптера или неисправности в системе прерываний.

Основные понятия и возможности звукового интерфейса MME

Потоковая модель

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

Звуковой буфер

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

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

Для каждого звукового буфера приложением также создается заголовок (header) - структура-описатель, в которую заносятся параметры буфера и режимы его обработки. Обмен буферами между приложением и драйвером происходит в виде обмена указателями их заголовков.

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

Синхронные и асинхронные устройства

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

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

Полу- и полнодуплексные звуковые устройства

Звуковые адаптеры, способные одновременно записывать и воспроизводить различные звуковые потоки, называются полнодуплексными (full duplex). Соответствующие устройства ввода и вывода в Windows могут быть открыты и использованы одновременно, и при этом встречные потоки никак не влияют друг на друга.

Адаптеры, способные в каждый момент времени работать только в одном режим - либо на запись, либо на воспроизведение - называют полудуплексными (half duplex). Из соответствующей пары устройств Windows одновременно может быть открыто только одно - либо устройство ввода, либо устройство вывода. При попытке одновременного открытия второго устройства возвращается ошибка "устройство занято".

Некоторые адаптеры обладают возможностью ограниченной полнодуплексной работы - например, только в монофоническом режиме (ряд адаптеров на микросхемах ESS), только в 8-разрядном режиме (большинство моделей Sound Blaster 16, AWE32, SB 32, AWE64), и т.п. В остальных режимах такие адаптеры работают только в полудуплексе.

Уведомление о завершении обработки буфера

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

Подготовка буферов

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

Способы кодирования цифрового звука

При работе со звуковыми адаптерами чаще всего используется традиционный способ цифрового кодирования PCM (Pulse Code Modulation - импульсно-кодовая модуляция, ИКМ). Ряд мгновенных значений звуковой амплитуды, следующих друг за другом с частотой дискретизации, представляется рядом чисел выбранной разрядности, значения которых пропорциональны величине амплитуды. Именно в таком виде звуковой поток снимается с выхода АЦП или подается на вход ЦАП.

Однако, наряду с предельной простотой, PCM обладает существенной избыточностью, передавая звук настолько точно, насколько это возможно при выбранных параметрах оцифровки. Зачастую на первое место выходит задача минимизации скорости и объема звукового потока, в то время как отдельными параметрами точности и качества можно пренебречь. В таких случаях используются другие способы кодирования, где звуковая информация представляется в виде относительных изменений амплитуды (ADPCM - adaptive differential PCM, адаптивная разностная ИКМ), мгновенных "снимков" спектра (Audio MPEG) и т.п.

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

Формат потока

Совокупность основных параметров потока - способа кодирования, частоты дискретизации, количества каналов (стерео/моно) и разрядности отсчета - называется форматом потока (Wave Format). Желаемый формат указывается при открывании устройства; для смены формата требуется закрывание и повторное открывание устройства.

Главным параметром формата является способ кодирования, который называется еще признаком формата (format tag). Каждый способ кодирования порождает группу однотипных форматов, различающихся лишь точностью представления, а следовательно - и качеством передачи звука.

Основные частоты дискретизации (11025, 22050 и 44100 Гц) в сочетаниях с различным количеством каналов (1 или 2) и разрядностью отсчета (8 или 16) при способе кодирования PCM образуют 12 типовых форматов. Частота 11025 Гц (полоса звуковых частот примерно до 5 кГц) приблизительно соответствует качеству телефонного сигнала, частота 22050 Гц (полоса до 10 кГц) - среднего радиоприемника, частота 44100 Гц (полоса до 20 кГц) - качественной звуковой аппаратуре.

Структура потока

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

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

8-разрядные отсчеты в PCM представляются в виде беззнаковых целых чисел; за нуль сигнала принято "центральное" значение 128 (шестнадцатеричное 80). Таким образом, предельной отрицательно амплитуде сигнала соответствует нулевое значение отсчета, а предельной положительной - значение FF. Для пересчета значений отсчетов в знаковую двуполярную форму в диапазоне от -128 до +127 из них нужно вычитать 128 (0x80) (или прибавлять то же самое смещение, вычисляя по модулю 256, что дает такой же результат).

Отсчеты с разрядностью более 8 представляются в виде целых чисел со знаком в стандартном формате Intel; за нуль сигнала принято нулевое же значение отсчета. Здесь может без каких-либо ограничений применяться обычная целая арифметика - например, над типами short (16-разрядный) и long (32-разрядный).

Если разрядность отсчета превышает 16, она может быть не кратна байту - современные звуковые адаптеры могут использовать 18-, 20- и 22-разрядные отсчеты. В таком случае отсчет выравнивается по старшей границе трех- или четырехбайтового слова, а лишние младшие разряды заполняются нулями. Такое представление позволяет работать с отсчетами любой разрядности, как с 24- или 32-разрядными; от фактической разрядности отсчета зависит лишь точность получившегося числа.

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

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

Позиция в потоке

С момента запуска потока драйвер отслеживает текущую позицию записи или воспроизведения, которая в любой момент может быть запрошена приложением. Точность определения реальной позиции зависит от используемого устройства - она может указывать на конкретный звуковой отсчет, либо на небольшую группу отсчетов, находящуюся в данный момент в обработке. Как правило, все адаптеры имеют скрытую небольшую очередь между встроенным процессором и ЦАП/АЦП, так что полученная от драйвера позиция почти всегда будет отличаться от реальной на несколько отсчетов.

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

Паузы, сброс и зацикливание

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

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

Поддержка нескольких процессов

Звуковая подсистема Windows допускает работу с устройством нескольких процессов (клиентов) одновременно. Многие современные и даже некоторые устаревшие звуковые устройства поддерживают более одного клиента; устройство вывода (или его драйвер) смешивает проигрываемые клиентами звуковые потоки, а устройство ввода - размножает записываемый поток для всех подключенных клиентов.

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

Служба переназначения устройств и форматов

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

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

Подсистема сжатия звука

В Win32 имеется подсистема сжатия звука (Audio Compression Manager - ACM), при помощи которой возможно взаимное преобразование звуковых форматов - как внутри групп, так и между ними. Наряду с простыми преобразованиями - изменением частоты дискретизации, количества каналов или разрядности отсчета, ACM предоставляет широкий набор форматов сжатия - ADPCM, a-Law, mu-Law, MSN Audio, GSM, MPEG и т.п. Подсистема сжатия реализована в виде набора так называемых кодеков (ACM Codec) - специальных драйверов ACM, которые и занимаются непосредственно переводом звука из одного формата в другой. Сам же ACM - это диспетчер, который взаимодействует с приложением, и по запрошенным форматам активизирует нужные кодеки, снабжая их необходимыми параметрами.

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

Прозрачное для приложения преобразование форматов

Когда приложение открывает конкретное звуковое устройство, указывая требуемый формат потока, звуковая подсистема пытается связаться непосредственно с драйвером устройства. Однако, если устройство не поддерживает требуемый формат, а обратившейся программой разрешена работа через Wave Mapper, подсистема может попытаться найти подходящий кодек ACM, который способен в реальном времени преобразовывать один из "родных" форматов устройства в формат, запрошенный приложением. Если такой кодек обнаружен - подсистема прозрачно включает его в работу между драйвером и приложением, и приложение может считать, что выбранный формат потока поддерживается непосредственно устройством.

ПРИМЕЧАНИЕ

Надо сказать, что в моих экспериментах с прозрачным преобразованием при использовании кодека MPEG Layer 3 система Windows 95 OSR2 Pan Euro регулярно зависала наглухо. Очевидно, по причине крайне редкого использования такого режима в существующих приложениях разработчики Microsoft не сумели обнаружить и исправить имеющиеся в этих подсистемах ошибки.

Номера звуковых устройств

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

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

Идентификаторы (ключи) открытых устройств

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

Ключи звуковых устройств не имеют ничего общего с ключами файлов, событий, окон, задач и т.п. Системные функции DuplicateHandle, CloseHandle и прочие к ним не применимы.

Разделение интерфейсных функций по типу устройств

Несмотря на то, что большая часть функций интерфейса одинакова либо симметрична для устройств ввода и вывода, разработчики Microsoft по непонятной причине разделили названия функций для устройств ввода и вывода - каждая функция имеет префикс, состоящий из типа и "ориентации" устройства: midiIn, waveOut и т.п. С одной стороны, это способствует защите от ошибок, однако с другой - усложняет создание универсальных функций и классов, в которых направление передачи задается параметром. К счастью, одинаковые по назначению функции имеют одинаковые же наборы параметров, так что в C/C++ одинаковые или симметричные пары функций легко объединяются при помощи указателей.

Общая схема взаимодействия программы и звуковой подсистемы

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

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

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

Затем программа создает (обычно - в динамической памяти) один или несколько звуковых буферов с заголовками и заполняет заголовки в соответствии с установленными правилами. Программа может также сразу подготовить все звуковые буферы к передаче драйверу функциями Prepare, либо делать это непосредственно перед передачей каждого очередного буфера.

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

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

Цикл воспроизведения начинается с заполнения одного или нескольких буферов звуковыми данными, после чего они передаются драйверу устройства вывода функцией Write. После получения первого же буфера драйвер запускает ЦАП адаптера, который начинает извлекать звуковые отсчеты. Драйвер всегда воспроизводит каждый буфер полностью, в соответствии со значением поля dwBufferLength в его заголовке. После возврата отработанных буферов приложению они вновь заполняются данными и опять передаются драйверу.

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

Для устройств вывода, поддерживающих расширенные функции управления, программа может регулировать громкость звука функцией SetVolume, а также изменять высоту тона и скорость воспроизведения функциями SetPitch/SetPlaybackRate. Более общим способом регулировки громкости является обращение к микшеру (mixer), который является устройством класса Aux.

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

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

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

Средства разработки, включаемые файлы и библиотеки

В данной статье описывается программирование на языке C/C++ в среде Microsoft Visual C++. С небольшими изменениями описание может использоваться в средах C++ Builder, Delphi и других.

Большая часть необходимых констант, типов, структур и прототипов функций звуковой подсистемы определяется в файле MMSYSTEM.H, который входит в состав любого SDK для Windows. По умолчанию этот файл включается в компиляцию при включении общего файла WINDOWS.H. Дополнительные, редко используемые константы определены в файле MMREG.H. Интерфейс подсистемы сжатия (ACM) определен в файле MSACM.H.

Основные звуковые функции импортируются из библиотеки WINMM.LIB, функции ACM - из MSACM32.LIB.

Структуры, используемые в звуковом интерфейсе

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

Структура WAVEFORMATEX

Описывает формат звукового потока. Все поля заполняются приложением, звуковая подсистема и драйвер никогда не изменяют полей этой структуры.

WORD  wFormatTag; 
WORD  nChannels; 
DWORD nSamplesPerSec; 
DWORD nAvgBytesPerSec; 
WORD  nBlockAlign; 
WORD  wBitsPerSample; 
WORD  cbSize; 

wFormatTag - код группы форматов, отражающий способ кодирования звука. Константы, представляющие различные группы, имеют имена вида WAVE_FORMAT_x, где x - название группы. В файле MMSYSTEM.H определена только одна константа из этой серии - WAVE_FORMAT_PCM; все остальные константы определены в файле MMREG.H.

nChannels - количество каналов. Обычно этот параметр имеет значение 1 или 2.

nSamplesPerSec - частота дискретизации (количество отсчетов одного канала в секунду).

nAvgBytesPerSec - примерное количество байтов в секунду (скорость звукового потока). Формально требуется, чтобы для форматов PCM это поле было равно произведению nSamplesPerSec и nBlockAlign, однако на практике драйверы обычно не проверяют этого условия.

nBlockAlign - количество байтов, занимаемое одним блоком звукового потока. Формально, для форматов PCM это поле должно быть равно произведению nChannels и wBitsPerSample, деленному на 8, однако это правило устанавливалось в то время, когда применялись только 8- и 16-разрядные отсчеты. Таким образом, для правильного вычисления произведение перед делением необходимо округлить в большую сторону до ближайшего кратного 8:

nBlockAlign = (nChannels * wBitsPerSample + 7) / 8

Поскольку для "больших" отсчетов могут использоваться как 24-, так и 32-разрядные слова, однозначной связи между разрядностью отсчета и размером блока может не быть.

wBitsPerSample - разрядность отсчета.

cbSize - размер дополнительной информации о формате, расположенной непосредственно за концом структуры. Если дополнительной информации нет, поле должно быть нулевым. Для форматов PCM это поле игнорируется.

Структуры WAVEINCAPS и WAVEOUTCAPS

Описывают свойства и характеристики звукового устройства. Все поля структур заполняются только звуковой подсистемой и драйвером. Структуры WAVEINCAPS и WAVEOUTCAPS практически одинаковы; WAVEINCAPS имеет следующий вид:

WORD  wMid;
WORD  wPid;
MMVERSION  vDriverVersion;
CHAR  szPname [MAXPNAMELEN];
DWORD  dwFormats;
WORD  wChannels;
WORD  wReserved1; 

wMid, wPid - идентификаторы разработчика (Manufacturer) и самого драйвера (Product). Полный список известных на момент выпуска SDK идентификаторов определен в файле MMREG.H

vDriverVersion - версия драйвера, представленная младшим словом (тип MMVERSION эквивалентен типу UINT, который в Win32 является 32-разрядным). Старший байт слова представляет основной номер версии, а младший байт - номер подверсии, отражающей непринципиальные изменения драйвера. Для выделения каждого номера можно использовать стандартные макросы HIBYTE и LOBYTE.

szPname - имя устройства в виде строки ASCIIZ.

dwFormats - множество поддерживаемых устройством типовых форматов в виде битовой комбинации. Константы для отдельных битов имеют названия вида WAVE_FORMAT_fcbb, где f - старшая цифра частоты дискретизации (1, 2 или 4), c - количество каналов (S - стерео или M - моно), а bb - разрядность отсчета (08 или 16). Например, WAVE_FORMAT_1M08 - 11025 Гц, 8 бит, моно, а WAVE_FORMAT_2S16 - 22050 Гц, 16 бит, стерео.

wChannels - максимальное количество каналов, поддерживаемых устройством. Большинство адаптеров поддерживает два канала (стереорежим).

wReserved1 - служебное поле.

Структура WAVEOUTCAPS полностью аналогична WAVEINCAPS за исключением того, что содержит дополнительное поле dwSupport - битовую комбинацию режимов вывода, поддерживаемых устройством:

WAVECAPS_VOLUME программное управление громкостью
WAVECAPS_LRVOLUME независимое по каналам управление громкостью
WAVECAPS_PITCH управление высотой тона (pitch control)
WAVECAPS_PLAYBACKRATE управление скоростью воспроизведения
WAVECAPS_SYNC драйвер работает только в синхронном режиме
WAVECAPS_SAMPLEACCURATE позиция отслеживается с точностью до отсчета

С точностью до поля dwSupport обе структуры совпадают по расположению полей в памяти, так что для удобства и универсальности их можно использовать в объединении (union).

Структура WAVEHDR

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

LPSTR  lpData;
DWORD  dwBufferLength;
DWORD  dwBytesRecorded;
DWORD  dwUser;
DWORD  dwFlags;
DWORD  dwLoops;
LPWAVEHDR  lpNext;
DWORD  reserved;

lpData - указатель звукового буфера (тип char *). Устанавливается приложением, драйвером не изменяется.

dwBufferLength - размер буфера в байтах. Устанавливается приложением, драйвером не изменяется.

dwBytesRecorded - количество байтов, записанных устройством в буфер. Устанавливается драйвером, имеет значение только после завершения обработки данного буфера.

dwUser - поле пользователя, в которое программа может занести любое угодное ей значение - например, ссылку на внутренний описатель устройства. Устанавливается приложением, драйвером не используется и не изменяется.

dwFlags - флаги состояния буфера. Устанавливается приложением, модифицируется драйвером.

WHDR_PREPARED Буфер подготовлен (зафиксирован в памяти)
WHDR_INQUEUE Буфер находится в очереди драйвера
WHDR_DONE Обработка буфера драйвером завершена
WHDR_BEGINLOOP Буфер является первым в цикле
WHDR_ENDLOOP Буфер является последним в цикле

dwLoops - количество проигрываний цикла для устройств вывода. Имеет значение только в первом буфере цикла. В обычном режиме, без циклов, поле должно быть нулевым. Устанавливается приложением, драйвером не изменяется.

lpNext - указатель заголовка следующего в очереди буфера. Управляется только драйвером.

reserved - служебное поле. Управляется только драйвером.

Структура MMTIME

Описывает время в терминах различных мультимедийных потоков. В звуковой подсистеме используется для получения текущей позиции потока. Приложением устанавливается только поле wType, остальные поля заполняются драйвером.

UINT wType;
union {
  DWORD ms;
  DWORD sample;
  DWORD cb;
  DWORD ticks;
  struct {
    BYTE hour;
    BYTE min;
    BYTE sec;
    BYTE frame;
    BYTE fps;
    BYTE dummy;
    BYTE pad[2]
  } smpte;
  struct {
    DWORD songptrpos;
  } midi;
} u;

wType - формат времени, описываемого структурой:

TIME_BYTES Количество байтов от начала потока
TIME_MIDI Время в стандарте MIDI
TIME_MS Время в миллисекундах
TIME_SAMPLES Количество звуковых блоков с начала звукового потока
TIME_SMPTE Время в стандарте SMPTE
TIME_TICKS Время в тиках от начала MIDI–потока

ms - счетчик миллисекунд, для случая TIME_MS.

sample - счетчик звуковых блоков, для случая TIME_SAMPLES.

cb - счетчик байтов, для случая TIME_BYTES.

ticks - счетчик тиков, для случая TIME_TICKS.

smpte - набор данных в формате SMPTE, для случая TIME_SMPTE.

midi - данные в формате MIDI–времени.

hour, min, sec - счетчик полных часов, минут и секунд.

frame - счетчик кадров внутри последней секунды.

fps - кадров в секунду (24, 25, 29, 30).

dummy, pad - выравнивание на границу двойного слова.

songptrpos - позиция указателя композиции в MIDI (song pointer).

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

По желанию программы, звуковая подсистема может использовать три вида уведомлений: установку объекта программного события (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 - размер структуры в байтах.

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

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

Функция устанавливает выходной уровень воспроизводимого сигнала. Несмотря на то, что функцией допускается 65536 уровней громкости, большинство адаптеров поддерживает лишь от 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, реализующая в реальном времени эффект задержки (delay). Суть эффекта состоит в сложении исходного звукового сигнала с его копией, задержанной во времени на небольшую величину (единицы-сотни миллисекунд). Задержка на величину до 15-20 мс воспринимается на слух, как "дробление" источника звука; на этом принципе основано создание хорового эффекта. Задержка на величину 20-50 мс воспринимается, как реверберация (ощущение объема), а большие величины задержки - как обычное эхо.


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

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

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

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

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

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

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


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 13    Оценка 235        Оценить