Добавление звука
Примечание.
Этот раздел является частью серии руководств по созданию простой игры универсальная платформа Windows (UWP) с помощью DirectX. Эта ссылка задает контекст для ряда.
В этом разделе мы создадим простой звуковой механизм с помощью API XAudio2 . Если вы не знакомы с XAudio2, мы включили короткий интро в разделе "Аудио".
Примечание.
Если вы не скачали последний игровой код для этого примера, перейдите в пример игры Direct3D. Этот пример является частью большой коллекции примеров функций UWP. Инструкции по скачиванию примера см. в разделе "Примеры приложений для разработки Windows".
Цель
Добавьте звуки в пример игры с помощью XAudio2.
Определение звукового модуля
В примере игры звуковые объекты и поведение определяются в трех файлах:
- Audio.h/.cpp: определяет объект Audio , содержащий ресурсы XAudio2 для воспроизведения звука. Он также определяет метод приостановки и возобновления воспроизведения звука, если игра приостановлена или деактивирована.
- MediaReader.h/.cpp: определяет методы чтения файлов аудио .wav из локального хранилища.
- SoundEffect.h/.cpp: определяет объект для воспроизведения звука в игре.
Обзор
Существует три основных части при настройке воспроизведения звука в игре.
Все они определены в методе Simple3DGame::Initialize . Поэтому сначала рассмотрим этот метод, а затем рассмотрим дополнительные сведения в каждом из разделов.
После настройки мы узнаем, как активировать звуковые эффекты для воспроизведения. Дополнительные сведения см. в разделе "Воспроизведение звука".
Метод Simple3DGame::Initialize
В Simple3DGame::Initialize, где m_controller и m_renderer также инициализированы, мы настроим звуковой механизм и подготовим его к воспроизведению звуков.
- Создайте m_audioController, который является экземпляром класса Audio .
- Создайте ресурсы звука, необходимые с помощью метода Audio::CreateDeviceIndependentResources . Здесь были созданы два объекта XAudio2 — музыкальный модуль и объект звукового модуля, а также главный голос для каждого из них. Объект подсистемы музыки можно использовать для воспроизведения фоновой музыки для игры. Звуковая подсистема может использоваться для воспроизведения звуковых эффектов в игре. Дополнительные сведения см. в разделе "Создание и инициализация звуковых ресурсов".
- Создайте mediaReader, который является экземпляром класса MediaReader . MediaReader, который является вспомогательным классом для класса SoundEffect , считывает небольшие звуковые файлы синхронно из расположения файла и возвращает звуковые данные в виде массива байтов.
- Используйте MediaReader::LoadMedia для загрузки звуковых файлов из своего расположения и создания переменной targetHitSound для хранения загруженных .wav звуковых данных. Дополнительные сведения см. в разделе "Загрузка аудиофайла".
Звуковые эффекты связаны с объектом игры. Таким образом, когда конфликт возникает с этим игровым объектом, он активирует звуковой эффект, который будет воспроизводиться. В этом примере игры мы имеем звуковые эффекты для ammo (то, что мы используем для стрельбы целей с) и для цели.
- В классе GameObject есть свойство HitSound, которое используется для связывания звукового эффекта с объектом.
- Создайте новый экземпляр класса SoundEffect и инициализировать его. Во время инициализации создается исходный голос для звукового эффекта.
- Этот класс воспроизводит звук с помощью эталонного голоса, предоставленного из класса Audio . Звуковые данные считываются из расположения файла с помощью класса MediaReader . Дополнительные сведения см. в разделе "Связывание звука с объектом".
Примечание.
Фактический триггер для воспроизведения звука определяется движением и столкновением этих игровых объектов. Таким образом, вызов на самом деле воспроизводить эти звуки определяется в методе Simple3DGame::UpdateDynamics . Дополнительные сведения см. в разделе "Воспроизведение звука".
void Simple3DGame::Initialize(
_In_ std::shared_ptr<MoveLookController> const& controller,
_In_ std::shared_ptr<GameRenderer> const& renderer
)
{
// The following member is defined in the header file:
// Audio m_audioController;
...
// Create the audio resources needed.
// Two XAudio2 objects are created - one for music engine,
// the other for sound engine. A mastering voice is also
// created for each of the objects.
m_audioController.CreateDeviceIndependentResources();
m_ammo.resize(GameConstants::MaxAmmo);
...
// Create a media reader which is used to read audio files from its file location.
MediaReader mediaReader;
auto targetHitSoundX = mediaReader.LoadMedia(L"Assets\\hit.wav");
// Instantiate the targets for use in the game.
// Each target has a different initial position, size, and orientation.
// But share a common set of material properties.
for (int a = 1; a < GameConstants::MaxTargets; a++)
{
...
// Create a new sound effect object and associate it
// with the game object's (target) HitSound property.
target->HitSound(std::make_shared<SoundEffect>());
// Initialize the sound effect object with
// the sound effect engine, format of the audio wave, and audio data
// During initialization, source voice of this sound effect is also created.
target->HitSound()->Initialize(
m_audioController.SoundEffectEngine(),
mediaReader.GetOutputWaveFormatEx(),
targetHitSoundX
);
...
}
// Instantiate a set of spheres to be used as ammunition for the game
// and set the material properties of the spheres.
auto ammoHitSound = mediaReader.LoadMedia(L"Assets\\bounce.wav");
for (int a = 0; a < GameConstants::MaxAmmo; a++)
{
m_ammo[a] = std::make_shared<Sphere>();
m_ammo[a]->Radius(GameConstants::AmmoRadius);
m_ammo[a]->HitSound(std::make_shared<SoundEffect>());
m_ammo[a]->HitSound()->Initialize(
m_audioController.SoundEffectEngine(),
mediaReader.GetOutputWaveFormatEx(),
ammoHitSound
);
m_ammo[a]->Active(false);
m_renderObjects.push_back(m_ammo[a]);
}
...
}
Создание и инициализация звуковых ресурсов
- Используйте XAudio2Create, API XAudio2, чтобы создать два новых объекта XAudio2, определяющих подсистемы музыки и звуковых эффектов. Этот метод возвращает указатель на интерфейс IXAudio2 объекта, который управляет всеми состояниями звукового модуля, потоком обработки звука, голосовой графом и т. д.
- После создания экземпляров обработчиков используйте IXAudio2::CreateMasteringVoice , чтобы создать главный голос для каждого из объектов подсистемы звука.
Дополнительные сведения см. в разделе "Практическое руководство. Инициализация XAudio2".
Метод Audio::CreateDeviceIndependentResources
void Audio::CreateDeviceIndependentResources()
{
UINT32 flags = 0;
winrt::check_hresult(
XAudio2Create(m_musicEngine.put(), flags)
);
HRESULT hr = m_musicEngine->CreateMasteringVoice(&m_musicMasteringVoice);
if (FAILED(hr))
{
// Unable to create an audio device
m_audioAvailable = false;
return;
}
winrt::check_hresult(
XAudio2Create(m_soundEffectEngine.put(), flags)
);
winrt::check_hresult(
m_soundEffectEngine->CreateMasteringVoice(&m_soundEffectMasteringVoice)
);
m_audioAvailable = true;
}
Загрузка звукового файла
В примере игры код для чтения файлов формата звука определяется в MediaReader.h/cpp__. Чтобы прочитать закодированный .wav звуковой файл, вызовите MediaReader::LoadMedia, передав имя файла .wav в качестве входного параметра.
Метод MediaReader::LoadMedia
Этот метод использует API Media Foundation для чтения в звуковом файле .wav в качестве буфера модуляции кода пульса (PCM).
Настройка средства чтения источника
- Используйте MFCreateSourceReaderFromURL для создания средства чтения источника мультимедиа (IMFSourceReader).
- Используйте MFCreateMediaType для создания объекта типа мультимедиа (IMFMediaType) (mediaType). Он представляет описание формата мультимедиа.
- Укажите, что декодированные выходные данные mediaType являются звуком PCM, который является типом звука, который может использовать XAudio2 .
- Задает тип декодированного выходного носителя для средства чтения источника путем вызова МВФSourceReader::SetCurrentMediaType.
Дополнительные сведения о том, почему мы используем средство чтения источника, перейдите в раздел "Средство чтения источника".
Описание формата данных аудиопотока
- Используйте МВФSourceReader::GetCurrentMediaType , чтобы получить текущий тип носителя для потока.
- Используйте IMFMediaType::MFCreateWaveFormatExFromMFMediaType , чтобы преобразовать текущий тип аудиомедийного носителя в буфер WAVEFORMATEX , используя результаты предыдущей операции в качестве входных данных. Эта структура задает формат данных звукового потока волны, который используется после загрузки звука.
Формат WAVEFORMATEX можно использовать для описания буфера PCM. По сравнению со структурой WAVEFORMATEXTENSIBLE , ее можно использовать только для описания подмножества форматов звуковых волн. Дополнительные сведения о различиях между WAVEFORMATEX и WAVEFORMATEXTENSIBLE см. в описании дескрипторов расширяемого формата волн.
Чтение аудиопотока
- Получите длительность аудиопотока в секундах путем вызова МВФSourceReader::GetPresentationAttribute , а затем преобразует длительность в байты.
- Чтение звукового файла в виде потока путем вызова МВФSourceReader::ReadSample. ReadSample считывает следующий пример из источника мультимедиа.
- Используйте IMFSample::ConvertToContiguousBuffer для копирования содержимого буфера образца звука (пример) в массив (mediaBuffer).
std::vector<byte> MediaReader::LoadMedia(_In_ winrt::hstring const& filename)
{
winrt::check_hresult(
MFStartup(MF_VERSION)
);
// Creates a media source reader.
winrt::com_ptr<IMFSourceReader> reader;
winrt::check_hresult(
MFCreateSourceReaderFromURL(
(m_installedLocationPath + filename).c_str(),
nullptr,
reader.put()
)
);
// Set the decoded output format as PCM.
// XAudio2 on Windows can process PCM and ADPCM-encoded buffers.
// When using MediaFoundation, this sample always decodes into PCM.
winrt::com_ptr<IMFMediaType> mediaType;
winrt::check_hresult(
MFCreateMediaType(mediaType.put())
);
// Define the major category of the media as audio. For more info about major media types,
// go to: https://msdn.microsoft.com/library/windows/desktop/aa367377.aspx
winrt::check_hresult(
mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio)
);
// Define the sub-type of the media as uncompressed PCM audio. For more info about audio sub-types,
// go to: https://msdn.microsoft.com/library/windows/desktop/aa372553.aspx
winrt::check_hresult(
mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM)
);
// Sets the media type for a stream. This media type defines that format that the Source Reader
// produces as output. It can differ from the native format provided by the media source.
// For more info, go to https://msdn.microsoft.com/library/windows/desktop/dd374667.aspx
winrt::check_hresult(
reader->SetCurrentMediaType(static_cast<uint32_t>(MF_SOURCE_READER_FIRST_AUDIO_STREAM), 0, mediaType.get())
);
// Get the current media type for the stream.
// For more info, go to:
// https://msdn.microsoft.com/library/windows/desktop/dd374660.aspx
winrt::com_ptr<IMFMediaType> outputMediaType;
winrt::check_hresult(
reader->GetCurrentMediaType(static_cast<uint32_t>(MF_SOURCE_READER_FIRST_AUDIO_STREAM), outputMediaType.put())
);
// Converts the current media type into the WaveFormatEx buffer structure.
UINT32 size = 0;
WAVEFORMATEX* waveFormat;
winrt::check_hresult(
MFCreateWaveFormatExFromMFMediaType(outputMediaType.get(), &waveFormat, &size)
);
// Copies the waveFormat's block of memory to the starting address of the m_waveFormat variable in MediaReader.
// Then free the waveFormat memory block.
// For more info, go to https://msdn.microsoft.com/library/windows/desktop/aa366535.aspx and
// https://msdn.microsoft.com/library/windows/desktop/ms680722.aspx
CopyMemory(&m_waveFormat, waveFormat, sizeof(m_waveFormat));
CoTaskMemFree(waveFormat);
PROPVARIANT propVariant;
winrt::check_hresult(
reader->GetPresentationAttribute(static_cast<uint32_t>(MF_SOURCE_READER_MEDIASOURCE), MF_PD_DURATION, &propVariant)
);
// 'duration' is in 100ns units; convert to seconds, and round up
// to the nearest whole byte.
LONGLONG duration = propVariant.uhVal.QuadPart;
unsigned int maxStreamLengthInBytes =
static_cast<unsigned int>(
((duration * static_cast<ULONGLONG>(m_waveFormat.nAvgBytesPerSec)) + 10000000) /
10000000
);
std::vector<byte> fileData(maxStreamLengthInBytes);
winrt::com_ptr<IMFSample> sample;
winrt::com_ptr<IMFMediaBuffer> mediaBuffer;
DWORD flags = 0;
int positionInData = 0;
bool done = false;
while (!done)
{
// Read audio data.
...
}
return fileData;
}
Связывание звука с объектом
Связывание звуков с объектом происходит при инициализации игры в методе Simple3DGame::Initialize .
Рекапитуляция:
- В классе GameObject есть свойство HitSound, которое используется для связывания звукового эффекта с объектом.
- Создайте новый экземпляр объекта класса SoundEffect и свяжите его с объектом игры. Этот класс воспроизводит звук с помощью API XAudio2 . В нем используется главный голос, предоставляемый классом Audio . Звуковые данные можно считывать из расположения файла с помощью класса MediaReader .
SoundEffect::Initialize используется для инициализации экземпляра SoundEffect со следующими входными параметрами: указатель на объект подсистемы звука (объекты IXAudio2, созданные в методе Audio::CreateDeviceIndependentResources), указатель на формат файла .wav с помощью метода MediaReader::GetOutputWaveFormatEx и звуковых данных, загруженных с помощью метода MediaReader::LoadMedia. Во время инициализации исходный голос для звукового эффекта также создается.
Метод SoundEffect::Initialize
void SoundEffect::Initialize(
_In_ IXAudio2* masteringEngine,
_In_ WAVEFORMATEX* sourceFormat,
_In_ std::vector<byte> const& soundData)
{
m_soundData = soundData;
if (masteringEngine == nullptr)
{
// Audio is not available so just return.
m_audioAvailable = false;
return;
}
// Create a source voice for this sound effect.
winrt::check_hresult(
masteringEngine->CreateSourceVoice(
&m_sourceVoice,
sourceFormat
)
);
m_audioAvailable = true;
}
Воспроизведение звука
Триггеры для воспроизведения звуковых эффектов определены в методе Simple3DGame::UpdateDynamics , так как это место, где перемещение объектов обновляется и определяется столкновение между объектами.
Так как взаимодействие между объектами сильно отличается, в зависимости от игры, мы не будем обсуждать динамику игровых объектов здесь. Если вы хотите понять свою реализацию, перейдите к методу Simple3DGame::UpdateDynamics .
В принципе, когда возникает столкновение, он активирует звуковой эффект для воспроизведения путем вызова SoundEffect::P laySound. Этот метод останавливает любые звуковые эффекты, которые в настоящее время воспроизводится и помещает буфер в памяти с нужными звуковыми данными. Он использует исходный голос для задания тома, отправки звуковых данных и запуска воспроизведения.
Метод SoundEffect::P laySound
- Использует исходный объект голосовой связи m_sourceVoice для запуска воспроизведения буфера звуковых данных m_soundData
- Создает XAUDIO2_BUFFER, в которую он предоставляет ссылку на буфер звуковых данных, а затем отправляет его с вызовом IXAudio2SourceVoice::SubmitSourceBuffer.
- В очереди звуковых данных SoundEffect::P laySound запускает воспроизведение путем вызова IXAudio2SourceVoice::Start.
void SoundEffect::PlaySound(_In_ float volume)
{
XAUDIO2_BUFFER buffer = { 0 };
if (!m_audioAvailable)
{
// Audio is not available so just return.
return;
}
// Interrupt sound effect if it is currently playing.
winrt::check_hresult(
m_sourceVoice->Stop()
);
winrt::check_hresult(
m_sourceVoice->FlushSourceBuffers()
);
// Queue the memory buffer for playback and start the voice.
buffer.AudioBytes = (UINT32)m_soundData.size();
buffer.pAudioData = m_soundData.data();
buffer.Flags = XAUDIO2_END_OF_STREAM;
winrt::check_hresult(
m_sourceVoice->SetVolume(volume)
);
winrt::check_hresult(
m_sourceVoice->SubmitSourceBuffer(&buffer)
);
winrt::check_hresult(
m_sourceVoice->Start()
);
}
Метод Simple3DGame::UpdateDynamics
Метод Simple3DGame::UpdateDynamics заботится о взаимодействии и столкновении между игровыми объектами. Когда объекты сталкиваются (или пересекаются), он активирует связанный звуковой эффект для воспроизведения.
void Simple3DGame::UpdateDynamics()
{
...
// Check for collisions between ammo.
#pragma region inter-ammo collision detection
if (m_ammoCount > 1)
{
...
// Check collision between instances One and Two.
...
if (distanceSquared < (GameConstants::AmmoSize * GameConstants::AmmoSize))
{
// The two ammo are intersecting.
...
// Start playing the sounds for the impact between the two balls.
m_ammo[one]->PlaySound(impact, m_player->Position());
m_ammo[two]->PlaySound(impact, m_player->Position());
}
}
#pragma endregion
#pragma region Ammo-Object intersections
// Check for intersections between the ammo and the other objects in the scene.
// ...
// Ball is in contact with Object.
// ...
// Make sure that the ball is actually headed towards the object. At grazing angles there
// could appear to be an impact when the ball is actually already hit and moving away.
if (impact > 0.0f)
{
...
// Play the sound associated with the Ammo hitting something.
m_objects[i]->PlaySound(impact, m_player->Position());
if (m_objects[i]->Target() && !m_objects[i]->Hit())
{
// The object is a target and isn't currently hit, so mark
// it as hit and play the sound associated with the impact.
m_objects[i]->Hit(true);
m_objects[i]->HitTime(timeTotal);
m_totalHits++;
m_objects[i]->PlaySound(impact, m_player->Position());
}
...
}
#pragma endregion
#pragma region Apply Gravity and world intersection
// Apply gravity and check for collision against enclosing volume.
...
if (position.z < limit)
{
// The ammo instance hit the a wall in the min Z direction.
// Align the ammo instance to the wall, invert the Z component of the velocity and
// play the impact sound.
position.z = limit;
m_ammo[i]->PlaySound(-velocity.z, m_player->Position());
velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
}
...
#pragma endregion
}
Следующие шаги
Мы рассмотрели платформу UWP, графику, элементы управления, пользовательский интерфейс и звук игры Windows 10. В следующей части этого руководства расширение примера игры объясняется другими вариантами, которые можно использовать при разработке игры.
Основные понятия аудио
Для разработки игр Windows 10 используйте XAudio2 версии 2.9. Эта версия поставляется с Windows 10. Дополнительные сведения см. в версиях XAudio2.
AudioX2 — это низкоуровневый API, обеспечивающий обработку сигналов и основу смешивания. Дополнительные сведения см. в разделе "Основные понятия XAudio2".
Голоса XAudio2
Существует три типа объектов голосовой связи XAudio2: исходные, вложенные и эталонные голоса. Голоса — это объекты XAudio2, используемые для обработки, управления и воспроизведения звуковых данных.
- Исходные голоса работают с звуковыми данными, предоставляемыми клиентом.
- Исходные и вложенные голоса отправляют выходные данные в один или несколько подмиксов или эталонных голосов.
- Подмикс и мастеринг голоса смешивают звук со всех голосов, кормящих их, и работают над результатом.
- Управление голосами получает данные из исходных голосов и вложенных голосов и отправляет эти данные в звуковое оборудование.
Дополнительные сведения см. в разделе "Голоса XAudio2".
Звуковой граф
Аудиограмма — это коллекция голосов XAudio2. Звук начинается с одной стороны звукового графа в исходных голосах, при необходимости проходит через один или несколько вложенных голосов и заканчивается на образном голосе. Звуковой граф будет содержать исходный голос для каждого звука, воспроизводимого в настоящее время, ноль или несколько вложенных голосов и один образец. Самый простой звуковой граф и минимальный, необходимый для шума в XAudio2, — это один исходный вывод голосовых данных непосредственно на главный голос. Дополнительные сведения см. в разделе "Звуковые графы".
Дополнительные материалы
- Практическое руководство. Инициализация XAudio2
- Практическое руководство. Загрузка файлов аудиоданных в XAudio2
- Практическое руководство. Воспроизведение звука с помощью XAudio2
Ключ аудио .h files
Audio.h
// Audio:
// This class uses XAudio2 to provide sound output. It creates two
// engines - one for music and the other for sound effects - each as
// a separate mastering voice.
// The SuspendAudio and ResumeAudio methods can be used to stop
// and start all audio playback.
class Audio
{
public:
Audio();
void Initialize();
void CreateDeviceIndependentResources();
IXAudio2* MusicEngine();
IXAudio2* SoundEffectEngine();
void SuspendAudio();
void ResumeAudio();
private:
...
};
MediaReader.h
// MediaReader:
// This is a helper class for the SoundEffect class. It reads small audio files
// synchronously from the package installed folder and returns sound data as a
// vector of bytes.
class MediaReader
{
public:
MediaReader();
std::vector<byte> LoadMedia(_In_ winrt::hstring const& filename);
WAVEFORMATEX* GetOutputWaveFormatEx();
private:
winrt::Windows::Storage::StorageFolder m_installedLocation{ nullptr };
winrt::hstring m_installedLocationPath;
WAVEFORMATEX m_waveFormat;
};
SoundEffect.h
// SoundEffect:
// This class plays a sound using XAudio2. It uses a mastering voice provided
// from the Audio class. The sound data can be read from disk using the MediaReader
// class.
class SoundEffect
{
public:
SoundEffect();
void Initialize(
_In_ IXAudio2* masteringEngine,
_In_ WAVEFORMATEX* sourceFormat,
_In_ std::vector<byte> const& soundData
);
void PlaySound(_In_ float volume);
private:
...
};