Воскресенье, 27.07.2025, 16:29
Приветствую Вас Гость | RSS

Мой сайт

Главная | Регистрация | Вход

Главная » 2013 » Март » 21 » Использование DirectSound
11:57
 

Использование DirectSound

Хотя на первый взгляд DirectSound может показаться сложным, в реальности он не столь труден для понимания и использования. Вы создаете COM-объект, являющийся интерфейсом для звукового оборудования. Этот COM-объект дает вам возможность создавать индивидуальные звуковые буферы (называемые вторичными звуковыми буферами, secondary sound buffer) для хранения звуковых данных.

Данные из этих звуковых буферов смешиваются вместе в главном буфере микширования (называемом первичный звуковой буфер, primary sound buffer) и воспроизводятся в указанном вами звуковом формате. Эти форматы воспроизведения могут отличаться по частоте, количеству каналов и разрядности выборки. Допустимые частоты — 8 000 Гц, 11 025 Гц, 22 050 Гц и 44 100 Гц (CD-качество).

Для количества каналов есть два варианта: один канал для монофонического звука или два канала для стереофонического. Параметры разрядности также ограничиваются двумя вариантами: 8 бит для низкокачественного воспроизведения звука и 16 бит для высококачественного воспроизведения. По умолчанию, если вы не выполняли настройку вручную, для первичного звукового буфера DirectSound установлены следующие параметры: 22 050 Гц, 8-разрядная выборка, стереофонический звук.

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

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

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

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

Чтобы использовать в вашем проекте DirectSound и DirectMusic надо включить заголовочные файлы dsound.h и dmusici.h и добавить в список компоновки файл библиотеки dsound.lib. Также хорошо бы указать при компоновке и библиотеку dxguid.lib, поскольку в ней определены некоторые полезные элементы, которые используются DirectSound.

Таблица 4.1. COM-интерфейсы DirectSound

На рис. 4.4 показаны взаимоотношения между этими объектами. IDirectSound8 — это главный интерфейс из которого вы создаете звуковые буфера (IDirectSoundBuffer8). Затем звуковой буфер может создать собственный интерфейс уведомления (IDirectSoundNotify8), который вы используете для отметки позиции в звуковом буфере по достижении которой будет отправлено уведомление. Интерфейс уведомлений полезен при потоковом воспроизведении звука.

Перед тем, как делать что-либо еще, вам необходимо включить заголовочный файл dsound.h и указать в списке компоновки библиотеку dsound.lib. После этого можно приступать к первому этапу использования DirectSound — созданию объекта IDirectSound8, который является главным интерфейсом, представляющим звуковое оборудование. Делается это с помощью функции DirectSoundCreate8:

Функция DirectSoundCreate8, подобно всем другим функциям DirectSound, возвращает DS_OK, если завершилась успешно, или код ошибки в противном случае. Чтобы контроль ошибок был проще можно для проверки возвращаемого значения использовать макросы FAILED и SUCCEEDED.

Используя функцию DirectSoundCreate8 и глобальный экземпляр объекта IDirectSound8, можно инициализировать объект звуковой системы следующим образом:

Следующий этап инициализации — установка уровня кооперации (cooperative level) объекта IDirectSound8. Вы используете уровень кооперации, чтобы определить как будет осуществляться совместное с другими приложениями использование ресурсов звуковой карты. Хотите ли вы, чтобы карта была полностью в вашем распоряжении, не позволяя никому другому выполнять воспроизведение; или наоборот — хотите разрешить совместный доступ? Или вам нужен специальный формат воспроизведения, который не совпадает с установленным по умолчанию?

Установка уровня кооперации — это работа функции IDirectSound8::SetCooperativeLevel. Есть четыре возможных уровня кооперации, и они перечислены в таблице 4.2. Каждому соответствует собственный макрос, определенный в DirectSound.

Таблица 4.2. Уровни кооперации DirectSound

Вот прототип функции IDirectSound8::SetCooperativeLevel:

Какой уровень кооперации использовать? Вообще-то это зависит от типа создаваемого приложения. Для полноэкранных приложений используйте монопольный уровень. В других случаях я рекомендую приоритетный уровень. Будьте бдительны, когда используете уровень отличный от нормального — помните, что в таком случае вам надо задать формат воспроизведения. Я покажу вам, как это делается в следующем разделе, «Установка формата воспроизведения».

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

А теперь пример установки приоритетного уровня кооперации с использованием ранее инициализированного объекта IDirectSound8:

Последний этап инициализации DirectSound, выполняемый только если вы используете уровень кооперации, отличный от нормального, — захват управления первичным звуковым буфером и установка формата воспроизведения для системы. Процесс делится на две части: сначала вы используете объект IDirectSound8 для создания интерфейса буфера, а затем используете интерфейс для модификации формата.

Объект IDirectSoundBuffer представляет первичный звуковой буфер. Здесь не требуется интерфейс восьмой версии, поскольку система микширования в разных версиях DX не менялась. Звуковой буфер (как первичный, так и вторичный) создает функция IDirectSound8::CreateSoundBuffer, которая выглядит так:

В параметре pcDSBufferDesc передается указатель на структуру DSBUFFERDESC, хранящую различную информацию о создаваемом буфере. Для первичного буфера вы не будете использовать все возможности, предоставляемые объектом звукового буфера, но тем не менее, взглянем на всю структуру целиком:

Назначение полей ясно из их названий, за исключением lpwfxFormat. Это указатель на структуру, описывающую формат воспроизведения создаваемого буфера. Поскольку пока мы не имеем дела с ней, оставим подробное описание на потом. Что касается dwBufferBytes, первичный звуковой буфер уже существует, так что присвойте dwBufferBytes значение 0.

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

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

Таблица 4.3. Флаги для создания звукового буфера

Флаг Описание DSBCAPS_CTRL3D У буфера есть трехмерные возможности. DSBCAPS_CTRLFREQUENCY Позволяет менять частоту в ходе воспроизведения буфера. DSBCAPS_CTRLFX Буфер позволяет обработку эффектов. DSBCAPS_CTRLPAN У буфера есть возможность позиционирования. DSBCAPS_CTRLPOSITIONNOTIFY Буфер может использовать уведомления. DSBCAPS_CTRLVOLUME Позволяет на лету регулировать громкость для буфера. DSBCAPS_GETCURRENTPOSITION2 Позволяет запрашивать буфер о местонахождении позиции воспроизведения. DSBCAPS_GLOBALFOCUS Создает глобальный звуковой буфер; это значит, что воспроизводимый звук будет слышно даже если активна другая программа. DSBCAPS_LOCDEFER Позволяет буферу использовать аппаратные и программные ресурсы. DSBCAPS_LOCHARDWARE Заставляет использовать аппаратные ресурсы, такие как микшер и память звуковой карты. DSBCAPS_LOCSOFTWARE Заставляет использовать программные ресурсы, такие как программное микширование и системная память. DSBCAPS_MUTE3DATMAXDISTANCE Выключает трехмерный звук, если его источник находится слишком далеко от слушателя. DSBCAPS_PRIMARYBUFFER Делает создаваемый буфер первичным звуковым буфером. Используйте этот флаг только один раз и только тогда, когда устанавливаете уровень кооперации отличный от нормального. DSBCAPS_STATIC Если возможно, буфер будет размещен в памяти звуковой карты. Используйте флаг только для небольших звуков. DSBCAPS_STICKYFOCUS Заставляет буфер продолжать воспроизведение, когда пользователь переключается на другое приложение, не использующее DirectSound. Звук будет отключен, независимо от наличия флага.

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

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

Давайте пойдем дальше и создадим интерфейс первичного звукового буфера:

Теперь, когда вы получили возможность управлять первичным звуковым буфером, пришло время установить формат воспроизведения. У вас есть несколько вариантов, но я рекомендую использовать осмысленные значения, такие как 11 025 Гц, 16-разрядная выборка, моно или 22 050 Гц, 16-разрядная выборка, моно.

Выбирая формат, постарайтесь не использовать стереофонию. Она создает ненужную нагрузку на процессор, поскольку настоящие стереофонические звуки достаточно сложно записать. Также старайтесь всегда использовать 16-разрядную выборку, поскольку ее качество значительно выше, чем у 8-разрядной. Никогда не соглашайтесь на меньшее! Что касается частоты, то чем она выше — тем лучше, но не стоит выбирать частоту выше 22 050 Гц. Даже звуки с CD-качеством прекрасно воспроизводятся на 22 050 Гц без заметных потерь.

Все сказано, пойдем дальше. Вы устанавливаете формат воспроизведения через вызов IDirectSoundBuffer::SetFormat:

Функция SetFormat должна вызываться только для объекта первичного звукового буфера.

Первый и единственный аргумент — это указатель на структуру WAVEFORMATEX, содержащую сведения о формате, который вы хотите установить:

Вы должны без труда заполнить эту структуру, за исключением полей nBlockAlign и nAvgBytesPerSec. Переменная nBlockAlign — это количество байт, используемых для каждой выборки звука. Она инициализируется так:

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

Теперь, вооружившись этими сведениями, пришло время попытаться применить их! Вот пример установки формата 22 050 Гц, 16 бит, моно:

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

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

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

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

Чтобы начать воспроизведение звукового буфера (с необязательной возможностью зацикленного воспроизведения), вы должны вызвать функцию звукового буфера Play, которая выглядит так:

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

Для первичного звукового буфера вам практически всегда будет требоваться зацикленное воспроизведение, и вот как это можно сделать:

Когда вы закончите работать с первичным звуковым буфером (и со звуковой системой в целом), надо остановить ее, вызвав функцию IDirectSoundBuffer::Stop, у которой нет аргументов:

Не надо останавливать первичный звуковой буфер, пока не будут остановлены все вторичные звуковые буферы. Когда воспроизводится вторичный звуковой буфер, автоматически запускается воспроизведение первичного буфера.

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

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

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

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

Снова вы используете структуру WAVEFORMATEX для хранения формата и структуру DSBUFFERDESC для описания возможностей буфера. Однако на этот раз вы указываете указатель на структуру WAVEFORMATEX внутри структуры DSBUFFERDESC.

Вот пример создания вторичного звукового буфера, использующего формат 22 050 Гц, 16 бит, моно. Я создаю буфер достаточный для хранения двухсекундного звука (поскольку сейчас я не буду помещать в него никаких реальных звуков), с возможностью регулировки громкости, позиционирования и частоты.

Великолепно! Звуковой буфер создан, и теперь мы готовы воспроизводить звуки! Осталась одна задача — поместить звуковые данные в буфер. В распоряжении звуковых буферов есть пара функций: IDirectSoundBuffer8::Lock, которая блокирует буфер звуковых данных и возвращает указатель на область данных, и IDirectSoundBuffer8::Unlock, освобождающая ресурсы, используемые во время операции блокировки.

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

Почему вы получаете два указателя и два размера? Потому что звуковой буфер является кольцевым и для блокировки требуемого количества байтов вам может потребоваться вернуться к началу буфера. Первый указатель — это запрошенная вами позиция, а первый размер — это количество байтов от этой позиции до конца буфера. Второй указатель обычно устанавливается на начало буфера, а второй размер — это оставшееся количество блокируемых байтов, которые простираются за конец звукового буфера. На рис. 4.7 показан пример буфера данных с двумя указателями и размерами.

Вот как выглядит прототип функции блокировки:

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

Во-вторых, посмотрите на переменную dwFlags, которая может принимать три значения: 0 для блокировки запрашиваемого фрагмента, DSBLOCK_FROMWRITECURCOR для блокировки от текущей позиции записи и DSBLOCK_ENTIREBUFFER для блокировки всего буфера, игнорируя указанные смещение и размер.

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

Лучше всего присваивать переменной dwFlags значение 0 - это гарантирует, что будет заблокировано только указанное количество байтов, находящихся в заданном месте. Также, если вы не хотите использовать второй указатель или размер, присвойте соответствующим переменным значения NULL и 0.

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

К данному моменту вы закончили работу с буфером и готовы разблокировать его, чтобы освободить используемые в процессе ресурсы. Это делается с помощью функции IDirectSoundBuffer8::Unlock:

Вам необходимо передать значения (а не указатели на них), полученные при блокировке буфера:

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

Мы подошли к одному примечательному различию — вы должны сообщить звуковому буферу с какой позиции внутри него надо начинать воспроизведение. Обычно первое воспроизведение звука вы начинаете с его начала. Однако остановка звука не сбрасывает позицию воспроизведения, поскольку вы можете включить паузу, а затем вызвать функцию воспроизведения снова, чтобы продолжить прослушивание с того места, на котором в последний раз остановились. Установка позиции воспроизведения легко выполняется с помощью следующей функции:

У функции всего один аргумент — смещение, с которого вы хотите начать воспроизведение звука. Оно должно быть выровнено на размер блока выборки, который был задан при создании буфера. Если каждый раз при воспроизведении вы хотите слушать звук с начала, можно использовать следующий код:

Чтобы остановить воспроизведение просто используйте функцию IDirectSoundBuffer8::Stop:

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

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

DirectSound только делает звук тише. Это выполняется путем задания уровня звука в сотых долях децибел в диапазоне от 0 (полная громкость) до –10 000 (тишина). Проблема в том, что громкость звука может понизиться до полной тишины где-нибудь еще, в зависимости от звуковой системы пользователя.

Для изменения громкости вам достаточно вызвать следующую функцию:

Ее единственный аргумент — это уровень громкости в сотых долях децибел, как я уже говорил. В качестве примера настройки громкости взгляните на приведенный ниже код, который уменьшит громкость на 25 децибел:

Далее идет позиционирование (panning) — это возможность передвигать центр воспроизведения между правым и левым динамиками (как показано на рис. 4.8). Думайте о позиционировании как о регулировке баланса в вашей стереосистеме. Позиционирование определяет, насколько звук будет смещен влево или вправо. Самой дальней левой позиции (работает только левый динамик) соответствует значение –10 000, а самой дальней правой (работает только правый динамик) соответствует 10 000. Все промежуточные значения соответствуют определенному балансу между правым и левым каналами.

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

DirectSound определяет два макроса, представляющих крайний левый и крайний правый уровни позиционирования; это DSBPAN_LEFT и DSBPAN_RIGHT соответственно.

Просто укажите в аргументе lPan требуемый уровень позиционирования. Попробуйте в качестве примера установить значение позиционирования буфера равным –5000, что уменьшит громкость правого динамика на 50 дБ:

Чтобы во время воспроизведения можно было управлять позиционированием звука, при создании звукового буфера необходимо указать флаг DSBCAPS_CTRLPAN.

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

Изменение частоты воспроизведения звукового буфера меняет высоту звука. Представьте, что вы можете превратить голос героя игры в верещание белки слегка изменив частоту! Вы можете использовать ту же запись мужского голоса для имитации женского немного повысив его частоту. Вы не верите? Проверьте сами.

Частота устанавливается следующей функцией:

Вам необходимо только задать в аргументе dwFrequency желаемое значение, например:

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

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

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

При создании звукового буфера используйте флаг DSBCAPS_LOCKSOFTWARE, говорящий DirectSound о необходимости использовать системную память. Это позволит не беспокоиться о потере ресурсов.

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

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

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

Позиции определяются по их смещению в буфере (как показано на рис. 4.10) или с помощью макроса, обозначающего остановку или завершение воспроизведения. Этот макрос определен в DirectSound как DSBPN_OFFSETSTOP.

Вы не можете указать произвольное смещение в буфере; оно должно быть выровнено по размеру блока выборки. Также уведомления должны быть упорядочены от меньших смещений к большим и никогда два уведомления не могут использовать одно и то же смещение. Если вы используете макрос DSBPN_OFFSETSTOP, это уведомление должно устанавливаться последним. Например, если размер блока равен 2 (монофонический звук, 16 бит) и вы попытаетесь установить смещения 4 и 5, то произойдет сбой, потому что смещения 4 и 5 соответствуют одной выборке.

Чтобы получить объект IDirectSoundNotify8 вы запрашиваете его через объект IDirectSoundBuffer8:

Чтобы использовать уведомления, вы должны при создании звукового буфера указать флаг DSBCAPS_CTRLPOSITIONNOTIFY.

У интерфейса уведомления есть только одна функция:

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

Параметр pcPositionNotifies является указателем на массив структур DSBPOSITIONNOTIFY. Взгляните, что представляет собой эта структура и какую информацию содержит:

Ловушка здесь — это использование дескриптора события. У события есть два состояния — произошло (установлено) или не произошло (сброшено). Создавая событие вы объявляете переменную дескриптора и инициализируете ее следующим образом:

Когда вы закончите использовать событие, следует вызвать CloseHandle(hEvent) для освобождения ресурсов.

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

Попытайтесь хранить все события в массиве; позднее вы поймете важность их организации подобным образом.

Я хочу сделать паузу и показать вам, как установить событие и смещение уведомления. Я буду использовать звуковой буфер размером 65 536 байт и создам два события, представляющие середину и конец буфера соответственно:

Теперь буфер готов, можно двигаться дальше, запустить воспроизведение звукового буфера и позволить событиям сыпаться. Далее следует просто поглядывать на события, ожидая их сигнала. Ожидание событий — это работа функции WaitForMultipleObjects:

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

Функция WaitForMultipleObject может сканировать одновременно только 64 объекта, и вам надо следить, чтобы не превысить этот лимит. Функция может также вернуть значение WAIT_FAILED, указывающее что во время ожидания события произошла ошибка. В таком случае просто перезапустите ожидание, и все будет хорошо.

В действительности, чтобы получить из возвращаемого значения номер события требуется дополнительная работа. Надо просто вычесть значение WAIT_OBJECT_0 из возвращенного функцией значения, и вы получите число из диапазона от 0 до количества событий минус единица.

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

Единственная проблема с приведенным выше примером воспроизведения звука заключается в том, что код должен быть помещен в главном цикле обработки сообщений вашей программы и, таким образом, в нем надо сканировать стандартные сообщения Windows. Это возможно, и именно такой способ предпочитает Microsoft в примерах к DirectX SDK.

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

Мне кажется, я слышал вздох. Не волнуйтесь, использовать потоки для работы с событиями не так трудно. Если вы читали главу 1, «Подготовка к работе с книгой», то уже убедились, что создать поток легко. Трудность в том, как обращаться с таким типом установки.

В главе 1 я писал, что для закрытия потока он должен внутри себя вызвать функцию ExitThread. Но как поток узнает, когда это надо делать, если он занят бесконечным сканированием списка сообщений? Решение — добавить еще одно событие, которое будет сообщать о том, что надо закрыть поток!

Чтобы вручную сгенерировать событие вы используете следующий вызов с дескриптором события:

Чтобы сбросить событие используйте следующий вызов функции:

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

Вот и все. Вам остается только вызвать функцию PlaySound, передав ей звуковой буфер с имеющимся в нем звуком; потом вы ждете пока звук не закончится или вызываете функцию StopSound для принудительной остановки. Вы можете освободить ресурсы и закрыть поток, вызвав функцию StopSound даже если воспроизведение звука уже завершено.

Теперь, когда у вас есть доступ к звуковому буферу, где взять звуковые данные? Простейший способ - воспользоваться широко распространенным форматом звуковых файлов от Microsoft, называемым волновые файлы (wave files), и использующим расширение файла .WAV.

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

Взгляните на структуру, которую я создал чтобы хранить заголовок волнового файла для последующего использования:

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

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

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

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

Первая функция, CreateBufferFromWAV, получает указатель на открытый волновой файл, а также указатель на структуру sWaveHeader, которая будет заполнена данными заголовка, полученными из волнового файла. По возвращении из функции CreateBufferFromWAV вы получаете указатель на новый созданный объект IDirectSoundBuffer8, который готов принять звуковые данные, получаемые вызовом функции LoadSoundData. Взгляните на код этих двух функций:

А вот пример функции, которая использует функции CreateBufferFromWAV и LoadSoundData для загрузки волнового файла. После возврата из показанной ниже функции LoadWAV вы получаете готовый для работы звуковой буфер:

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

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

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

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

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

Вот как это все делается:

  1. Создаем звуковой буфер, скажем, размером 65536 байт.

  2. Устанавливаем четыре позиции уведомлений (в конце каждой четверти звукового буфера).

  3. Загружаем в буфер столько данных, сколько поместится.

  4. Запускаем воспроизведение звукового буфера, указав флаг DSBPLAY_LOOP.

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

  6. Чтобы определить, какое событие отмечает конец звука, определите модуль размера звука по размеру буфера (остаток от деления размера звука на размер буфера). Разделите значение модуля на четыре (количество уведомлений) чтобы определить, какое из событий используется для оповещения конечной позиции звука.

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

Код демонстрационной программы Stream вы найдете в главе 6, «Создаем ядро игры». Там вы обнаружите набор рабочих функций, которые можно использовать для потокового воспроизведения звука в собственном проекте.

Весь код примера потокового воспроизведения (называемого Stream) находится на прилагаемом к книге CD-ROM (загляните в папку BookCode\Chap04\Stream). Из-за его длины я не могу привести весь код здесь, но он следует представленной в данном разделе методике. Для потокового воспроизведения звука вызовите функцию PlayStreamedSound с именем звукового файла, который вы хотите воспроизвести, и вункция позаботится за вас обо всем остальном.

Просмотров: 473 | Добавил: exceir | Рейтинг: 0.0/0
Всего комментариев: 0

Статистика


Онлайн всего: 1
Гостей: 1
Пользователей: 0

Форма входа

Поиск

Календарь

«  Март 2013  »
Пн Вт Ср Чт Пт Сб Вс
    123
45678910
11121314151617
18192021222324
25262728293031
Конструктор сайтовuCoz