Condividi tramite


Aggiungi suono

Annotazioni

Questo argomento fa parte della serie di esercitazioni Creare un semplice gioco UWP (Universal Windows Platform) con DirectX. L'argomento in tale collegamento imposta il contesto per la serie.

In questo argomento viene creato un semplice motore audio usando API di XAudio2. Se sei nuovo/a in XAudio2, abbiamo incluso una breve introduzione sotto concetti audio.

Annotazioni

Se non hai scaricato il codice di gioco più recente per questo esempio, vai al gioco di esempio Direct3D . Questo esempio fa parte di una vasta raccolta di esempi di funzionalità UWP. Per istruzioni su come scaricare l'esempio, vedere Applicazioni di esempio per lo sviluppo di Windows.

Obiettivo

Aggiungi suoni al gioco di esempio utilizzando XAudio2.

Definire il motore audio

Nel gioco di esempio, gli oggetti audio e i comportamenti sono definiti in tre file:

  • Audio.h/.cpp: definisce l'oggetto Audio, che contiene le risorse XAudio2 per la riproduzione del suono. Definisce anche il metodo per sospendere e riprendere la riproduzione audio se il gioco viene sospeso o disattivato.
  • MediaReader.h/.cpp: definisce i metodi per la lettura di file audio .wav dall'archiviazione locale.
  • SoundEffect.h/.cpp: definisce un oggetto per la riproduzione audio nel gioco.

Informazioni generali

La configurazione per la riproduzione audio nel tuo gioco si compone di tre componenti principali.

  1. Creare e inizializzare le risorse audio
  2. Carica il file audio
  3. Associare il suono all'oggetto

Sono tutti definiti nel metodo Simple3DGame::Initialize . Esaminiamo quindi prima questo metodo e quindi esaminiamo altri dettagli in ognuna delle sezioni.

Dopo aver configurato, si apprenderà come attivare gli effetti sonori da riprodurre. Per maggiori informazioni, vai a e riproduci il suono.

Metodo Simple3DGame::Initialize

In Simple3DGame::Initialize, dove vengono inizializzati anche m_controller e m_renderer , impostiamo il motore audio e lo prepariamo per riprodurre suoni.

  • Creare m_audioController, che è un'istanza della classe Audio.
  • Creare le risorse audio necessarie usando il metodo Audio::CreateDeviceIndependentResources . In questo caso sono stati creati due oggetti XAudio2: un oggetto motore musicale, un oggetto motore sonoro e, per ciascuno di essi, una voce di mastering. L'oggetto motore musicale può essere usato per riprodurre musica di sottofondo per il gioco. Il motore audio può essere usato per riprodurre effetti sonori nel gioco. Per altre info, vedi Creare e inizializzare le risorse audio.
  • Crea mediaReader, che è un'istanza della classe MediaReader. MediaReader, che è una classe helper per la classe SoundEffect , legge i file audio di piccole dimensioni in modo sincrono dalla posizione del file e restituisce i dati audio come matrice di byte.
  • Usare MediaReader::LoadMedia per caricare i file audio dalla sua posizione e creare una variabile targetHitSound per contenere i dati .wav caricati. Per altre informazioni, vedi Caricare file audio.

Gli effetti sonori sono associati all'oggetto gioco. Quindi, quando si verifica una collisione con l'oggetto gioco, attiva l'effetto sonoro da riprodurre. In questo gioco di esempio abbiamo effetti sonori per le munizioni (ciò che usiamo per sparare bersagli con) e per il bersaglio.

  • Nella classe GameObject è presente una proprietà HitSound usata per associare l'effetto audio all'oggetto .
  • Creare una nuova istanza della classe SoundEffect e inizializzarla. Durante l'inizializzazione, viene creata una voce di origine per l'effetto audio.
  • Questa classe riproduce un suono usando una voce di mastering fornita dalla classe Audio. I dati audio vengono letti dalla posizione del file utilizzando la classe MediaReader. Per ulteriori informazioni, vedi Associare un suono all'oggetto.

Annotazioni

Il trigger effettivo per riprodurre il suono è determinato dal movimento e dalla collisione di questi oggetti di gioco. Di conseguenza, la chiamata a riprodurre effettivamente questi suoni è definita nel metodo Simple3DGame::UpdateDynamics. Per maggiori informazioni, vai a e riproduci il suono.

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

Creare e inizializzare le risorse audio

  • Usa XAudio2Create, un'API XAudio2, per creare due nuovi oggetti XAudio2 che definiscono i motori di effetti musicali e audio. Questo metodo restituisce un puntatore all'interfaccia IXAudio2 dell'oggetto che gestisce tutti gli stati del motore audio, il thread di elaborazione audio, il grafico vocale e altro ancora.
  • Dopo aver creato un'istanza dei motori, usare IXAudio2::CreateMasteringVoice per creare una voce di mastering per ciascuno degli oggetti del motore audio.

Per ulteriori informazioni, vedere Procedura: Inizializzare XAudio2.

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

Caricare il file audio

Nel gioco di esempio il codice per la lettura dei file di formato audio è definito in MediaReader.h/cpp__. Per leggere un file audio .wav codificato, chiama MediaReader::LoadMedia, passando il nome file del .wav come parametro di input.

Metodo MediaReader::LoadMedia

Questo metodo utilizza le API Media Foundation per leggere il file audio .wav come buffer PCM (Modulazione a Codice di Impulsi).

Configurare il lettore di origine

  1. Usare MFCreateSourceReaderFromURL per creare un lettore di origine multimediale (IMFSourceReader).
  2. Utilizzare MFCreateMediaType per creare un oggetto di tipo media (IMFMediaType) (mediaType). Rappresenta una descrizione di un formato multimediale.
  3. Specificare che l'output decodificato del mediaTypeè l'audio PCM, ovvero un tipo audio che XAudio2 può usare.
  4. Imposta il tipo di supporto di output decodificato per il lettore di origine chiamando IMFSourceReader::SetCurrentMediaType.

Per ulteriori informazioni sul motivo per cui utilizziamo il Lettore di Origine, vai a Lettore di Origine.

Descrivere il formato dei dati del flusso audio

  1. Usare IMFSourceReader::GetCurrentMediaType per ottenere il tipo di supporto corrente per il flusso.
  2. Utilizzare IMFMediaType::MFCreateWaveFormatExFromMFMediaType per convertire il tipo di supporto audio corrente in un buffer WAVEFORMATEX, usando i risultati dell'operazione precedente come input. Questa struttura specifica il formato di dati del flusso audio d'onda utilizzato dopo il caricamento dell'audio.

Il formato WAVEFORMATEX può essere usato per descrivere il buffer PCM. Rispetto alla struttura WAVEFORMATEXTENSIBLE, può essere usata solo per descrivere un sottoinsieme di formati di onda audio. Per ulteriori informazioni sulle differenze tra WAVEFORMATEX e WAVEFORMATEXTENSIBLE, consultare Descrittori Estendibili Wave-Format.

Leggi il flusso audio

  1. Ottieni la durata, in secondi, del flusso audio chiamando IMFSourceReader::GetPresentationAttribute e quindi converti la durata in byte.
  2. Leggi il file audio come un flusso chiamando IMFSourceReader::ReadSample. readSample legge l'esempio successivo dall'origine multimediale.
  3. Usare IMFSample::ConvertToContiguousBuffer per copiare il contenuto del buffer di esempio audio (esempio) in una matrice (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;
}

Associare il suono all'oggetto

L'associazione dei suoni all'oggetto avviene quando il gioco viene inizializzato nel metodo Simple3DGame::Initialize .

Riassunto:

  • Nella classe GameObject è presente una proprietà HitSound usata per associare l'effetto audio all'oggetto .
  • Creare una nuova istanza dell'oggetto classe SoundEffect e associarla all'oggetto gioco. Questa classe riproduce un suono usando API di XAudio2. Utilizza una voce di mastering fornita dalla classe audio . I dati audio possono essere letti dal percorso del file usando la classe MediaReader .

SoundEffect::Initialize viene usato per inizializzare l'istanza di SoundEffect con i parametri di input seguenti: puntatore all'oggetto motore audio (oggetti IXAudio2 creati nel metodo Audio::CreateDeviceIndependentResources), puntatore al formato del file .wav utilizzando MediaReader::GetOutputWaveFormatEx, e i dati audio caricati mediante il metodo MediaReader::LoadMedia. Durante l'inizializzazione, viene creata anche la voce di origine per l'effetto audio.

Il metodo 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;
}

Riprodurre il suono

I trigger per riprodurre effetti sonori sono definiti nel metodo Simple3DGame::UpdateDynamics perché è qui che viene aggiornato il movimento degli oggetti e determinata la collisione tra di essi.

Poiché l'interazione tra oggetti differisce notevolmente, a seconda del gioco, non esamineremo le dinamiche degli oggetti del gioco qui. Se sei interessato a comprenderne l'implementazione, consulta il metodo Simple3DGame::UpdateDynamics.

In linea di principio, quando si verifica una collisione, attiva la riproduzione dell'effetto sonoro chiamando SoundEffect::PlaySound. Questo metodo arresta qualsiasi effetto sonoro attualmente in riproduzione e mette in coda il buffer in memoria con i dati audio desiderati. Usa la voce di origine per impostare il volume, inviare i dati audio e avviare la riproduzione.

Metodo SoundEffect::PlaySound

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

Metodo Simple3DGame::UpdateDynamics

Il metodo Simple3DGame::UpdateDynamics gestisce l'interazione e la collisione tra gli oggetti del gioco. Quando gli oggetti si scontrano (o si incrociano), fanno riprodurre l'effetto sonoro associato.

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
}

Passaggi successivi

Abbiamo trattato il framework UWP, la grafica, i controlli, l'interfaccia utente e l'audio di un gioco di Windows 10. La parte successiva di questa esercitazione, Estensione del gioco di esempio, illustra altre opzioni che possono essere usate durante lo sviluppo di un gioco.

Concetti relativi all'audio

Per lo sviluppo di giochi di Windows 10, usa XAudio2 versione 2.9. Questa versione viene fornita con Windows 10. Per ulteriori informazioni, vedere Versioni XAudio2.

AudioX2 è un'API di basso livello che fornisce l'elaborazione dei segnali e la base di combinazione. Per altre informazioni, vedere concetti chiave di XAudio2.

Voci XAudio2

Esistono tre tipi di oggetti voce XAudio2: voci sorgente, submix e mastering. Le voci sono gli oggetti usati da XAudio2 per elaborare, modificare e riprodurre dati audio.

  • Le voci di origine operano sui dati audio forniti dal client.
  • Le voci sorgente e submix inviano il loro output a una o più voci di submix o di mastering.
  • Le voci submix e mastering mescolano l'audio da tutte le voci che le alimentano e operano sul risultato.
  • Le voci di mastering ricevono dati dalle voci di origine e dalle voci submix e li trasmettono all'hardware audio.

Per ulteriori informazioni, vai a voci XAudio2.

Grafico audio

Il grafico audio è una raccolta di voci XAudio2 . L'audio inizia da un lato di un grafo audio nelle voci di origine, facoltativamente passa attraverso una o più voci di submix e termina in una voce di mastering finale. Un grafico audio conterrà una voce di origine per ogni suono attualmente riprodotto, zero o più voci di submix e una voce di mastering. Il grafico audio più semplice e il minimo necessario per creare un rumore in XAudio2 è una singola voce sorgente che manda direttamente a una voce di mastering. Per altre informazioni, vedere Grafici audio.

Lettura aggiuntiva

File H audio chiave

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