Как воспроизвести файл WAV

Программа содержит несколько исходных файлов: main.cpp, main.h и DXUtil.cpp. Все они являются уникальными для данного проекта, за исключением файла DXUtil.cpp, который является частью набора вспомогательных файлов DirectX SDK.

Проект использует следующие библиотеки: dxguid.lib, winmm.lib и dsound.lib. Вы можете спросить, почему нет библиотеки с именем dmusic.lib. Я не знаю, что вам ответить. Microsoft решила поместить функции DirectMusic в библиотеку DirectSound. Я предполагаю, что это вызвано тем, что они большей частью совместно используют одну и ту же логику.

Что-то я давно не показывал вам новых иллюстраций.

Видно, что WinMain() вызывает функцию bInitializeSoundSystem(). Эта функция инициализации выполняет несколько вызовов функций DirectX для инициализации звуковой системы. Затем программа ожидает события мыши и воспроизводит файл WAV с помощью функции vPlaySound(). Если вы еще этого не сделали, запустите программу и щелкните по ее окну левой кнопкой мыши чтобы воспроизвести файл WAV. Разве вам не понравился этот замечательный тестовый файл WAV? Эй, я знаю, что он не производит особого впечатления. Если вы хотите поэкспериментировать, замените файл testsound.wav одним из ваших собственных звуковых файлов. Программа должна работать с любым WAV-файлом.
Заголовочный файл Main.h
Откройте заголовочный файл main.h и следуйте за мной. Заголовочный файл в этом примере очень простой и короткий. Вот он целиком:

#ifndef MAIN_H
#define MAIN_H
#define STRICT
#include
#include
#include
#include
#include
#include
#include

// Прототипы функций
LRESULT WINAPI fnMessageProcessor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void vCleanup(void);
bool bInitializeSoundSystem(HWND hWnd);
void vPlaySound(void);

// Глобальные звуковые данные
IDirectMusicLoader8 *g_pLoader;
IDirectMusicPerformance8 *g_pPerformance;
IDirectMusicSegment8 *g_pSound;
#endif

Список включаемых файлов не должен преподнести вам много сюрпризов. В основном это обычные включаемые файлы для Windows-программы. Единственный файл, добавляемый специально для DirectMusic — это dmusici.h.

В разделе прототипов функций появились две новые функции: bInitializeSoundSystem() и vPlaySound(). Назначение обоих очевидно из их названий. (Разве вы не любите самодокументируемый код?)

Следующий блок кода заголовочного файла содержит глобальные переменные, которые я использую в программе. Если вы прочитали предыдущий раздел этой главы, они должны выглядеть для вас очень знакомо. Интерфейс g_pLoader используется для загрузки звукового файла, интерфейс g_pPerformance предназначен для воспроизведения звука, а интерфейс g_pSound содержит загружаемые из звукового файла данные.

Файл программы Main.cpp
Главная часть кода расположена в файле программы main.cpp. Пришло время открыть его. Как обычно, нашего внимания требует функция WinMain(). Она содержит стандартный код инициализации приложения Windows и несколько новых вызовов функций. Вот фрагмент кода, который должен заинтересовать вас:

// Инициализация Direct Sound
bRet = bInitializeSoundSystem(hWnd);
if(bRet == 0) {
MessageBox(hWnd, "Initialization Failure",
"Failed to initialize Direct Sound",
MB_ICONEXCLAMATION | MB_OK);
// Сбой в программе, выход
exit(1);
}

Я вызываю функцию bInitializeSoundSystem() сразу после того, как создано окно программы. Функция получает один параметр — дескриптор окна. Если функция возвращает 0, значит ее выполнение закончилось неудачно. В этом случае я вывожу на экран окно с сообщением об ошибке и завершаю работу программы после того, как пользователь щелкнет по кнопке OK. В противном случае все работает как предполагалось и выполнение кода продолжается.

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

Как инициализировать COM
Первая вещь, которую должен сделать код, — инициализация COM. DirectX использует COM-интерфейсы, так что это неизбежное зло (или добро). Если вы не знакомы с COM, я рекомендую вам пойти в любимый книжный магазин и посмотреть несколько книг, посвященных этой технологии. Вызов для инициализации выглядит следующим образом:

CoInitialize(NULL);

Исключительно просто, правда? К счастью, это все, что требуется сделать для инициализации COM. После этого вы можете создавать интерфейсы DirectX.

Создание интерфейса загрузчика
Следующий фрагмент кода создает интерфейс загрузчика. Как вы, возможно, помните, интерфейс загрузчика отвечает за загрузку звуковых данных, таких как файлы WAV и файлы MIDI. Вот как выглядит соответствующий код:

if(FAILED(hResult = CoCreateInstance(CLSID_DirectMusicLoader, NULL,
CLSCTX_INPROC, IID_IDirectMusicLoader8,
(void**) &g_pLoader))) {
return(0);
}

Для получения интерфейса загрузчика я вызываю функцию CoCreateInstance(). Интерфейс IDirectMusicLoader8 использует CLSID CLSID_DirectMusicLoader и идентификатор интерфейса IID_IDirectMusicLoader8. Указатель на интерфейс сохраняется в глобальной переменной g_pLoader.

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

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

if(FAILED(hResult = CoCreateInstance(CLSID_DirectMusicPerformance, NULL,
CLSCTX_INPROC, IID_IDirectMusicPerformance8,
(void**) &g_pPerformance))) {
return(0);
}

И снова для создания интерфейса я применяю функцию CoCreateInstance(). Интерфейс IDirectPerformance8 использует CLSID CLSID_DirectMusicPerformance и идентификатор интерфейса IID_DirectMusicPerformance8. Указатель на интерфейс я сохраняю в глобальной переменной g_pPerformance.

Инициализация аудиосистемы
В отличие от загрузчика, исполнитель требует, чтобы после успешного создания интерфейса была выполнена его инициализация. Эта задача осуществляется функцией IDirectMusicPerformance8::InitAudio(), которая инициализирует исполнителя и устанавливает для него аудио-путь. Вот как выглядит прототип функции:

HRESULT InitAudio(
IDirectMusic** ppDirectMusic,
IDirectSound** ppDirectSound,
HWND hWnd,
DWORD dwDefaultPathType,
DWORD dwPChannelCount,
DWORD dwFlags,
DMUS_AUDIOPARAMS *pParams
);

Первый параметр, ppDirectMusic, позволяет функции возвратить созданный интерфейс DirectMusic. Если значение этого параметра равно NULL, то интерфейс DirectMusic создается и используется внутри объекта исполнителя. Я предпочитаю использовать NULL, поскольку это упрощает код.

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

Третий параметр, hWnd, предназначен для передачи дескриптора того окна для которого создается интерфейс DirectSound. Если значение параметра равно NULL, используется фоновое окно. Я предпочитаю использовать здесь NULL.

DMUS_APATH_DYNAMIC_3D Трехмерный звук
DMUS_APATH_DYNAMIC_MONO Монофонический звук
DMUS_APATH_DYNAMIC_STEREO Стереофонический звук
DMUS_APATH_SHARED_STEREOPLUSREVERB Стереофонический звук с эхом

--------------------------------------------------------------------------------

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

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

Шестой параметр, dwFlags, позволяет вам задать набор функциональных возможностей, которые вы хотите видеть в объекте исполнителя.
DMUS_AUDIOF_3D Трехмерные буферы
DMUS_AUDIOF_ALL Все возможности
DMUS_AUDIOF_BUFFERS Множественные буферы
DMUS_AUDIOF_DMOS Дополнительные DMO (DirectX Media Object)
DMUS_AUDIOF_EAX Эффекты EAX
DMUS_AUDIOF_ENVIRON Моделирование среды
DMUS_AUDIOF_STREAMING Изменяющиеся формы волн

--------------------------------------------------------------------------------

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

Седьмой параметр, pParams, позволяет задать желаемые аудио параметры в виде структуры данных DMUS_AUDIOPARAMS. Я обычно использую значения параметров по умолчанию и указываю здесь NULL.

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

Вот как выглядит код, описываемый в приведенном выше тексте:

// Инициализация аудиосистемы
if(FAILED(hResult = g_pPerformance->InitAudio(
NULL,
NULL,
hWnd,
DMUS_APATH_DYNAMIC_STEREO,
4,
DMUS_AUDIOF_ALL,
NULL
))) {
return(0);
}
// Получение пути по умолчанию
if(FAILED(g_pPerformance->GetDefaultAudioPath(&dmAudioPath)))
return(0);

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

HRESULT SetVolume(
long lVolume,
DWORD dwDuration
);

Первый параметр, lVolume, устанавливает желаемый уровень громкости в сотнях децибел. Допустимы значения от –9600 до 0. Значение 0 соответствует максимальной громкости.

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

Вот как выглядит используемый в примере код:

// Установка громкости
if(FAILED(dmAudioPath->SetVolume(0,0)))
return(0);

Загрузка звукового файла
Вы помните интерфейс загрузчика, который создали минуту назад? Пора снова воспользоваться им для загрузки тестового файла WAV, включенного в сопроводительные файлы. Чтобы сделать это, обратимся к функции LoadObjectFromFile(). Перед тем, как я покажу вам ее прототип, взгляните на код из программы:

// Загрузка звукового файла
if (FAILED(g_pLoader->LoadObjectFromFile(
CLSID_DirectMusicSegment,
IID_IDirectMusicSegment8,
L"testsound.wav",
(LPVOID*) &g_pSound
)))
{
return(0);
}

// Загрузка данных
if (FAILED (g_pSound->Download(g_pPerformance))) {
return(0);
}

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

Прототип функции LoadObjectFromFile() выглядит следующим образом:

HRESULT LoadObjectFromFile(
REFGUID rguidClassID,
REFIID iidInterfaceID,
WCHAR *pwzFilePath,
void ** ppObject
);

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

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

Третий параметр, pwzFilePath, является именем загружаемого файла. В рассматриваемом примере я использую файл testsound.wav. Вы можете заметить букву L перед строкой с именем файла. Я поместил ее потому, что в данном параметре должно передаваться имя в кодировке Unicode (где для представления одного символа используются два байта).

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

Как загрузить сегмент
Теперь вы должны загрузить сегмент в объект исполнителя. Эту задачу выполняет функция IDirectMusicSegment8::Download(), прототип которой выглядит так:

HRESULT Download(
IUnknown* pAudioPath
);

Как приятно и просто. Всего один параметр, который является указателем на интерфейс, в который загружается сегмент. Я в данном параметре передаю указатель на объект исполнителя g_pPerformance.

Функция vPlaySound()
Пример программы обрабатывает события левой кнопки мыши и вызывает при каждом событии функцию vPlaySound(). Обработкой событий занимается функция fnMessageProcessor(), но представляющий интерес код выполняется в функции воспроизведения звука. Давайте взглянем на код этой функции:

void vPlaySound(void)
{
// Воспроизведение звукового сегмента
g_pPerformance->PlaySegmentEx(
g_pSound,
NULL,
NULL,
DMUS_SEGF_DEFAULT | DMUS_SEGF_SECONDARY,
0,
NULL,
NULL,
NULL
);
}

Функция vPlaySound() очень проста. Фактически она состоит из единственного вызова функции IDirectMusicPerformance8::PlaySegmentEx().