Share via


Ajouter du son

Notes

Cette rubrique fait partie de la série de didacticiels Créer un jeu de plateforme Windows universelle simple (UWP) avec DirectX. La rubrique de 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 brève introduction sous Concepts audio.

Notes

Si vous n’avez pas téléchargé le dernier code de jeu pour cet exemple, accédez à l’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 du son. Il définit également une méthode pour interrompre et reprendre la lecture audio si le jeu est en pause 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 de son dans le jeu.

Vue d’ensemble

Il existe trois parties main lors de 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

Ils sont tous définis dans la méthode Simple3DGame::Initialize . Examinons donc d’abord cette méthode, puis examinons plus en détail chacune des sections.

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

Méthode Simple3DGame::Initialize

Dans Simple3DGame::Initialize, où m_controller et m_renderer sont également initialisés, nous configureons le moteur audio et le préparons à lire des sons.

  • Créez m_audioController, qui est un 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 de moteur de musique et un objet moteur de son, et une voix de mastering pour chacun d’eux ont été créés. L’objet moteur de musique peut être utilisé pour lire de la musique de fond 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 un 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 les données sonores sous la forme d’un 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 sonores .wav chargées. Pour plus d’informations, consultez Charger un 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) et pour la cible.

  • Dans la classe GameObject , il existe une propriété HitSound qui est utilisée pour associer l’effet sonore à l’objet .
  • Créez une instance de la classe SoundEffect et initialisez-la. Pendant 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 par 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 un son à un objet.

Notes

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 pour 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’effets sonores. 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 de son.

Pour plus d’informations, consultez Guide pratique pour initialiser XAudio2.

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

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 .wav encodé, appelez MediaReader::LoadMedia, en passant le nom de fichier du fichier .wav comme paramètre d’entrée.

MediaReader::LoadMedia, méthode

Cette méthode utilise les API Media Foundation pour la lecture du fichier audio .wav en tant que tampon de modulation par impulsions codées (PCM).

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 de type média (IMFMediaType) (mediaType). Il représente une description d’un format multimédia.
  3. Spécifiez que la sortie décodée du mediaType est 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 à 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’ondes qui est utilisé après le chargement de l’audio.

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

Lire le flux audio

  1. Obtenez la durée, en secondes, du flux audio en appelant IMFSourceReader::GetPresentationAttribute , puis convertit la durée en octets.
  2. Lisez le fichier audio dans en tant que 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 l’exemple de mémoire tampon 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 a lieu lors de l’initialisation du jeu, dans la méthode Simple3DGame::Initialize .

Résumé :

  • Dans la classe GameObject , il existe une propriété HitSound qui est 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 mastering 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 permet d’initaliser l’instance SoundEffect avec les paramètres d’entrée suivants : pointeur vers l’objet moteur de son (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. Pendant 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 de lecture des effets sonores sont définis dans la méthode Simple3DGame::UpdateDynamics , car c’est là que le mouvement 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 n’allons pas discuter de la dynamique des objets de jeu ici. Si vous souhaitez comprendre son implémentation, accédez à Méthode Simple3DGame::UpdateDynamics .

En principe, lorsqu’une collision se produit, elle déclenche la lecture de l’effet sonore en appelant SoundEffect::P laySound. Cette méthode arrête tous les effets sonores 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 audio en file d’attente, SoundEffect::PlaySound démarre la lecture 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()
        );
}

Méthode Simple3DGame::UpdateDynamics

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

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 tutoriel, 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 Windows 10 développement de jeux, utilisez XAudio2 version 2.9. Cette version est fournie avec Windows 10. Pour plus d’informations, accédez à Versions XAudio2.

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

Voix XAudio2

Il existe trois types d’objets vocaux XAudio2 : voix source, sous-mix et mastering. Les voix sont les objets que XAudio2 utilise pour traiter, manipuler et lire des données audio.

  • Les voix source opèrent sur les données audio fournies par le client.
  • Les voix source et sous-mixées envoient leur sortie vers une ou plusieurs voix sous-mixées ou de matriçage.
  • Les voix sous-mixées et de matriçage mixent les signaux provenant de toutes les voix qui les alimentent et opèrent sur le résultat.
  • Le mastering des voix reçoit les données des voix sources et des voix de sous-mixage, et envoie ces données au matériel audio.

Pour plus d’informations, accédez à Voix XAudio2.

Graphique audio

Le graphique audio est une collection de voix XAudio2. L’audio démarre d’un côté d’un graphique audio dans les voix sources, passe éventuellement par une ou plusieurs voix de sous-mixage et se termine à une voix de mastering. Un graphique audio contient une voix source pour chaque son en cours de lecture, zéro ou plusieurs voix de sous-mixage et une voix de mastering. Le graphique audio le plus simple, et le minimum nécessaire pour faire du bruit dans XAudio2, est une voix source unique sortant directement à une voix de mastering. Pour plus d’informations, accédez à Graphiques audio.

Documentation supplémentaire

Fichiers .h audio clés

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