| « Поставить закладку » « Сделать стартовой » | |||
|
|||
|
Статьи:: Графика и игроделание DirectX, OpenGL etc.) :: Direct3D :: Программирование звука в 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 аппаратными называют только те
буферы, которые находятся в памяти адаптера, и нередко путают термин Первичный и вторичные буферыЕсли в архитектуре адаптера один из аппаратных буферов является основным, его называют первичным (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). Достижение позицией воспроизведения позиции записи означает полное проигрывание буфера воспроизведения, при этом начинают воспроизводиться <старые> данные, которые приложение не успело перезаписать. Достижение позицией захвата позиции чтения означает переполнение буфера захвата, и последующие данные накладываются на <старые>, которые приложение не успело извлечь из буфера. Уровни взаимодействияDirectSound вводит четыре уровня взаимодействия (cooperation levels) приложений между собой и звуковым адаптером. Когда несколько приложений одновременно используют один и тот же адаптер, соотношение уровней взаимодействия определяет их приоритетность в использовании аппаратуры и создании звучания.
Потеря буферовКогда приложение, запросившее высший (write-primary) уровень взаимодействия, становится активным, подсистеме приходится передавать ему управление первичным буфером, теряя при этом собственный контроль над ним. И наоборот, когда активным становится приложение с менее высоким уровнем, подсистема возвращает себе контроль над буфером, но теперь его теряет <уходящее в фон> приложение. В этой ситуации, называемой потерей буферов (buffer lose), буфер прекращает звучание и его содержимое теряется. Когда приложение высшего уровня становится активным, звучание всех вторичных буферов приложений более низких уровней останавливается, а сами буферы помечаются как потерянные. Это происходит потому, что подсистема не может правильно отслеживать текущие позиции во вторичных буферах, не имея доступа к первичному буферу адаптера. И наоборот, когда приложение высшего уровня становится неактивным, его первичный буфер также помечается как потерянный. Приложения получают сообщение о потере буферов при попытках обращения к ним в виде кода ошибки DSERR_BUFFERLOST. Все приложения должны корректно обрабатывать эту ситуацию и выполнять восстановление потерянных буферов. ЭмуляцияПодсистема DirectSound может обходиться без поддержки со стороны драйвера. В этом случае нужная функциональность эмулируется через MME - традиционную звуковую подсистему Windows. Однако такая эмуляция крайне неэффективна, поскольку при этом подсистеме DirectSound приходится эмулировать первичный буфер в обычной памяти, смешивать в нем звуки из вторичных буферов, затем представлять его в виде цепочки буферов, передаваемых драйверу MME, который, в свою очередь, разбивает их на фрагменты и переносит в звуковой буфер адаптера. При этом каждое обращение к драйверу MME сопровождается переключением в 16-разрядный режим и обратно. Отметим, что без наличия специализированного DirectSound-драйвера невозможно получить высший (write-primary) уровень взаимодействия с адаптером. Тем не менее поддержка захвата (capture) звука в DirectSound реализована только методом эмуляции. Для драйверов DirectSound VxD определены лишь функции поддержки воспроизведения - дальше в унификации этого интерфейса Microsoft почему-то не пошла. Операции записи и воспроизведения с точки зрения адаптера почти идентичны и различаются в основном направлением движения данных. Однако прямой доступ к буферу адаптера в режиме записи позволил бы значительно повысить эффективность обработки входного сигнала в реальном времени. Поддержка записи звука определена только для драйверов WDM. Задержки звукаПодсистема MME из-за переключений между 32- и 16-разрядными режимами и неоптимальной с точки зрения адаптера структуры буферов часто дает существенную задержку (latency) между подачей звукового блока драйверу и появлением звука на выходе, равно как и в обратном направлении (при записи). DirectSound, за счет более оптимального управления адаптером, вносит задержки на уровне около 20 мс. Однако при эмуляции, когда работа идет через подсистему MME, задержки могут возрасти до 100-150 мс. Идентификация устройствВ отличие от подсистем MME, идентификация устройств в DirectSound следует правилам COM и использует GUID (Globally Unique IDentifier - идентификатор, уникальный в мировом масштабе). Любой объект COM имеет свой идентификатор, по которому приложения могут обращаться к нему. Идентификаторы доступных устройств приложение получает в процессе перебора (enumeration) устройств заданного класса. Уведомление о наступлении событийСобытием в подсистеме DirectSound считается достижение одной из заданных позиций в звуковом буфере. Для запроса уведомления о наступлении таких событий приложение может использовать специальный интерфейс IDirectSoundNotify, создавая соответствующие ему следящие объекты. При достижении указанных позиций следящий объект активизирует (set) заданные объекты события (event objects), которые могут быть опрошены приложением непосредственно, либо может быть создана отдельная задача (thread), ожидающая активизации одного или нескольких объектов событий. DirectSound вводит понятие набора свойств (property set) - параметров, описывающих виды обработки звука. При помощи набора свойств можно описать параметры зала, голоса исполнителя, манеры пения, звучания инструментов и т.п. При наличии необходимых средств обработки одну и ту же звуковую картину можно представлять в разных ракурсах, активизируя нужные наборы свойств. В данное время эти виды обработки почти не поддерживаются; наборы свойств введены в основном на будущее. Именование интерфейсных функцийРазличные интерфейсы имеют наборы функций (методов) с разными именами, поэтому в ссылках я буду приводить только имена методов, не снабжая их именем интерфейса. В прототипах имена всех методов приводятся полностью, с указанием интерфейса. Поскольку ряд интерфейсов имеет схожую структуру (IDirectSound/IDirectSoundCapture, IDirectSoundBuffer/IDirectSound3DBuffer), многие методы являются общими для нескольких интерфейсов сразу. В таких случаях я буду упоминать имя интерфейса, только если в использовании одного и того же метода в разных интерфейсах существует принципиальная разница. СовместимостьИнтерфейсы DirecSound доступны для платформ Windows 98 или 2000 и выше. В Windows 95 интерфейсы становятся доступными после установки пакета DirectX. В Windows NT 4/SP3 доступен только базовый уровень функциональности - интерфейсы IDirectSound и IDirectSoundBuffer. Общая схема взаимодействия программы и DirectSoundПриложение начинает работу с DirectSound, создавая объект устройства с интерфейсом IDirectSound - для воспроизведения звука или IDirectSoundCapture - для захвата (записи) звука. Объект устройства воспроизведения создается функцией DirectSoundCreate, объект устройства захвата - DirectSoundCaptureCreate. При создании объекта устройства указывается идентификатор устройства, которое будет через него программироваться. Приложение может либо запросить доступ к устройству по умолчанию, либо получить перечень идентификаторов доступных устройств функцией перебора - DirectSoundEnumerate или DirectSoundCaptureEnumerate. Функции перебора требуют указания локальной перебирающей функции приложения (callback), которая будет вызываться для каждого доступного устройства. Перебирающая функция может либо самостоятельно выбрать подходящее устройство, либо сформировать полный список устройств, из которого пользователь сделает выбор по своему усмотрению. Созданный объект устройства может быть опрошен методом GetCaps, возвращающим его характеристики и возможности. Таким образом может быть, например, найдено минимально и оптимально подходящее для целей приложения устройство из всех имеющихся в системе. Перед началом работы с устройством необходимо установить уровень взаимодействия методом SetCooperativeLevel. Работа со звуком начинается с создания объектов звуковых буферов. Если приложение работает на обычном уровне взаимодействия, первичный буфер не создается. На остальных уровнях необходимо создать первичный буфер методом CreateSoundBuffer и задать его формат методом SetFormat. На обычном уровне взаимодействия формат первичного буфера фиксирован - 22 050 Гц, стерео, восемь разрядов. Объекты вторичных звуковых буферов также создаются при помощи метода CreateSoundBuffer - по одному для каждого источника звука; в этом же вызове задаются и форматы буферов. Для коротких звуков длительностью до нескольких секунд удобнее создавать статические буферы, целиком вмещающие цифровое представление звуков. Для длительных звуков рекомендуется создавать небольшие (порядка десятков-сотен килобайт) потоковые буферы, через которые будут непрерывно <прогоняться> фрагменты длительного звучания. Создав вторичный буфер, приложение должно заполнить его звуковыми данными. Процедуру занесения данных в буфер открывает метод Lock, возвращающий указатели доступных участков буфера. Эти участки заполняются данными, после чего вызывается метод Unlock, завершающий процедуру обновления данных. При желании приложение может разделить один и тот же экземпляр звучания между несколькими объектами буферов, создавая объекты-копии методом DuplicateSoundBuffer. Впоследствии, изменяя параметры звучания, можно получать различные звуки на основе одной и той же оцифровки, не расходуя дополнительную память. Для запуска воспроизведения буфера вызывается метод Play, для остановки - Stop. Чтобы определить, какой фрагмент звучит в данный момент, используется метод GetCurrentPosition, для запуска звучания с определенного места - SetCurrentPosition. При необходимости приложение может изменить параметры звучания в буфере: частоту дискретизации (SetFrequency), громкость (SetVolume), положение на панораме (SetPan). Для пространственных источников возможно изменение координат, ориентации, скорости движения и т.п. Для корректной работы приложение должно отслеживать ситуацию потери буферов. Получив от методов Lock или Play сообщение о потере буфера, необходимо приостановить генерацию звука и периодически выполнять метод Restore до тех пор, пока он не даст положительного результата. После этого буфер следует вновь заполнить звуковыми данными. Захват (запись) звука производится с помощью объектов устройств IDirectSoundCapture. Здесь нет разделения на первичный и вторичные буферы, поэтому методом CreateCaptureBuffer создается единственный буфер захвата, которому этим же методом приписывается нужный формат. Затем методом Start запускается захват звука, который может быть остановлен методом Stop. Для извлечения звуковых данных из буфера служат методы Lock и Unlock. Процесс захвата во многом симметричен процессу воспроизведения, поэтому в описании интерфейсов захвата упомянуты лишь их отличия от основных интерфейсов. При желании приложение может воспользоваться интерфейсом уведомления IDirectSoundNotify, запрашивая его у объектов тех буферов, для которых требуются уведомления, и заказывая установку заданных объектов программных событий (event objects) по достижении определенных позиций в буфере. Завершая работу, приложение уничтожает методом Release объекты буферов, а затем - объекты устройств. Программирование в DirectSoundСредства разработки, включаемые файлы и библиотекиОписывается программирование на языке C++ в среде Microsoft Visual C++. DirectSound поддерживает программирование и из обычного C, однако использование C++ позволяет более естественно оформлять работу с интерфейсами и не ведет к какому бы то ни было увеличению объектного кода по сравнению с C, поскольку модель DirectSound изначально ориентирована на объекты и методы обращения к ним из C++. Полный комплект DirectX 7.0a SDK занимает 128 Мбайт, однако наиболее важные его части доступны отдельно. Набор документации включаемых заголовочных файлов и библиотек занимает 1,3 Мбайт и доступен по ссылке http://download.microsoft.com/download/win98SE/DXSDK/7.0/W9X/EN-US/dx7libhdr.exe. Вместе с демонстрационной программой к статье прилагаются файлы dsound.h и dsound.lib из комплекта DirectX SDK 7.0a. Тем не менее одних этих файлов недостаточно для построения примера; среда разработки должна поддерживать хотя бы одну из версий DirectX. Структуры, используемые при работе с подсистемойВсе структуры имеют поле dwSize, в которое при инициализации структуры необходимо занести ее размер в байтах. Размер используется для определения версии интерфейса. Это единственное поле, которое требует обязательной инициализации во всех структурах; остальные поля заполняются, только если они являются входными. DSCAPS - параметры устройства воспроизведенияДанная структура описывает возможности и параметры устройства воспроизведения. Для удобства определен тип LPCDSCAPS - константный указатель на тип DSCAPS. DWORD dwSize; DWORD dwFlags; DWORD dwMinSecondarySampleRate; DWORD dwMaxSecondarySampleRate; DWORD dwPrimaryBuffers; DWORD dwMaxHwMixingAllBuffers; DWORD dwMaxHwMixingStaticBuffers; DWORD dwMaxHwMixingStreamingBuffers; DWORD dwFreeHwMixingAllBuffers; DWORD dwFreeHwMixingStaticBuffers; DWORD dwFreeHwMixingStreamingBuffers; DWORD dwMaxHw3DAllBuffers; DWORD dwMaxHw3DStaticBuffers; DWORD dwMaxHw3DStreamingBuffers; DWORD dwFreeHw3DAllBuffers; DWORD dwFreeHw3DStaticBuffers; DWORD dwFreeHw3DStreamingBuffers; DWORD dwTotalHwMemBytes; DWORD dwFreeHwMemBytes; DWORD dwMaxContigFreeHwMemBytes; DWORD dwUnlockTransferRateHwBuffers; DWORD dwPlayCpuOverheadSwBuffers; DWORD dwReserved1; DWORD dwReserved2;
DSBCAPS - параметры звукового буфераСтруктура описывает возможности и параметры существующего звукового буфера. Для удобства определен тип LPCDSBCAPS - константный указатель на тип DSBCAPS. DWORD dwSize; DWORD dwFlags; DWORD dwBufferBytes; DWORD dwUnlockTransferRate; DWORD dwPlayCpuOverhead;
Необходимо иметь в виду, что даже если звук источников, помеченных как звучащие в фоне (когда приложение не является текущим), будет слышен при переключении на другое приложение, то этот звук может приводиться к другому формату. Например, если одно приложение использует стереофонический формат и фоновые источники и происходит переключение на приложение, устанавливающее монофонический формат, звук от фоновых источников также станет монофоническим. Если текущее приложение устанавливает менее качественный формат - звук от более качественных фоновых источников может приобретать заметные искажения вследствие понижения разрядности и/или частоты дискретизации;
DSBUFFERDESC - описатель создаваемого буфераСтруктура описывает конфигурацию и свойства создаваемого звукового буфера. Для удобства определен тип LPDSBUFFERDESC - указатель на тип DSBUFFERDESC. DWORD dwSize; DWORD dwFlags; DWORD dwBufferBytes; DWORD dwReserved; LPWAVEFORMATEX lpwfxFormat; GUID guid3DAlgorithm;
Флаги LOCHARDWARE и LOCSOFTWARE являются взаимоисключающими. Они запрашивают размещение буфера в памяти адаптера или компьютера, однако не гарантируют, что это будет соблюдено. О фактическом размещении буфера можно узнать при помощи его метода GetCaps. Описанный формат структуры используется начиная с DirectX 7.0. До этого использовалась структура, не содержавшая поля guid3DAlgorithm; для совместимости старый формат сохранен под именем DSBUFFERDESC1. Подсистема определяет версию структуры, используя значение поля dwSize, и корректно обрабатывает наличие или отсутствие дополнительного поля. DSCCAPS - параметры устройства захватаОписывает возможности и параметры устройства записи (захвата). Для удобства определен тип LPCDSCCAPS - константный указатель на тип DSCCAPS. DWORD dwSize; DWORD dwFlags; DWORD dwFormats; DWORD dwChannels;
DSCBCAPS - параметры буфера захватаОписывает параметры существующего буфера захвата. Для удобства определен тип LPCDSCBCAPS - константный указатель на тип DSCBCAPS. DWORD dwSize; DWORD dwFlags; DWORD dwBufferBytes; DWORD dwReserved;
DSCBUFFERDESC - описатель создаваемого буфера захватаОписывает конфигурацию и свойства создаваемого буфера захвата. Для удобства определен тип LPDSCBUFFERDESC - указатель на тип DSCBUFFERDESC. DWORD dwSize; DWORD dwFlags; DWORD dwBufferBytes; DWORD dwReserved; LPWAVEFORMATEX lpwfxFormat. Как видно, структура аналогична DSBUFFERDESC, за исключением поля guid3DAlgorithm. В версиях DirectX до 7.0, где в структуре DSBUFFERDESC этого поля не было, форматы описателей создаваемых буферов для устройств воспроизведения и записи были тождественны. Различия касаются также поля dwFlags, содержащего управляющие флаги. В описываемой структуре оно может содержать только флаг DSCBCAPS_WAVEMAPPED. DSBPOSITIONNOTIFY - описатель позиции для уведомленияОписывает позицию звукового буфера, при достижении которой выполняется уведомление. Определен также вспомогательный тип LPDSBPOSITIONNOTIFY - указатель структуры. DWORD dwOffset; HANDLE hEventNotify;
Если для буфера разрешено досрочное прекращение звучания при нехватке аппаратных ресурсов и звучание буфера прекращается по этой причине, уведомление не выполняется. Уведомление приложения о наступлении событийВ подсистеме DirectSound существует только одно асинхронное событие - это достижение заданной позиции внутри звукового буфера. Как частный случай событие может возникать при остановке звучания буфера - методом Stop, или при естественном достижении конца буфера. Подсистема использует для уведомления только объекты программных событий (event objects). Это не так удобно, как в подсистеме MME, предоставляющей несколько видов уведомлений, однако вполне в духе многозадачной модели Win32, когда для управления буферами и перезагрузки их содержимого создается отдельная задача (thread). Эта задача не занимается ничем посторонним, ожидая установки одного из заданных объектов событий, после которой выполняет обновление отработанной части буфера. Для одного буфера может быть указано сколько угодно позиций, при достижении которых происходит уведомление. Таким образом удобно, например, разделить буфер на две или более частей, и каждое уведомление будет означать, что отработана очередная часть буфера. С помощью такого механизма достигается эффективное и в то же время плавное продвижение длительного звукового потока через буфер небольшого размера, как это и делается в звуковых драйверах низкого уровня. Набор интерфейсных функций подсистемыОписание функций распределено по разделам, каждый из которых, в свою очередь, описывает соответствующий интерфейс DirectSound. Несколько общих функций высшего уровня, не принадлежащих интерфейсам, вынесено в начало описания. Перечень базовых интерфейсов DirectSound
Перечень внеинтерфейсных функций высшего уровня
Значения, возвращаемые функциями и методамиВсе функции и методы интерфейсов возвращают результат типа HRESULT, эквивалентный типу LONG. Значение DS_OK равное нулю означает успешное выполнение функции, любое другое значение указывает на ошибку. Константы для кодов ошибок имеют префиксы DSERR_:
Внеинтерфейсные функции высшего уровняEnumerate - перебор устройств воспроизведения или захватаHRESULT DirectSoundEnumerate ( LPDSENUMCALLBACK EnumCallback, LPVOID Context ); HRESULT DirectSoundCaptureEnumerate ( DSENUMCALLBACK EnumCallback, VOID *Context );
EnumCallback - перебирающая функцияBOOL CALLBACK EnumCallback ( LPGUID GUID, LPCSTR Description, LPCSTR Module, VOID *Context );
Функция вызывается для каждого найденного устройства заданного класса. Если возвращается значение TRUE - перебор продолжается, если FALSE - прекращается. Create - создание объекта устройства воспроизведения или захватаHRESULT WINAPI DirectSoundCreate ( LPCGUID GUID, LPDIRECTSOUND *Dev, LPUNKNOWN Outer ); HRESULT WINAPI DirectSoundCaptureCreate ( LPCGUID GUID, LPDIRECTSOUNDCAPTURE *Dev, LPUNKNOWN Outer );
После успешного создания устройства воспроизведения, до начала проигрывания звуковых источников приложение должно установить уровень взаимодействия методом SetCooperativeLevel. Интерфейс IUnknownЯвляется базовым для всех интерфейсов COM и содержит средства фиксации объекта, его освобождения и запроса нужного интерфейса из набора (агрегата). QueryInterface - запрос интерфейса из набораHRESULT IUnknown::QueryInterface ( REFIID IID, LPVOID *Obj );
Если объект, для которого интерфейс IUnknown является базовым, имеет указанный интерфейс, то в заданную переменную заносится указатель объекта, через который доступен запрашиваемый интерфейс. Обычно в качестве переменной выступает указатель объекта того типа, который имеет запрашиваемый интерфейс в своем составе. Например, при получении интерфейса IDirectSound3DBuffer из объекта с указателем Buf типа IDirectSoundBuffer вызывается метод Buf->QueryInterface (IID_IDirectSound3DBuffer, (LPVOID *)&Buf3D), где Buf3D - указатель объекта типа IDirectSound3DBuffer. Указатели Buf и Buf3D обычно ссылаются на один и тот же объект, однако рассчитывать на это не стоит. При успешном завершении функция возвращает код S_OK (нуль). Возможными кодами ошибки могут быть E_NOINTERFACE (исходный объект не имеет требуемого интерфейса) и E_POINTER (передан недопустимый указатель переменной). Счетчик ссылок полученного объекта увеличивается на единицу. При завершении работы с полученным интерфейсом необходимо освободить объект методом Release. AddRef - фиксация объектаULONG IUnknown::AddRef (); Увеличивает счетчик ссылок объекта на единицу. При создании объекта счетчик устанавливается в единицу, при получении нового интерфейса счетчик автоматически увеличивается на единицу. При завершении работы с объектом должен быть вызван метод Release, уменьшающий счетчик ссылок на единицу. Когда количество ссылок становится нулевым, объект уничтожается. Функция возвращает значение обновленного счетчика ссылок. Release - освобождение объектаULONG IUnknown::Release (); Уменьшает счетчик ссылок объекта на единицу. При обнулении счетчика объект уничтожается. Метод должен вызываться для всех объектов, полученных приложением, при завершении работы с ними. Интерфейс IDirectSoundОбслуживает объекты устройств воспроизведения. Содержит следующие методы:
Initialize - инициализация устройстваHRESULT IDirectSound::Initialize ( LPCGUID GUID );
Метод используется для объектов, созданных не специальной функциями Create, а стандартным для COM методом CoCreateInstance. Функции Create возвращают инициализированный объект, в то время как CoCreateInstance - объект-заготовку, который не связан с конкретным устройством. SetCooperativeLevel - установка уровня взаимодействияHRESULT IDirectSound::SetCooperativeLevel ( HWND Win, DWORD Level );
CreateSoundBuffer - создание звукового буфераHRESULT IDirectSound::CreateSoundBuffer ( LPCDSBUFFERDESC BufferDesc, LPDIRECTSOUNDBUFFER *Buffer, IUnknown *Outer );
Метод создает звуковой - первичный или вторичный буфер, в зависимости от состояния флага DSBCAPS_PRIMARYBUFFER. При создании буфера должны быть указаны способы управления (флаги DSBCAPS_CTRLxxx), которые будут использоваться при проигрывании буфера. Если заданы флаги DSBCAPS_LOCHARDWARE, DSBCAPS_LOCSOFTWARE, подсистема сразу же пытается разместить буфер в памяти определенного типа. Если задан флаг DSBCAPS_LOCDEFER, размещение буфера откладывается до запуска его звучания. DuplicateSoundBuffer - создание копии объекта буфераHRESULT IDirectSound::DuplicateSoundBuffer ( LPDIRECTSOUNDBUFFER Original, LPLPDIRECTSOUNDBUFFER Duplicate );
Метод служит для создания копии объекта, описывающего звуковой буфер, и используется для создания источников, разделяющих между собой один звуковой фрагмент. Оба объекта ссылаются на один и тот же участок памяти, и изначально их параметры полностью совпадают. Впоследствии приложение может изменять параметры каждого из объектов, что дает соответственное изменение звучания. Например, если это звук автомобиля, то методом копирования объектов можно создать звучания нескольких одинаковых автомобилей, различающихся громкостью, высотой, положением в пространстве и т.п., не затрачивая дополнительную память для самой звуковой оцифровки. GetCaps - запрос параметров и состояния устройстваHRESULT IDirectSound::GetCaps ( DSCAPS *Caps );
GetSpeakerConfig - запрос конфигурации звукоизлучателейHRESULT IDirectSound::GetSpeakerConfig ( DWORD *Config );
В конфигурации STEREO добавляется код расположения громкоговорителей. Имена значений кодов имеют префикс DSSPEAKER_GEOMETRY_:
Для выделения из двойного слова кодов конфигурации и расположения используются макросы DSSPEAKER_CONFIG и DSSPEAKER_GEOMETRY. SetSpeakerConfig - установка конфигурации звукоизлучателейHRESULT IDirectSound::SetSpeakerConfig ( DWORD Config );
Метод устанавливает новую конфигурацию звукоизлучателей, которая будет учитываться при создании результирующей звуковой картины. Для объединения кодов конфигурации и расположения используется макрос DSSPEAKER_COMBINED (Config, Geometry). Compact - уплотнение внутренней памяти адаптераHRESULT IDirectSound::Compact (); Метод выполняет уплотнение внутренней памяти адаптера, устраняя фрагментацию и собирая все неиспользуемые фрагменты в один непрерывный участок. Для использования метода приложение должно иметь уровень взаимодействия не ниже приоритетного. Интерфейс IDirectSoundBufferОбслуживает звуковые буферы устройства воспроизведения. Содержит следующие методы:
Initialize - инициализация объекта буфераHRESULT IDirectSoundBuffer::Initialize ( DIRECTSOUND *DevObj, LPCDSBUFFERDESC BufferDesc );
Метод инициализирует объект звукового буфера в соответствии с заданным описателем. При создании буфера методом IDirectSound::CreateSoundBuffer возвращается уже инициализированный объект буфера. Метод IDirectSoundBuffer::Initialize предназначен для унификации и будущих расширений интерфейса. Restore - восстановление памяти потерянного буфераHRESULT IDirectSoundBuffer::Restore (); Метод восстанавливает память и параметры потерянного звукового буфера. Попытка восстановления буфера также может завершиться ошибкой DSERR_BUFFERLOST; это означает, что текущим в данный момент по-прежнему является приложение с более высоким уровнем взаимодействия. В этом случае необходимо дождаться, пока приложение само станет текущим либо конкурирующее приложение снизит свой уровень. После успешного восстановления буфера его содержимое не определено, поэтому необходимо вновь заполнить буфер. Поскольку любая попытка заполнения или запуска буфера может закончиться ошибкой DSERR_BUFFERLOST, рекомендуется организовать эту работу в цикле, работающем до успешного заполнения и запуска буфера, и при неудачах - дожидающегося смены состояния или просто делающего небольшие паузы. GetCaps - запрос параметров буфераHRESULT IDirectSoundBuffer::GetCaps ( DSBCAPS *Caps );
Метод заполняет заданный описатель параметрами буфера, по которым можно судить о его размещении и возможностях использования. Поле флагов описывает реальные параметры размещения буфера, которые могут и не совпадать с запрошенными в описателе DSBUFFERDESC при создании буфера. GetFormat - запрос текущего формата буфераHRESULT IDirectSoundBuffer::GetFormat ( WAVEFORMATEX *Format, DWORD SizeAllocated, DWORD *SizeWritten );
Подсистема заполняет не более, чем SizeAllocated, байтов указанной области памяти. Если описатель не помещается в отведенную область, он обрезается. Если указатель области имеет нулевое значение, в переменную по указателю SizeWritten заносится размер области памяти, необходимый для размещения полного описателя. SetFormat - установка формата буфераHRESULT IDirectSoundBuffer::SetFormat ( LPCWAVEFORMATEX Format );
Метод устанавливает новый формат первичного звукового буфера. Для вторичных буферов задание формата возможно только при их создании; формат существующего объекта вторичного буфера изменить невозможно. Право установки формата первичного буфера имеют только приложения с уровнем взаимодействия не ниже приоритетного. Подсистема поддерживает только форматы PCM. На уровне доступа к первичному буферу перед сменой формата буфера необходимо остановить его работу методом Stop. На более низких уровнях, где нет прямого доступа приложения к буферу, подсистема выполняет остановку и перезапуск самостоятельно. Если первичный буфер не поддерживает устанавливаемый формат, ошибки не возникает. В этом случае подсистема устанавливает наиболее близкий формат и прозрачно преобразует звуковые данные при их занесении в буфер. Определить реально установленный формат можно при помощи метода GetFormat. Lock - запрос обновления данных в буфереHRESULT IDirectSoundBuffer::Lock ( DWORD WriteCursor, DWORD WriteBytes, LPVOID *Ptr1, DWORD *Bytes1, LPVOID *Ptr2, DWORD *Bytes2, DWORD Flags );
При задании нулевых значений в параметрах Ptr1 и Bytes1 подсистема предоставляет доступ только к непрерывной части буфера, не выполняя <заворачивание> через границу. Метод открывает процедуру обновления данных в буфере. Не гарантируется, что возвращенные указатели будут ссылаться внутрь самого буфера и предоставленный участок памяти будет содержать какие-либо звуковые данные. При запросе обновления программного буфера метод действительно возвращает указатели на его участки, однако при работе с аппаратным буфером, размещенным в памяти адаптера, к которой нет прямого доступа со стороны процессора, подсистема вынуждена создавать временный буфер в основной памяти, указатели на который и возвращаются методом. При успешном завершении метода приложению необходимо в кратчайший срок занести в предоставленные участки буфера нужные звуковые данные, после чего вызвать метод Unlock, который завершает процедуру обновления и, если данные были записаны во временный буфер, - пересылает их в память адаптера. Недостаточно быстрое заполнение буфера может привести к его опустошению и сбоям в звучании. Unlock - завершение обновления данных в буфереHRESULT IDirectSoundBuffer::Unlock ( VOID *Ptr1, DWORD Bytes1, VOID *Ptr2, DWORD Bytes2 );
Метод завершает процедуру обновления данных в буфере. Если буфер расположен в памяти адаптера и к нему нет прямого доступа со стороны процессора, метод выполняет пересылку данных из временного буфера в нужный участок памяти адаптера. Это может потребовать времени, количество которого можно оценить при помощи поля dwUnlockTransferRate описателя параметров буфера. Play - запуск звучания буфераHRESULT IDirectSoundBuffer::Play ( DWORD Reserved, DWORD Priority, DWORD Flags );
Флаги условий досрочного завершения звучания гарантируют, что источник будет смешиваться аппаратно, однако при нехватке ресурсов его звучание может быть досрочно прекращено. Имена констант имеют префикс DSBPLAY_TERMINATEBY_:
Флаги LOCHARDWARE и LOCSOFTWARE являются взаимоисключающими. Эти два флага, а также флаги TERMINATEBY допускаются только для буферов с отложенным размещением (флаг DSBCAPS_LOCDEFER). Метод запускает проигрывание буфера с текущей позиции воспроизведения. Для приложений, не имеющих прямого доступа к первичному буферу, первичный буфер создается и запускается автоматически при запуске первого вторичного буфера. Приложения, работающие на уровне доступа к первичному буферу, должны сами запускать его, указывая флаг LOOPING. Приложения более низкого уровня доступа могут использовать метод для гарантированного запуска первичного буфера, чтобы при отсутствии активных вторичных буферов адаптер не выключался (в первичном буфере в это время - <тишина>). Это позволяет избежать лишних включений/выключений адаптера и связанных с этим помех. По сути, активность первичного буфера для таких приложений зависит от внутреннего счетчика, к которому каждый последующий запуск любого буфера добавляет единицу, а остановка любого буфера - вычитает ее. При нулевом значении счетчика первичный буфер останавливается и адаптер выключается. Если буфер уже активизирован, метод лишь обновляет флаги режимов проигрывания, не затрагивая текущей позиции буфера. Перед первым с момента создания объекта устройства обращением к методу Play должен быть установлен уровень взаимодействия методом IDirectSound::SetCooperativeLevel. В противном случае метод Play завершается успешно, но звук появляется только после установки уровня взаимодействия. Stop - прекращение проигрывания буфераHRESULT IDirectSoundBuffer::Stop (); Метод останавливает проигрывание буфера. Для вторичного буфера сохраняется текущая позиция воспроизведения. Для первичного буфера, к которому приложение имеет уровень прямого доступа, позиция при остановке сбрасывается в нуль. Для приложений менее высоких уровней взаимодействия метод уменьшает счетчик активности первичного буфера на единицу; то же самое происходит и при остановке любого из вторичных буферов. При достижении счетчиком нулевого значения первичный буфер останавливается. При ненулевом значении счетчика и отсутствии активных вторичных буферов в первичном буфере проигрывается тишина. GetStatus - запрос состояния буфераHRESULT IDirectSoundBuffer::GetStatus ( DWORD *Status );
Флаги LOCHARDWARE и LOCSOFTWARE являются взаимоисключающими. Они, а также флаг TERMINATED могут быть установлены только для буферов с отложенным размещением (флаг DSBCAPS_LOCDEFER). GetCurrentPosition - запрос текущих позиций буфераHRESULT IDirectSoundBuffer::GetCurrentPosition ( DWORD *PlayCursor, DWORD *WriteCursor );
Позиции в буфере возвращаются в виде байтовых смещений. Если какая-либо позиция не нужна, соответствующий указатель может быть нулевым. Позиция записи обычно опережает позицию воспроизведения на 10-15 мс. Участок буфера начиная с позиции записи и предшествующей (по правилу закольцовки буфера) позиции воспроизведения считается уже проигранным и может быть обновлен в любой момент. SetCurrentPosition - установка позиции воспроизведения буфераHRESULT IDirectSoundBuffer::SetCurrentPosition ( DWORD PlayPosition );
Метод допустим только для вторичных буферов. Проигрывание первичного буфера всегда начинается с его начала. GetFrequency - запрос частоты дискретизацииHRESULT IDirectSoundBuffer::GetFrequency ( DWORD *Frequency );
SetFrequency - установка частоты дискретизацииHRESULT IDirectSoundBuffer::SetFrequency ( DWORD Frequency );
Метод применим только к вторичным буферам. Изменение частоты дискретизации изменяет скорость воспроизведения звука и, как следствие, его высоту. Воспроизведение звука с частотой дискретизации, отличной от используемой в первичном буфере, требует интерполяции отсчетов и при отсутствии средств аппаратного ускорения может заметно увеличить накладные расходы системы. GetPan - запрос текущего положения источника на панорамеHRESULT IDirectSoundBuffer::GetPan ( LONG *Pan );
SetPan - установка текущего положения источника на панорамеHRESULT IDirectSoundBuffer::SetPan ( LONG Pan );
GetVolume - запрос уровня громкости источникаHRESULT IDirectSoundBuffer::GetVolume ( LONG *Volume );
SetVolume - установка уровня громкости источникаHRESULT IDirectSoundBuffer::SetVolume ( LONG Volume );
Интерфейс IDirectSoundCaptureОбслуживает объекты устройств захвата (записи). Поскольку процессы воспроизведения и захвата в большинстве своем симметричны, методы интерфейса подобны методам IDirectSound. Разница заключается лишь в типизации параметров и структур. Интерфейс содержит следующие методы:
Initialize - инициализация объекта устройстваHRESULT IDirectSoundCapture::Initialize ( LPCGUID GUID ); Подобен методу IDirectSound::Initialize. GetCaps - запрос параметров устройстваHRESULT IDirectSoundCapture::GetCaps ( DSCCAPS *Caps );
Подобен методу IDirectSound::GetCaps. CreateCaptureBuffer - создание буфера захватаHRESULT IDirectSoundCapture::CreateCaptureBuffer ( DSCBUFFERDESC *BufferDesc, LPDIRECTSOUNDCAPTUREBUFFER *Buffer, UNKNOWN *Outer );
Подобен методу IDirectSound::CreateSoundBuffer. В отличие от процесса воспроизведения, допускающего смешивание нескольких источников, процесс захвата допускает наличие только одного буфера, и здесь нет разделения на первичный и вторичные буферы. Интерфейс IDirectSoundCaptureBufferОбслуживает буфер захвата. Подобен интерфейсу IDirectSoundBuffer, однако для каждого устройства может существовать только один буфер захвата; и нет разделения на первичный и вторичные буферы. Интерфейс содержит следующие методы:
Initialize - инициализация объекта буфераHRESULT IDirectSoundCaptureBuffer::Initialize ( DIRECTSOUNDCAPTURE *CaptureDev, LPCDSCBUFFERDESC BufferDesc );
Метод подобен IDirectSoundBuffer::Initialize. GetCaps - запрос параметров буфераHRESULT IDirectSoundCaptureBuffer::GetCaps ( DSCBCAPS *Caps );
Метод подобен IDirectSoundBuffer::GetCaps. GetFormat - запрос формата буфераHRESULT IDirectSoundCaptureBuffer::GetFormat ( WAVEFORMATEX *Format, DWORD SizeAllocated, DWORD *SizeWritten ); Рубрика: Direct3D
< |