Share via


Adicionar som

Observação

Este tópico faz parte da série de tutoriais Criar um jogo de Plataforma Universal do Windows simples (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ê for novo no XAudio2, incluímos uma breve introdução em Conceitos de áudio.

Observação

Se você não baixou o código de jogo mais recente para este exemplo, vá para o jogo de exemplo direct3D. Este exemplo faz parte de uma grande coleção de exemplos de recursos UWP. Para obter instruções sobre como baixar o exemplo, consulte Aplicativos de exemplo para desenvolvimento do Windows.

Objetivo

Adicione sons ao jogo de exemplo 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 som, caso o jogo seja pausado ou desativado.
  • MediaReader.h/.cpp: define os métodos para ler arquivos .wav de áudio do armazenamento local.
  • SoundEffect.h/.cpp: define um objeto para reprodução de som no jogo.

Visão geral

Há três main partes 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 . Portanto, vamos primeiro examinar esse método e, em seguida, aprofundar-se em mais detalhes em cada uma das seções.

Depois de configurar, aprendemos a disparar os efeitos sonoros a serem reproduzidos. Para obter mais informações, acesse Reproduzir o som.

Método Simple3DGame::Initialize

Em Simple3DGame::Initialize, em que 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úsicas em segundo plano para seu 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 . O 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 são associados ao objeto de jogo. Portanto, quando ocorre uma colisão com esse objeto de jogo, ele dispara o efeito sonoro a ser reproduzido. Neste jogo de exemplo, temos efeitos sonoros para a munição (com o que usamos para disparar alvos) e para o destino.

  • 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 da 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 de jogo. Portanto, a chamada para 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 efeito sonoro. 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 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, 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 Media Foundation para ler o arquivo de áudio .wav como um buffer de modulação por código de pulso (PCM).

Configurar o Leitor de Origem

  1. Use MFCreateSourceReaderFromURL para criar um leitor de origem de mídia (IMFSourceReader).
  2. Use MFCreateMediaType para criar um objeto de tipo de mídia (IMFMediaType) (mediaType). Ele representa uma descrição de um formato de mídia.
  3. Especifique que a saída decodificada do mediaType é o áudio PCM, que é um tipo de áudio que o 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 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 de 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 Extensible Wave-Format Descriptors.

Ler o fluxo de áudio

  1. Obtenha a duração, em segundos, do fluxo de áudio chamando IMFSourceReader::GetPresentationAttribute e converte a duração em bytes.
  2. Leia o arquivo de áudio em 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 .

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

Reproduzir 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 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, ela dispara 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.

Método SoundEffect::P 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 objetos de jogo. Quando objetos colidem (ou cruzam), ele dispara o efeito sonoro associado a 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óximas etapas

Abordamos a estrutura UWP, elementos gráficos, controles, interface do usuário e á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 desenvolvimento de jogos do Windows 10, use XAudio2 versão 2.9. Esta versão é fornecida com o Windows 10. Para obter mais informações, acesse Versões XAudio2.

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

Vozes XAudio2

Há três tipos de objetos de voz XAudio2: origem, submixe e vozes de masterização. As vozes são os objetos que 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 submixagem enviam sua saída para uma ou mais vozes de submixagem ou masterização.
  • As vozes de submixagem e masterização combinam 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.

Grafo de áudio

O grafo de áudio é uma coleção de vozes XAudio2. O áudio começa em um lado de um grafo de áudio em vozes de origem, opcionalmente passa por uma ou mais vozes de submixagem e termina em uma voz de mestre. Um grafo de áudio conterá uma voz de origem para cada som que está sendo reproduzido no momento, zero ou mais vozes de submixagem e uma voz de domínio. O grafo de áudio mais simples, e o mínimo necessário para fazer um ruído no XAudio2, é uma única voz de origem que é gerada diretamente para uma voz de domínio. Para obter mais informações, acesse Grafos de áudio.

Leituras adicionais

Arquivos .h de áudio de chave

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