Программирование звука в DirectSound
Основные черты и понятия DirectSound
Использование аппаратных ускорителей
Статические и потоковые буферы
Порядок создания вторичных буферов
Управление режимами вторичных буферов
Уведомление о наступлении событий
Именование интерфейсных функций
Общая схема взаимодействия программы и DirectSound
Программирование в DirectSound
Средства разработки, включаемые файлы и библиотеки
Структуры, используемые при работе с подсистемой
DSCAPS — параметры устройства воспроизведения
DSBCAPS — параметры звукового буфера
DSBUFFERDESC — описатель создаваемого буфера
DSCCAPS — параметры устройства захвата
DSCBCAPS — параметры буфера захвата
DSCBUFFERDESC — описатель создаваемого буфера захвата
DSBPOSITIONNOTIFY — описатель позиции для уведомления
Уведомление приложения о наступлении событий
Набор интерфейсных функций подсистемы
Перечень базовых интерфейсов DirectSound
Перечень внеинтерфейсных функций высшего уровня
Значения, возвращаемые функциями и методами
Внеинтерфейсные функции высшего уровня
Enumerate — перебор устройств воспроизведения или захвата
EnumCallback — перебирающая функция
Create — создание объекта устройства воспроизведения или захвата
QueryInterface — запрос интерфейса из набораRelease — освобождение объекта
Initialize — инициализация устройства
SetCooperativeLevel — установка уровня взаимодействия
CreateSoundBuffer — создание звукового буфера
DuplicateSoundBuffer — создание копии объекта буфера
GetCaps — запрос параметров и состояния устройства
GetSpeakerConfig — запрос конфигурации звукоизлучателей
SetSpeakerConfig — установка конфигурации звукоизлучателей
Compact — уплотнение внутренней памяти адаптера
Initialize — инициализация объекта буфера
Restore — восстановление памяти потерянного буфера
GetCaps — запрос параметров буфера
GetFormat — запрос текущего формата буфера
SetFormat — установка формата буфера
Lock — запрос обновления данных в буфере
Unlock — завершение обновления данных в буфере
Stop — прекращение проигрывания буфера
GetStatus — запрос состояния буфера
GetCurrentPosition — запрос текущих позиций буфера
SetCurrentPosition — установка позиции воспроизведения буфера
GetFrequency — запрос частоты дискретизации
SetFrequency — установка частоты дискретизации
GetPan — запрос текущего положения источника на панораме
SetPan — установка текущего положения источника на панораме
GetVolume — запрос уровня громкости источника
SetVolume — установка уровня громкости источника
Initialize — инициализация объекта устройства
GetCaps — запрос параметров устройства
CreateCaptureBuffer — создание буфера захвата
Интерфейс IDirectSoundCaptureBuffer
Initialize — инициализация объекта буфера
GetCaps — запрос параметров буфера
GetFormat — запрос формата буфера
GetCurrentPosition — запрос текущих позиций буфера
GetStatus — запрос состояния буфера
Lock — открывание процедуры извлечения данных
Unlock — завершение процедуры извлечения данных
Start — запуск захвата в буфер
Stop — остановка захвата в буфер
SetNotificationPositions — заказ позиций уведомления
QuerySupport — опрос наличия поддержки свойства
Типичные применения DirectSound
Оптимизация вывода звука в DirectSound
Непрерывная работа первичного буфера
Пример программы, использующей DirectSound
DirectSound — сравнительно новый программный интерфейс, входящий в семейство «мультимедийных» интерфейсов DirectX (DirectDraw, Direct3D, DirectInput и т.п.). Первым продуктом данного семейства является интерфейс Direct Draw, созданный почти одновременно с Windows 95 и предназначенный для оптимизации работы игровых приложений с видеоадаптером. Затем к нему добавились интерфейсы Direct3D, DirectInput, а впоследствии для многих классических интерфейсов с оконечными устройствами были введены Direct-версии.
Название DirectX трактуется буквально — «прямой, непосредственный интерфейс X». Классические системные интерфейсы с видео-, звуковыми и игровыми адаптерами относятся к Windows первых версий, когда внутренняя организация большинства адаптеров была достаточно разношерстной, многие решения находились в стадии отработки, а приложениям требовалось в первую очередь отображать прямоугольные окна, текст и графики, проигрывать длительные непрерывные звукозаписи и т.п. С массовым переходом производителей игр на платформу Windows и развитием видеотехнологий выяснилось, что большинство классических интерфейсов слишком абстрактны, поэтому при работе с конкретным устройством возникают заметные накладные расходы, ощутимо снижающие быстродействие; при частой и хаотичной перерисовке элементов экрана, выводе коротких одновременных звуков выполняется множество лишних операций. Семейство DirectX было разработано именно для того, чтобы приблизить аппаратуру к приложению, предоставив эффективный интерфейс.
Ценой эффективности явилось снижение универсальности интерфейсов, более явная привязка их к определенным схемам и протоколам взаимодействия. Для полной поддержки DirectX необходима установка специального расширенного DirectX-драйвера; традиционные драйверы устройств, как правило, для этого не годятся. В первые несколько лет существования DirectX выпуск DirectX-драйверов часто отставал от выпуска устройств; сейчас этот разрыв практически отсутствует, хотя далеко не все драйверы поддерживают полный набор заявленных функций. В случае появления новых технологий вывода изображения и звука может оказаться, что они не укладываются в схему DirectX, и тогда потребуется серьезная переработка как интерфейсов, так и использующих их приложений.
Интерфейсы семейства DirectX состоят из общесистемной части — подсистем DirectX, с которыми общаются приложения, и набора драйверов с поддержкой DirectX — обычных драйверов устройств, в которые добавлена поддержка новых объектов и функций. Центральной частью любой подсистемы DirectX является HAL (Hardware Abstraction Layer — уровень отвлечения от аппаратуры) — промежуточный интерфейс, который скрывает частные, непринципиальные особенности используемых аппаратных средств, но сохраняет полный контроль над основными, ключевыми возможностями аппаратуры. HAL реализуется на уровне драйвера устройства.
Поддержка DirectX включена в стандартную поставку начиная с Windows 98 и NT 5. В системах Windows 95, OSR2 и NT 4 поддержка DirectX не предусмотрена, однако Microsoft выпускает отдельные расширения для Windows 9x и NT, установка которых восполняет в системе отсутствующие компоненты — собственно подсистемы и распространенные драйверы.
Поддержка интерфейсов DirectX есть в большинстве систем программирования C++, Pascal и Basic, выпущенных после 1995 года. Семейство имеет целый ряд версий; в настоящее время используется версия 7. Если в среде программирования поддерживаются только более старые версии, можно заменить включаемые файлы и библиотеки на более новые, содержащие обновленные версии подсистем, взяв их из новой версии DirectX SDK.
Описания подсистем также имеются в поддерживающих их средах программирования. Более новые версии описаний распространяются Microsoft в составе DirectX SDK. Microsoft поддерживает в Internet справочную систему MSDN Online, где интерфейсы DirectX описаны в разделе http://msdn.microsoft.com/library/psdk/directx/dxstart_1x2d.htm, а сам DirectSound — в http://msdn.microsoft.com/library/psdk/directx/dsover_9dno.htm.
Основные черты и понятия DirectSound
Назначение и структура
Подсистема DirectSound обеспечивает приложениям практически непосредственный доступ к аппаратуре звукового адаптера. Этот факт вовсе не означает, что приложению приходится вникать в детали программирования того или иного адаптера — это остается прерогативой HAL. Вместо этого приложению предоставляется модель современного звукового адаптера, предельно приближенная к реальности, с минимальным уровнем абстракции. При таком подходе общение приложения с адаптером сопровождается минимальными накладными расходами, и в то же время избавляет приложение от излишних подробностей программирования аппаратуры.
Подсистема DirectSound построена по объектно-ориентированному принципу в соответствии с моделью COM (Component Object Model — модель объектов-компонентов, или составных объектов) и состоит из набора интерфейсов. Каждый интерфейс отвечает за объект определенного типа — устройство, буфер, службу уведомления и т.п. По сути, интерфейс представляет собой обычный набор управляющих функций, или методов, организованных в класс объектно-ориентированного языка.
DirectSound не поддерживает звуковые форматы, отличные от PCM. Назначение DirectSound — исключительно эффективный вывод звука, а операции по преобразованию форматов остаются в ведении приложений, которые могут использовать для этого подсистему сжатия звука (ACM).
Смешивание сигналов
Одно из наиболее неудобных ограничений MME/Wave — невозможность проигрывания нескольких звуков на одном устройстве одновременно без их программного смешивания самим приложением. DirectSound снимает это ограничение, позволяя приложению просто задавать несколько источников звука, которые будут проиграны одновременно. Количество таких источников ограничено только доступной памятью и быстродействием аппаратуры.
Объемный звук
Источники звука, работающие в базовой модели DirectSound, могут быть только моно- стереофоническими. Работа расширенной базовой модели, DirectSound3D, основана на другой концепции, позволяющей размещать монофонические источники звука в пространстве. Для каждого источника, а также для самого слушателя, задаются координаты в пространстве, ориентация, направление и скорость перемещения. DirectSound3D обрабатывает все источники и создает для слушателя объемную и реалистичную звуковую картину с учетом интерференции, затухания, направленности, эффекта Доплера и т.п.
В этой статье рассматривается только базовая модель DirectSound. Расширение DirectSound3D будет описано в следующих выпусках.
Использование аппаратных ускорителей
При наличии у используемого звукового адаптера средств аппаратного ускорения (hardware acceleration) — смешивания нескольких источников звука, обсчета трехмерной модели, создания звуковых эффектов и т.п. — DirectSound всю возможную работу старается переложить на аппаратуру адаптера. Использование аппаратного ускорения остается прозрачным для приложения — DirectSound использует возможности аппаратуры там, где это можно, а в остальных случаях выполняет нужную работу на программном уровне. Тем не менее приложениям, использующим большое количество одновременно звучащих источников или сложную трехмерную картину, имеет смысл упрощать свою модель при отсутствии средств аппаратного ускорения, иначе накладные расходы могут существенно снизить общую производительность системы.
Для реализации на аппаратном уровне смешивания и звуковых эффектов необязательно нужен современный адаптер с шиной PCI. Многие комбинированные адаптеры с шиной ISA — семейство Sound Blaster AWE, Turtle Beach Maui/Tropez/Pinnacle, Guillemot MaxiSound — имеют встроенные таблично-волновые синтезаторы с расширяемой оперативной памятью. Если синтезатор свободен и у него достаточно свободной памяти, DirectSound может загрузить туда некоторое количество источников звука и проигрывать их средствами синтезатора.
Конфигурация звукоизлучателей
Для создания реалистичной звуковой картины DirectSound нуждается в информации о расположении звукоизлучателей — громкоговорителей или наушников — относительно слушателя. Для достижения максимального эффекта звукоизлучатели должны быть установлены и настроены правильно, а приложение должно указать используемую конфигурацию подсистеме DireсtSound.
Звуковые буферы
Большинство существующих звуковых адаптеров использует для обмена звуком с центральным процессором звуковые буферы, представляющие собой участок памяти, в который заносятся звуковые данные. Обычно буфер является кольцевым, то есть указатель текущей позиции при достижении конца буфера автоматически перебрасывается на его начало, совершая внутри буфера круговое движение. Адаптер и его драйвер работают параллельно с разными частями буфера, стараясь следовать друг за другом и не создавать конфликтов; если их работа достаточно согласованна — получается непрерывное движение сколь угодно длительного звукового потока.
В отличие от концепции связанной цепочки программных буферов, принятой в MME, DirectSound предоставляет приложению почти прямой доступ к аппаратным буферам адаптера. В этой концепции просто смещены акценты: вывод коротких и повторяющихся звуков значительно упрощается, а вывод длительных непрерывных звучаний несколько усложняется относительно модели MME.
Размещение звуковых буферов
Различные адаптеры используют буферы разного типа. Классические адаптеры типа Sound Blaster, Windows Sound System и совместимые с ними используют буфер в основной памяти компьютера с доступом через DMA. Адаптеры архитектуры Hurricane (Turtle Beach Tahiti, Fiji и совместимые) используют буфер в собственной (on-board) памяти, который доступен в виде «окна» в диапазоне адресов внешних устройств. Существуют также адаптеры со встроенным буфером, доступ к которому осуществляется через порты ввода-вывода; обычно так работают таблично-волновые синтезаторы.
В зависимости от размещения и способа управления различают аппаратные (hardware) и программные (software) буферы. Аппаратным называют буфер, к которому адаптер имеет прямой доступ; такой буфер располагается либо в памяти самого адаптера, либо в основной памяти с обращением через DMA или Bus Mastering. Программные буферы всегда располагаются в основной памяти и управляются центральным процессором, адаптер к таким буферам прямого доступа не имеет.
В документации по DirectSound аппаратными называют только те буферы, которые находятся в памяти адаптера, и нередко путают термин «hardware» в отношении размещения буфера и способа смешивания звука. Я буду пользоваться выражением «аппаратный буфер» — для обозначения буфера в памяти адаптера и выражением «буфер с аппаратным смешиванием», когда речь будет идти о буфере, к которому адаптер имеет прямой доступ при извлечении звука.
Первичный и вторичные буферы
Если в архитектуре адаптера один из аппаратных буферов является основным, его называют первичным (primary). Остальные буферы, занимающие подчиненное положение, называются вторичными (secondary). Обычно звуки из вторичных буферов смешиваются воедино в первичном буфере, откуда и поступают на ЦАП адаптера.
Для адаптеров типа SB/WSS, работающих только с одним буфером, он и является первичным; вторичные буферы могут быть только программными и управляются самой подсистемой DirectSound. И наоборот, для современных многоканальных адаптеров PCI, имеющих несколько равноправных каналов вывода звука, первичный буфер недоступен, зато некоторое количество вторичных может быть аппаратными, и управление звуками в них осуществляет непосредственно сам адаптер.
Чаще всего приложению не требуется использовать первичный буфер. В типовой схеме взаимодействия для каждого источника звука создается свой вторичный буфер (в DirectSound часто отождествляются понятия «источник звука» и «вторичный звуковой буфер»). Впоследствии приложение в нужные моменты включает и выключает звучание источников, меняет текущую позицию в звуке, параметры звучания и т.п. Вторичные буферы могут иметь произвольные размеры, которые задаются приложением при их создании. Даже если смешивание выполняет DirectSound — оно осуществляется на уровне ядра (VxD или системно).
В исключительных случаях возможен прямой доступ к первичному буферу. При этом запрещается использование вторичных буферов — то есть приложение теряет возможность описывать независимые источники звука. Зато наличие доступа к первичному буферу гарантирует, что все изменения в звуковых данных будут услышаны максимально быстро (единицы миллисекунд). Однако первичный буфер имеет фиксированный размер, выбираемый драйвером DirectSound, и размер этот достаточно мал (несколько десятков миллисекунд звучания). Для того чтобы успевать вписывать звук в первичный буфер, приложение должно иметь высокий уровень приоритета. Но даже в этом случае Windows не гарантирует нужной скорости, не являясь системой реального времени.
Статические и потоковые буферы
Вторичный буфер может быть статическим (static) и потоковым (streaming). Статические буферы предназначены для постоянных звуков, цифровое представление которых не меняется либо меняется достаточно редко. Потоковые буферы ориентированы на часто изменяемые звуки, как правило — на представление длительного звукового потока, который по частям «прогоняется» через буфер.
Статические и потоковые буферы различаются только тем, что подсистема старается в первую очередь делать аппаратными статические буферы, загружая их в память адаптера. Таким образом, постоянные и короткие звуки оказываются в распоряжении адаптера, и достаточно лишь дать команду, чтобы они включились в общее звучание.
При желании приложение может явно указывать при создании буфера тип памяти для его размещения.
Порядок создания вторичных буферов
DirectSound оптимизирует использование вторичных буферов в порядке их создания приложением; источники звука, созданные в первую очередь, имеют приоритет в использовании аппаратных средств. Буферы, созданные первыми, подсистема старается по возможности загружать в память адаптера, предоставлять им каналы DMA, ресурсы Bus Mastering и т.п.; при исчерпании аппаратных ресурсов DirectSound переходит на самостоятельную, программную обработку оставшихся буферов.
Управление режимами вторичных буферов
Поскольку каждый вторичный буфер описывает независимый источник звука, подсистема предоставляет средства управления режимами звучания источника. Для базовых источников DirectSound доступно управление громкостью, панорамой и частотой дискретизации; для источников DirectSound3D еще и пространственными координатами, направленностью и скоростью движения.
Набор необходимых для источника методов управления задается при создании буфера и позволяет подсистеме оптимально связывать буферы с аппаратными ресурсами. Впоследствии доступны только заказанные методы управления; для изменения набора необходимо уничтожить буфер и создать его заново.
Позиции в буфере
DirectSound использует для адресации в звуковых буферах понятие текущих позиций, или курсоров. Различают позицию записи/воспроизведения, отслеживающую проигрывание звука из буфера в адаптер или запись звука из адаптера в буфер, и позицию доступа, отслеживающую чтение/запись (обмен данными) между приложением и буфером. В первичной англоязычной документации первая позиция называется Play/Capture Position, а вторая — Read/Write Position, поэтому отсутствуют коллизии между значениями термина «запись». Для обозначения ввода звука извне я также буду пользоваться термином «захват» (capture).
Позиция воспроизведения (play) следует за позицией записи (write) в буфер, а позиция чтения (read) — за позицией захвата (capture). Достижение позицией воспроизведения позиции записи означает полное проигрывание буфера воспроизведения, при этом начинают воспроизводиться «старые» данные, которые приложение не успело перезаписать. Достижение позицией захвата позиции чтения означает переполнение буфера захвата, и последующие данные накладываются на «старые», которые приложение не успело извлечь из буфера.