Добавление звука

Примечание

Этот раздел является частью серии учебников Создание простой игры универсальная платформа Windows (UWP) с помощью DirectX. Раздел по этой ссылке задает контекст для ряда.

В этом разделе мы создадим простую подсистему звука с помощью API XAudio2 . Если вы не знакомы с XAudio2, мы включили краткое введение в раздел Основные понятия аудио.

Примечание

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

Назначение

Добавьте звуки в пример игры с помощью XAudio2.

Определение обработчика звука

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

  • Audio.h/.cpp: определяет объект Audio , содержащий ресурсы XAudio2 для воспроизведения звука. Он также определяет метод приостановки и возобновления воспроизведения звука в случаях приостановки или деактивации игры.
  • MediaReader.h/.cpp: определяет методы чтения звуковых WAV-файлов из локального хранилища.
  • SoundEffect.h/.cpp: определяет объект для воспроизведения звука в игре.

Общие сведения

Настройка воспроизведения звука в игре состоит из трех main частей.

  1. Создание и инициализация звуковых ресурсов
  2. Загрузка звукового файла
  3. Связывание звука с объектом

Все они определены в методе Simple3DGame::Initialize . Поэтому давайте сначала рассмотрим этот метод, а затем рассмотрим более подробные сведения в каждом из разделов.

После настройки мы узнаем, как активировать звуковые эффекты для воспроизведения. Дополнительные сведения см. в разделе Воспроизведение звука.

Метод Simple3DGame::Initialize

В Simple3DGame::Initialize, где также инициализированы m_controller и m_renderer , мы настраиваем звуковой модуль и готовим его к воспроизведению звуков.

  • Создайте m_audioController, которая является экземпляром класса Audio .
  • Создайте необходимые звуковые ресурсы с помощью метода Audio::CreateDeviceIndependentResources . Здесь были созданы два объекта XAudio2 — объект обработчика музыки и звуковой подсистемы, а также мастеринг-голос для каждого из них. Объект обработчика музыки можно использовать для воспроизведения фоновой музыки для игры. Звуковой механизм можно использовать для воспроизведения звуковых эффектов в игре. Дополнительные сведения см. в статье Создание и инициализация звуковых ресурсов.
  • Создайте mediaReader, который является экземпляром класса MediaReader . MediaReader, который является вспомогательным классом для класса SoundEffect , синхронно считывает небольшие звуковые файлы из расположения файла и возвращает звуковые данные в виде массива байтов.
  • Используйте MediaReader::LoadMedia для загрузки звуковых файлов из своего расположения и создайте переменную targetHitSound для хранения загруженных звуковых данных WAV. Дополнительные сведения см. в разделе Загрузка звукового файла.

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

  • В классе 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 в виде буфера импульсно-кодовой модуляции (ИКМ).

Настройка средства чтения источника

  1. Используйте MFCreateSourceReaderFromURL для создания средства чтения источника мультимедиа (IMFSourceReader).
  2. Используйте MFCreateMediaType для создания объекта типа мультимедиа (IMFMediaType) (mediaType). Он представляет описание формата мультимедиа.
  3. Укажите, что декодированные выходные данные mediaType — звук PCM, который может использовать XAudio2 .
  4. Задает тип декодированного выходного носителя для средства чтения источника путем вызова IMFSourceReader::SetCurrentMediaType.

Дополнительные сведения о том, почему мы используем средство чтения исходного кода, см. в статье Средство чтения исходного кода.

Описание формата данных аудиопотока

  1. Используйте IMFSourceReader::GetCurrentMediaType , чтобы получить текущий тип мультимедиа для потока.
  2. Используйте IMFMediaType::MFCreateWaveFormatExFromMFMediaType для преобразования текущего типа аудиоданных в буфер WAVEFORMATEX , используя в качестве входных данных результаты предыдущей операции. Эта структура задает формат данных волнового звукового потока, который используется после загрузки звука.

Формат WAVEFORMATEX можно использовать для описания буфера PCM. По сравнению со структурой WAVEFORMATEXTENSIBLE , она может использоваться только для описания подмножества форматов звуковых волн. Дополнительные сведения о различиях между WAVEFORMATEX и WAVEFORMATEXTENSIBLE см. в разделе Расширяемые дескрипторы Wave-Format.

Чтение аудиопотока

  1. Получите длительность аудиопотока в секундах, вызвав IMFSourceReader::GetPresentationAttribute , а затем преобразует длительность в байты.
  2. Считайте звуковой файл в виде потока, вызвав IMFSourceReader::ReadSample. ReadSample считывает следующий пример из источника мультимедиа.
  3. Используйте 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::PlaySound начинает воспроизведение, направляя вызов 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, — это единый исходный голос, который выводится непосредственно в мастеринговый голос. Дополнительные сведения см. в разделе Звуковые графы.

Дополнительные материалы

H-файлы key audio

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:
    ...
};