Partilhar 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, iremos criar um mecanismo de som simples usando as interfaces de programação de aplicações (APIs) XAudio2. Se és novo em XAudio2, incluímos uma breve introdução sobre conceitos de áudio.

Observação

Se ainda não tiver transferido o código mais recente do jogo para este exemplo, aceda ao jogo de exemplo Direct3D . Este exemplo faz parte de uma grande coleção de exemplos de recursos UWP. Para obter instruções sobre como descarregar o exemplo, consulte Aplicações de exemplo para desenvolvimento Windows.

Objetivo

Adicione sons ao jogo de amostra usando 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 de leitura de áudio .wav arquivos do armazenamento local.
  • SoundEffect.h/.cpp: Define um objeto para reprodução de som no jogo.

Visão geral

Há três partes principais na configuração para reprodução de áudio no 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, em seguida, mergulhar em mais detalhes em cada uma das seções.

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

Método Simple3DGame::Initialize

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

  • Create m_audioController, que é uma instância da classe Audio.
  • Crie os recursos de áudio necessários usando o método Audio::CreateDeviceIndependentResources. Foram criados aqui dois objetos XAudio2: um motor de música, um motor de som, e uma voz de masterização para cada um deles. O objeto do motor de música pode ser utilizado para reproduzir música de fundo para o seu jogo. O motor de som pode ser usado para reproduzir efeitos sonoros no 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 armazenar 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. Assim, quando ocorre uma colisão com esse objeto de jogo, ele dispara 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.
  • Esta classe reproduz um som usando uma voz mestre fornecida pela classe Audio. Os dados de som são lidos do local do arquivo usando a classe MediaReader. Para saber mais, veja Associar som ao objeto.

Observação

O gatilho real para reproduzir o som é determinado pelo movimento e colisão desses objetos do jogo. Assim, a definição da chamada para realmente reproduzir esses sons é feita no método Simple3DGame::UpdateDynamics. Para mais informações, aceda a para tocar 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. Este método retorna um ponteiro para a interface IXAudio2 do objeto que gerencia todos os estados do mecanismo de áudio, a thread de processamento de áudio, o gráfico de voz e muito mais.
  • Depois que os mecanismos tiverem sido instanciados, use IXAudio2::CreateMasteringVoice para criar uma voz de masterização para cada um dos objetos do mecanismo de som.

Para obter mais informações, vá para Como: Inicializar 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 utiliza as APIs da Media Foundation para ler o arquivo de áudio .wav como um buffer de PCM (Modulação por Código de Pulsos).

Configurar o leitor de código-fonte

  1. Use MFCreateSourceReaderFromURL para criar um leitor de fonte de mídia (IMFSourceReader).
  2. Use MFCreateMediaType para criar um objeto de tipo de media (IMFMediaType) (mediaType). Ele 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 XAudio2 pode usar.
  4. Define o tipo de mídia de saída decodificada para o leitor de origem chamando IMFSourceReader::SetCurrentMediaType.

Para obter mais informações sobre por que usamos o Source Reader, vá para Source Reader.

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 corrente para um buffer WAVEFORMATEX, utilizando os resultados da operação anterior como entrada. Esta estrutura especifica o formato de dados do fluxo de áudio WAV que é usado depois de o áudio ser 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 Extensible Wave-Format Descriptors.

Leia o fluxo de áudio

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

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

Recapitulação:

  • 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 do jogo. Essa classe reproduz um som usando XAudio2 APIs. Ele usa uma voz de masterização fornecida pela classe Audio. Os dados de som podem ser lidos a partir do local do ficheiro usando a classe MediaReader.

SoundEffect::Initialize é usado para inicializar a instância 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 .wav utilizando o 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;
}

Toque o som

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

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

Em princípio, quando ocorre uma colisão, aciona o efeito sonoro ao chamar SoundEffect::PlaySound. 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.

Método SoundEffect::PlaySound

  • Usa o objeto de voz de origem m_sourceVoice para iniciar a reprodução do buffer de dados de som m_soundData
  • Cria um XAUDIO2_BUFFER, ao qual ele fornece uma referência ao buffer de dados de som e, em seguida, envia-o com uma chamada para IXAudio2SourceVoice::SubmitSourceBuffer.
  • Com os dados de som enfileirados, SoundEffect::PlaySound inicia a reprodução chamando 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()
        );
}

Método Simple3DGame::UpdateDynamics

O método Simple3DGame::UpdateDynamics cuida da interação e colisão entre objetos de jogo. Quando os objetos colidem (ou se cruzam), aciona-se o efeito sonoro associado para ser reproduzido.

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óximos passos

Cobrimos a estrutura UWP, gráficos, controles, interface do usuário e áudio de um jogo do Windows 10. A próxima parte deste tutorial, Estendendo a amostra de jogo, 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, vá para XAudio2 Versões.

AudioX2 é uma API de baixo nível que fornece processamento de sinal e base de mistura. Para obter mais informações, consulte XAudio2 Key Concepts.

Vozes XAudio2

Existem três tipos de objetos de voz XAudio2: origem, submistura e masterização de vozes. 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 submistura enviam sua saída para uma ou mais vozes de submixagem ou masterização.
  • A submixagem e masterização de vozes mistura o áudio de todas as vozes que as fornecem e opera 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, consulte XAudio2 vozes.

Gráfico de áudio

"Áudio gráfico é 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 submistura e termina em uma voz de masterização. Um diagrama de áudio conterá uma voz de origem para cada som atualmente em reprodução, zero ou mais vozes de submixagem e uma voz de masterização. O gráfico de áudio mais simples, e o mínimo necessário para produzir um som no XAudio2, é uma única voz de origem a ser reproduzida diretamente através de uma voz de masterização. Para obter mais informações, vá para Gráficos de áudio.

Leitura adicional

Principais arquivos .h de áudio

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

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