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

Евгений Музыченко

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

Основные понятия и возможности звукового интерфейса 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

 

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

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

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

  1. Wave — устройства ввода/вывода цифрового звука, представленного в виде последовательности значений (отсчетов) амплитуды, следующих друг за другом с заданной частотой дискретизации;
  2. MIDI — устройства ввода/вывода сообщений управления музыкальными инструментами в формате MIDI (Musical Instrument Digital Interface). Сами по себе MIDI–сообщения не содержат звука, а их последовательность представляет собой «электронную партитуру» исполняемого произведения.

В 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), только в восьмиразрядном режиме (большинство моделей 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.

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

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

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

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

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

В начало

В начало

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

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

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

В начало

В начало

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

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

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

В начало

В начало

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

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

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

В начало

В начало

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

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

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

В начало

В начало

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

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

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

В начало

В начало

Следующая страница