Compartilhar via


Adicionar som

Observação

Este tópico faz parte da série de tutoriais Criar um jogo simples da Plataforma Universal do Windows (UWP) com DirectX. O tópico nesse link define o contexto da série.

Neste tópico, criamos um mecanismo de som simples usando APIs XAudio2 . Se você é novo no XAudio2, incluímos uma breve introdução em Conceitos de áudio.

Observação

Se não tiver feito download do código de jogo mais recente para essa amostra, acesse jogo de exemplo do Direct3D. Esta amostra faz parte de uma grande coleção de amostras de recursos UWP. Para obter instruções sobre como fazer o download da amostra, confira Aplicativos de amostra para desenvolvimento do Windows.

Objetivo

Adicione sons ao jogo de exemplo usando o XAudio2.

Definir o mecanismo de áudio

No jogo de exemplo, os objetos e comportamentos de áudio são definidos em três arquivos:

  • Audio.h/.cpp: define o objeto Audio , que contém os recursos XAudio2 para reprodução de som. Ele também define o método para suspender e retomar a reprodução de áudio se o jogo for pausado ou desativado.
  • MediaReader.h/.cpp: define os métodos para ler arquivos de .wav de áudio do armazenamento local.
  • SoundEffect.h/.cpp: Define um objeto para reprodução de som no jogo.

Visão geral

Existem três partes principais na configuração para reprodução de áudio em seu jogo.

  1. Criar e inicializar os recursos de áudio
  2. Carregar arquivo de áudio
  3. Associar som ao objeto

Todos eles são definidos no método Simple3DGame::Initialize . Então, vamos primeiro examinar esse método e depois mergulhar em mais detalhes em cada uma das seções.

Após a configuração, aprendemos como acionar os efeitos sonoros para reproduzir. Para obter mais informações, acesse Reproduzir o som.

Método Simple3DGame::Initialize

Em Simple3DGame::Initialize, onde m_controller e m_renderer também são inicializados, configuramos o mecanismo de áudio e o preparamos para reproduzir sons.

  • Crie m_audioController, que é uma instância da classe Audio .
  • Crie os recursos de áudio necessários usando o método Audio::CreateDeviceIndependentResources . Aqui, dois objetos XAudio2 — um objeto de mecanismo de música e um objeto de mecanismo de som, e uma voz de masterização para cada um deles foram criados. O objeto do mecanismo de música pode ser usado para reproduzir música de fundo para o jogo. O mecanismo de som pode ser usado para reproduzir efeitos sonoros em seu jogo. Para obter mais informações, consulte Criar e inicializar os recursos de áudio.
  • Crie mediaReader, que é uma instância da classe MediaReader . MediaReader, que é uma classe auxiliar para a classe SoundEffect , lê pequenos arquivos de áudio de forma síncrona do local do arquivo e retorna dados de som como uma matriz de bytes.
  • Use MediaReader::LoadMedia para carregar arquivos de som de seu local e criar uma variável targetHitSound para manter os dados de som .wav carregados. Para obter mais informações, consulte Carregar arquivo de áudio.

Os efeitos sonoros estão associados ao objeto do jogo. Então, quando ocorre uma colisão com esse objeto do jogo, ele aciona o efeito sonoro a ser reproduzido. Neste jogo de amostra, temos efeitos sonoros para a munição (o que usamos para atirar em alvos) e para o alvo.

  • Na classe GameObject, há uma propriedade HitSound que é usada para associar o efeito sonoro ao objeto.
  • Crie uma nova instância da classe SoundEffect e inicialize-a. Durante a inicialização, uma voz de origem para o efeito sonoro é criada.
  • Essa classe reproduz um som usando uma voz de masterização fornecida pela classe Audio . Os dados de som são lidos do local do arquivo usando a classe MediaReader . Para obter mais informações, consulte Associar som ao objeto.

Observação

O gatilho real para reproduzir o som é determinado pelo movimento e colisão desses objetos do jogo. Portanto, a chamada para realmente reproduzir esses sons é definida no método Simple3DGame::UpdateDynamics . Para obter mais informações, acesse Reproduzir o som.

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]);
    }
    ...
}

Criar e inicializar os recursos de áudio

  • Use XAudio2Create, uma API XAudio2, para criar dois novos objetos XAudio2 que definem os mecanismos de música e efeitos sonoros. Esse método retorna um ponteiro para a interface IXAudio2 do objeto que gerencia todos os estados do mecanismo de áudio, o thread de processamento de áudio, o grafo de voz e muito mais.
  • Depois que os mecanismos forem instanciados, use IXAudio2::CreateMasteringVoice para criar uma voz de domínio para cada um dos objetos do mecanismo de som.

Para obter mais informações, acesse Como inicializar o XAudio2.

Método 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;
}

Carregar arquivo de áudio

No jogo de exemplo, o código para ler arquivos de formato de áudio é definido em MediaReader.h/cpp__. Para ler um arquivo de áudio .wav codificado, chame MediaReader::LoadMedia, passando o nome do arquivo do .wav como o parâmetro de entrada.

Método MediaReader::LoadMedia

Esse método usa as APIs do Media Foundation para ler o arquivo de áudio .wav como um buffer de PCM (Pulse Code Modulation).

Configurar o Leitor de Origem

  1. Use MFCreateSourceReaderFromURL para criar um leitor de fonte de mídia (IMFSourceReader).
  2. Use MFCreateMediaType para criar um objeto de tipo de mídia (IMFMediaType) (mediaType). Representa uma descrição de um formato de mídia.
  3. Especifique que a saída decodificada do mediaType é áudio PCM, que é um tipo de áudio que o XAudio2 pode usar.
  4. Define o tipo de mídia de saída decodificado para o leitor de origem chamando IMFSourceReader::SetCurrentMediaType.

Para obter mais informações sobre por que usamos o Leitor de origem, acesse Leitor de origem.

Descrever o formato de dados do fluxo de áudio

  1. Use IMFSourceReader::GetCurrentMediaType para obter o tipo de mídia atual para o fluxo.
  2. Use IMFMediaType::MFCreateWaveFormatExFromMFMediaType para converter o tipo de mídia de áudio atual em um buffer WAVEFORMATEX , usando os resultados da operação anterior como entrada. Essa estrutura especifica o formato de dados do fluxo de áudio da onda que é usado depois que o áudio é carregado.

O formato WAVEFORMATEX pode ser usado para descrever o buffer PCM. Em comparação com a estrutura WAVEFORMATEXTENSIBLE , ela só pode ser usada para descrever um subconjunto de formatos de onda de áudio. Para obter mais informações sobre as diferenças entre WAVEFORMATEX e WAVEFORMATEXTENSIBLE, consulte Descritores de formato de onda extensível.

Ler o fluxo de áudio

  1. Obtenha a duração, em segundos, do fluxo de áudio chamando IMFSourceReader::GetPresentationAttribute e converta a duração em bytes.
  2. Leia o arquivo de áudio como um fluxo chamando IMFSourceReader::ReadSample. ReadSample lê o próximo exemplo da fonte de mídia.
  3. Use IMFSample::ConvertToContiguousBuffer para copiar o conteúdo do buffer de exemplo de áudio (exemplo) em uma matriz (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;
}

Associar som ao objeto

A associação de sons ao objeto ocorre quando o jogo é inicializado, no método Simple3DGame::Initialize .

Recapitular:

  • Na classe GameObject, há uma propriedade HitSound que é usada para associar o efeito sonoro ao objeto.
  • Crie uma nova instância do objeto de classe SoundEffect e associe-a ao objeto de jogo. Essa classe reproduz um som usando APIs XAudio2 . Ele usa uma voz de masterização fornecida pela classe Audio . Os dados de som podem ser lidos do local do arquivo usando a classe MediaReader .

SoundEffect::Initialize é usado para inicializar a instância de SoundEffect com os seguintes parâmetros de entrada: ponteiro para o objeto do mecanismo de som (objetos IXAudio2 criados no método Audio::CreateDeviceIndependentResources), ponteiro para o formato do arquivo de .wav usando MediaReader::GetOutputWaveFormatEx e os dados de som carregados usando o método MediaReader::LoadMedia. Durante a inicialização, a voz de origem para o efeito sonoro também é criada.

Método 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;
}

Reproduza o som

Os gatilhos para reproduzir efeitos sonoros são definidos no método Simple3DGame::UpdateDynamics porque é aqui que o movimento dos objetos é atualizado e a colisão entre objetos é determinada.

Como a interação entre os objetos difere muito, dependendo do jogo, não vamos discutir a dinâmica dos objetos do jogo aqui. Se você estiver interessado em entender sua implementação, acesse o método Simple3DGame::UpdateDynamics .

Em princípio, quando ocorre uma colisão, ele aciona o efeito sonoro a ser reproduzido chamando SoundEffect::P laySound. Esse método interrompe todos os efeitos sonoros que estão sendo reproduzidos no momento e enfileira o buffer na memória com os dados de som desejados. Ele usa a voz de origem para definir o volume, enviar dados de som e iniciar a reprodução.

SoundEffect::P método laySound

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()
        );
}

Método Simple3DGame::UpdateDynamics

O método Simple3DGame::UpdateDynamics cuida da interação e da colisão entre os objetos do jogo. Quando os objetos colidem (ou se cruzam), ele aciona a reprodução do efeito sonoro associado.

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
}

Próximas etapas

Cobrimos a estrutura UWP, os gráficos, os controles, a interface do usuário e o áudio de um jogo do Windows 10. A próxima parte deste tutorial, Estendendo o jogo de exemplo, explica outras opções que podem ser usadas ao desenvolver um jogo.

Conceitos de áudio

Para o desenvolvimento de jogos do Windows 10, use o XAudio2 versão 2.9. Esta versão é fornecida com o Windows 10. Para obter mais informações, acesse Versões do XAudio2.

O AudioX2 é uma API de baixo nível que fornece processamento de sinal e base de mixagem. Para obter mais informações, consulte Conceitos principais do XAudio2.

Vozes XAudio2

Há três tipos de objetos de voz XAudio2: vozes de origem, submix e masterização. As vozes são os objetos que o XAudio2 usa para processar, manipular e reproduzir dados de áudio.

  • As vozes de origem operam em dados de áudio fornecidos pelo cliente.
  • As vozes de origem e submix enviam sua saída para uma ou mais vozes de submix ou masterização.
  • As vozes de submixagem e masterização misturam o áudio de todas as vozes que as alimentam e operam no resultado.
  • As vozes de masterização recebem dados de vozes de origem e vozes de submixagem e enviam esses dados para o hardware de áudio.

Para obter mais informações, acesse Vozes XAudio2.

Gráfico de áudio

Audio graph é uma coleção de vozes XAudio2. O áudio começa em um lado de um gráfico de áudio em vozes de origem, opcionalmente passa por uma ou mais vozes de submix e termina em uma voz de masterização. Um gráfico de áudio conterá uma voz de origem para cada som que está sendo reproduzido no momento, zero ou mais vozes de submix e uma voz de masterização. O gráfico de áudio mais simples e o mínimo necessário para fazer um ruído no XAudio2 é uma saída de voz de fonte única diretamente para uma voz de masterização. Para obter mais informações, acesse Gráficos de áudio.

Leituras adicionais

Arquivos .h de áudio chave

Áudio.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;
};

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