Partager via


Ajouter du son

Remarque

Cette rubrique fait partie de la série de tutoriels Créer un jeu simple de plateforme Windows universelle simple (UWP) avec DirectX. La rubrique accessible via ce lien définit le contexte de la série.

Dans cette rubrique, nous créons un moteur audio simple à l’aide des API XAudio2 . Si vous débutez avec XAudio2, nous avons inclus une courte introduction sous les concepts audio.

Remarque

Si vous n’avez pas téléchargé le code du jeu le plus récent pour cet exemple, accédez à Exemple de jeu Direct3D. Cet exemple fait partie d’une grande collection d’exemples de fonctionnalités UWP. Pour obtenir des instructions sur le téléchargement de l’exemple, consultez Exemples d’applications pour le développement Windows.

Objectif

Ajoutez des sons dans l’exemple de jeu à l’aide de XAudio2.

Définir le moteur audio

Dans l’exemple de jeu, les objets audio et les comportements sont définis dans trois fichiers :

  • Audio.h/.cpp : définit l’objet Audio, qui contient les ressources XAudio2 pour la lecture audio. Il définit également la méthode de suspension et de reprise de la lecture audio si le jeu est suspendu ou désactivé.
  • MediaReader.h/.cpp : définit les méthodes de lecture des fichiers audio .wav à partir du stockage local.
  • SoundEffect.h/.cpp : définit un objet pour la lecture sonore dans le jeu.

Vue d’ensemble

Il existe trois parties principales dans la configuration de la lecture audio dans votre jeu.

  1. Créer et initialiser les ressources audio
  2. Charger le fichier audio
  3. Associer le son à l’objet

Elles sont toutes définies dans la méthode Simple3DGame ::Initialize . Examinons d’abord cette méthode, puis examinons plus de détails dans chacune des sections.

Après avoir configuré, nous apprenons à déclencher les effets sonores à lire. Pour plus d’informations, accédez à Lire le son.

Simple3DGame ::Initialize, méthode

Dans Simple3DGame ::Initialize, où m_controller et m_renderer sont également initialisés, nous avons configuré le moteur audio et nous l’avons préparé pour lire des sons.

  • Créez m_audioController, qui est une instance de la classe Audio .
  • Créez les ressources audio nécessaires à l’aide de la méthode Audio ::CreateDeviceIndependentResources . Ici, deux objets XAudio2 : un objet moteur de musique et un objet moteur sonore, et une voix de mastering pour chacun d’entre eux ont été créés. L’objet moteur de musique peut être utilisé pour jouer de la musique d’arrière-plan pour votre jeu. Le moteur sonore peut être utilisé pour jouer des effets sonores dans votre jeu. Pour plus d’informations, consultez Créer et initialiser les ressources audio.
  • Créez mediaReader, qui est une instance de la classe MediaReader . MediaReader, qui est une classe d’assistance pour la classe SoundEffect , lit les petits fichiers audio de manière synchrone à partir de l’emplacement du fichier et retourne des données audio sous forme de tableau d’octets.
  • Utilisez MediaReader ::LoadMedia pour charger des fichiers audio à partir de son emplacement et créer une variable targetHitSound pour contenir les données audio chargées .wav. Pour plus d’informations, consultez Charger le fichier audio.

Les effets sonores sont associés à l’objet de jeu. Ainsi, lorsqu’une collision se produit avec cet objet de jeu, elle déclenche l’effet sonore à jouer. Dans cet exemple de jeu, nous avons des effets sonores pour les munitions (ce que nous utilisons pour tirer des cibles avec) et pour la cible.

  • Dans la classe GameObject , il existe une propriété HitSound utilisée pour associer l’effet sonore à l’objet.
  • Créez une instance de la classe SoundEffect et initialisez-la. Lors de l’initialisation, une voix source pour l’effet sonore est créée.
  • Cette classe lit un son à l’aide d’une voix de mastering fournie à partir de la classe Audio . Les données sonores sont lues à partir de l’emplacement du fichier à l’aide de la classe MediaReader . Pour plus d’informations, consultez Associer le son à l’objet.

Remarque

Le déclencheur réel pour jouer le son est déterminé par le mouvement et la collision de ces objets de jeu. Par conséquent, l’appel à lire réellement ces sons est défini dans la méthode Simple3DGame ::UpdateDynamics . Pour plus d’informations, accédez à Lire le son.

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

Créer et initialiser les ressources audio

  • Utilisez XAudio2Create, une API XAudio2, pour créer deux nouveaux objets XAudio2 qui définissent les moteurs de musique et d’effet sonore. Cette méthode retourne un pointeur vers l’interface IXAudio2 de l’objet qui gère tous les états du moteur audio, le thread de traitement audio, le graphique vocal, etc.
  • Une fois les moteurs instanciés, utilisez IXAudio2 ::CreateMasteringVoice pour créer une voix de mastering pour chacun des objets du moteur sonore.

Pour plus d’informations, accédez à How to : Initialize XAudio2.

Audio ::CreateDeviceIndependentResources, méthode

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

Charger le fichier audio

Dans l’exemple de jeu, le code de lecture des fichiers de format audio est défini dans MediaReader.h/cpp__. Pour lire un fichier audio encodé .wav, appelez MediaReader ::LoadMedia, en passant le nom de fichier du .wav comme paramètre d’entrée.

MediaReader ::LoadMedia, méthode

Cette méthode utilise les API Media Foundation pour lire dans le fichier audio .wav en tant que mémoire tampon PCM (Pulse Code Modulation).

Configurer le lecteur source

  1. Utilisez MFCreateSourceReaderFromURL pour créer un lecteur de source multimédia (IMFSourceReader).
  2. Utilisez MFCreateMediaType pour créer un objet media type (IMFMediaType) (mediaType). Il représente une description d’un format multimédia.
  3. Spécifiez que la sortie décodée du mediaType est l’audio PCM, qui est un type audio que XAudio2 peut utiliser.
  4. Définit le type de média de sortie décodé pour le lecteur source en appelant IMFSourceReader ::SetCurrentMediaType.

Pour plus d’informations sur la raison pour laquelle nous utilisons le lecteur source, accédez au lecteur source.

Décrire le format de données du flux audio

  1. Utilisez IMFSourceReader ::GetCurrentMediaType pour obtenir le type de média actuel pour le flux.
  2. Utilisez IMFMediaType ::MFCreateWaveFormatExFromMFMediaType pour convertir le type de média audio actuel en mémoire tampon WAVEFORMATEX , en utilisant les résultats de l’opération précédente comme entrée. Cette structure spécifie le format de données du flux audio d’onde utilisé après le chargement audio.

Le format WAVEFORMATEX peut être utilisé pour décrire la mémoire tampon PCM. Par rapport à la structure WAVEFORMATEXTENSIBLE , elle ne peut être utilisée que pour décrire un sous-ensemble de formats d’ondes audio. Pour plus d’informations sur les différences entre WAVEFORMATEX et WAVEFORMATEXTENSIBLE, consultez les descripteurs extensibles de format wave-format.

Lire le flux audio

  1. Obtenez la durée, en secondes, du flux audio en appelant IMFSourceReader ::GetPresentationAttribute , puis convertissez la durée en octets.
  2. Lisez le fichier audio dans un flux en appelant IMFSourceReader ::ReadSample. ReadSample lit l’exemple suivant à partir de la source multimédia.
  3. Utilisez IMFSample ::ConvertToContiguousBuffer pour copier le contenu de la mémoire tampon de l’exemple audio (exemple) dans un tableau (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;
}

Associer le son à l’objet

L’association de sons à l’objet se produit lorsque le jeu initialise, dans la méthode Simple3DGame ::Initialize .

Récapituler:

  • Dans la classe GameObject , il existe une propriété HitSound utilisée pour associer l’effet sonore à l’objet.
  • Créez une instance de l’objet de classe SoundEffect et associez-le à l’objet de jeu. Cette classe lit un son à l’aide des API XAudio2 . Il utilise une voix de maîtrise fournie par la classe Audio . Les données sonores peuvent être lues à partir de l’emplacement du fichier à l’aide de la classe MediaReader .

SoundEffect ::Initialize est utilisé pour initaliser l’instance SoundEffect avec les paramètres d’entrée suivants : pointeur vers l’objet moteur audio (objets IXAudio2 créés dans la méthode Audio ::CreateDeviceIndependentResources), pointeur vers le format du fichier .wav à l’aide de MediaReader ::GetOutputWaveFormatEx et les données sonores chargées à l’aide de la méthode MediaReader ::LoadMedia. Lors de l’initialisation, la voix source de l’effet sonore est également créée.

SoundEffect ::Initialize, méthode

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

Lire le son

Les déclencheurs pour lire des effets sonores sont définis dans la méthode Simple3DGame ::UpdateDynamics , car c’est là que le déplacement des objets est mis à jour et que la collision entre les objets est déterminée.

Étant donné que l’interaction entre les objets diffère considérablement, selon le jeu, nous ne allons pas discuter de la dynamique des objets de jeu ici. Si vous souhaitez comprendre son implémentation, accédez à la méthode Simple3DGame ::UpdateDynamics .

En principe, lorsqu’une collision se produit, elle déclenche l’effet sonore à jouer en appelant SoundEffect ::P laySound. Cette méthode arrête tous les effets sonores actuellement en cours de lecture et met en file d’attente la mémoire tampon en mémoire avec les données sonores souhaitées. Il utilise la voix source pour définir le volume, envoyer des données sonores et démarrer la lecture.

SoundEffect ::P laySound, méthode

  • Utilise l’objet vocal source m_sourceVoice pour démarrer la lecture de la mémoire tampon de données audio m_soundData
  • Crée un XAUDIO2_BUFFER, auquel il fournit une référence à la mémoire tampon de données sonores, puis l’envoie avec un appel à IXAudio2SourceVoice ::SubmitSourceBuffer.
  • Avec les données sonores mises en file d’attente, SoundEffect ::P laySound commence à être lu en appelant 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, méthode

La méthode Simple3DGame ::UpdateDynamics prend en charge l’interaction et la collision entre les objets de jeu. Lorsque les objets entrent en collision (ou se croisent), il déclenche l’effet sonore associé à lire.

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
}

Étapes suivantes

Nous avons abordé l’infrastructure UWP, les graphismes, les contrôles, l’interface utilisateur et l’audio d’un jeu Windows 10. La partie suivante de ce didacticiel, Extension de l’exemple de jeu, explique d’autres options qui peuvent être utilisées lors du développement d’un jeu.

Concepts audio

Pour le développement de jeux Windows 10, utilisez XAudio2 version 2.9. Cette version est fournie avec Windows 10. Pour plus d’informations, accédez à XAudio2 Versions.

AudioX2 est une API de bas niveau qui fournit un traitement de signal et une base de mixage. Pour plus d’informations, consultez Concepts clés XAudio2.

Voix XAudio2

Il existe trois types d’objets vocaux XAudio2 : la source, le sous-mélange et la maîtrise des voix. Les voix sont les objets que XAudio2 utilise pour traiter, manipuler et lire des données audio.

  • Les voix sources fonctionnent sur les données audio fournies par le client.
  • Les voix sources et de sous-mélange envoient leur sortie à une ou plusieurs voix de sous-mélange ou de mastering.
  • Les voix de sous-mixage et de mastering mélangent l’audio de toutes les voix qui les alimentent, et fonctionnent sur le résultat.
  • La maîtrise des voix reçoit des données des voix sources et des voix de sous-mélange, et envoie ces données au matériel audio.

Pour plus d’informations, accédez aux voix XAudio2.

Graphique audio

Le graphique audio est une collection de voix XAudio2. L’audio commence à un côté d’un graphique audio dans les voix sources, passe éventuellement par une ou plusieurs voix de sous-mixage et se termine par une voix de mastering. Un graphe audio contient une voix source pour chaque son en cours de lecture, zéro ou plusieurs voix de sous-mélange et une voix de maîtrise. Le graphique audio le plus simple, et le minimum nécessaire pour faire un bruit dans XAudio2, est une voix source unique en sortie directe vers une voix de mastering. Pour plus d’informations, accédez aux graphiques audio.

Lectures supplémentaires

Fichiers .h audio de clé

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