Dela via


Lägg till ljud

Anmärkning

Det här avsnittet är en del av Skapa ett enkelt UWP-spel (Universal Windows Platform) med DirectX självstudieserie. Ämnet på länken anger kontexten för serien.

I det här avsnittet skapar vi en enkel ljudmotor med hjälp av XAudio2 API:er. Om du är nybörjare på XAudio2har vi inkluderat en kort introduktion under Ljudbegrepp.

Anmärkning

Om du inte har laddat ned den senaste spelkoden för det här exemplet går du till Direct3D-exempelspel. Det här exemplet är en del av en stor samling UWP-funktionsexempel. Anvisningar om hur du laddar ned exemplet finns i Exempelprogram för Windows-utveckling.

Mål

Lägg till ljud i exempelspelet med hjälp av XAudio2.

Definiera ljudmotorn

I exempelspelet definieras ljudobjekten och beteendena i tre filer:

  • Audio.h/.cpp: Definierar objektet Audio som innehåller XAudio2 resurser för ljuduppspelning. Den definierar också metoden för att pausa och återuppta ljuduppspelning om spelet pausas eller inaktiveras.
  • MediaReader.h/.cpp: Definierar metoderna för att läsa ljud .wav filer från lokal lagring.
  • SoundEffect.h/.cpp: Definierar ett objekt för ljuduppspelning i spelet.

Översikt

Det finns tre huvuddelar i att konfigurera för ljuduppspelning i ditt spel.

  1. Skapa och initiera ljudresurserna
  2. Läs in ljudfil
  3. Associera ljud till objekt

De definieras alla i metoden Simple3DGame::Initialize. Så vi ska först undersöka den här metoden och sedan gå in på mer information i vart och ett av avsnitten.

När vi har konfigurerat får vi lära oss hur vi utlöser ljudeffekterna för att spela upp dem. För mer information, gå till Spela ljudet.

Simple3DGame::Initiera metod

I Simple3DGame::Initiera, där m_controller och m_renderer också initieras, konfigurerar vi ljudmotorn och gör den redo att spela upp ljud.

  • Skapa m_audioController, som är en instans av klassen Audio.
  • Skapa de ljudresurser som behövs med hjälp av metoden Audio::CreateDeviceIndependentResources. Här skapades två XAudio2 objekt – ett musikmotorobjekt och ett ljudmotorobjekt och en huvudröst för var och en av dem. Musikmotorobjektet kan användas för att spela bakgrundsmusik för ditt spel. Ljudmotorn kan användas för att spela upp ljudeffekter i ditt spel. Mer information finns i Skapa och initiera ljudresurserna.
  • Skapa mediaReader, som är en instans av klassen MediaReader. MediaReader, som är en hjälpklass för klassen SoundEffect, läser små ljudfiler synkront från filplatsen och returnerar ljuddata som en bytematris.
  • Använd MediaReader::LoadMedia för att läsa in ljudfiler från dess plats och skapa en targetHitSound- variabel för att lagra inlästa .wav ljuddata. Mer information finns i Ladda in ljudfil.

Ljudeffekter är associerade med spelobjektet. Så när en kollision inträffar med det spelobjektet utlöser det ljudeffekten som ska spelas upp. I det här exempelspelet har vi ljudeffekter för ammunitionen (det vi använder för att skjuta mål med) och för målet.

  • I klassen GameObject finns det en HitSound-egenskap som används för att associera ljudeffekten med objektet.
  • Skapa en ny instans av klassen SoundEffect och initiera den. Under initieringen skapas en källröst för ljudeffekten.
  • Den här klassen spelar upp ett ljud med hjälp av en mastering-röst från klassen Audio. Ljuddata läss från filplatsen med hjälp av klassen MediaReader. Mer information finns i Associera ljud till objekt.

Anmärkning

Den faktiska utlösaren för att spela ljudet bestäms av rörelse och kollision av dessa spelobjekt. Därför definieras anropet för att faktiskt spela upp dessa ljud i metoden Simple3DGame::UpdateDynamics. För mer information, gå till Spela ljudet.

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

Skapa och initiera ljudresurserna

  • Använd XAudio2Skapa, ett XAudio2-API, för att skapa två nya XAudio2-objekt som definierar musik- och ljudeffektmotorerna. Den här metoden returnerar en pekare till objektets IXAudio2- gränssnitt som hanterar alla ljudmotortillstånd, tråden för ljudbearbetning, röstdiagrammet med mera.
  • När motorerna har instansierats använder du IXAudio2::CreateMasteringVoice för att skapa en mastering-röst för var och en av ljudmotorobjekten.

Mer information finns i Så här initierar du XAudio2.

Ljud::CreateDeviceIndependentResources-metoden

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

Läs in ljudfil

I exempelspelet definieras koden för att läsa ljudformatfiler i MediaReader.h/cpp__. Om du vill läsa en kodad .wav ljudfil anropar du MediaReader::LoadMediaoch skickar filnamnet för .wav som indataparameter.

MediaReader::LoadMedia-metod

Den här metoden använder API:erna Media Foundation för att läsa i .wav ljudfilen som en PCM-buffert (Pulse Code Modulation).

Konfigurera källläsaren

  1. Använd MFCreateSourceReaderFromURL för att skapa en mediekällläsare (IMFSourceReader).
  2. Använd MFCreateMediaType för att skapa ett medietypobjekt (IMFMediaType) (mediaType). Den representerar en beskrivning av ett medieformat.
  3. Ange att mediaTypeavkodade utdata är PCM-ljud, vilket är en ljudtyp som XAudio2 kan använda.
  4. Anger den avkodade utdatamedietypen för källläsaren genom att anropa IMFSourceReader::SetCurrentMediaType.

Mer information om varför vi använder källläsaren finns i Källläsare.

Beskriva dataformatet för ljudströmmen

  1. Använd IMFSourceReader::GetCurrentMediaType för att hämta den aktuella medietypen för strömmen.
  2. Använd IMFMediaType::MFCreateWaveFormatExFromMFMediaType för att konvertera den aktuella ljudmedietypen till en WAVEFORMATEX buffert, med resultatet av den tidigare åtgärden som indata. Den här strukturen anger dataformatet för den vågljudström som används när ljudet har lästs in.

WAVEFORMATEX- format kan användas för att beskriva PCM-bufferten. Jämfört med WAVEFORMATEXTENSIBLE- struktur kan den bara användas för att beskriva en delmängd av ljudvågformat. Mer information om skillnaderna mellan WAVEFORMATEX och WAVEFORMATEXTENSIBLEfinns i Extensible Wave-Format Descriptors.

Läsa ljudströmmen

  1. Hämta ljudströmmens varaktighet i sekunder genom att anropa IMFSourceReader::GetPresentationAttribute och konverterar sedan varaktigheten till byte.
  2. Läs in ljudfilen som en ström genom att anropa IMFSourceReader::ReadSample. ReadSample läser nästa exempel från mediekällan.
  3. Använd IMFSample::ConvertToContiguousBuffer för att kopiera innehållet i ljudexempelbufferten (exempel) till en matris (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;
}

Associera ljud till objekt

Associera ljud till objektet sker när spelet initieras i metoden Simple3DGame::Initialize.

Resumé:

  • I klassen GameObject finns det en HitSound-egenskap som används för att associera ljudeffekten med objektet.
  • Skapa en ny instans av SoundEffect-klassobjektet och associera det med spelobjektet. Den här klassen spelar upp ett ljud med hjälp av XAudio2 API:er. Den använder en mastering-röst som tillhandahålls av klassen Audio. Ljuddata kan läsas från filplatsen med hjälp av klassen MediaReader.

SoundEffect::Initiera används för att initiera SoundEffect-instansen med följande indataparametrar: pekare till ljudmotorobjekt (IXAudio2-objekt som skapats i Audio::CreateDeviceIndependentResources-metod), pekare till format för .wav-filen med hjälp av MediaReader::GetOutputWaveFormatExoch ljuddata som läses in med hjälp av MediaReader::LoadMedia-metoden. Under initieringen skapas även källrösten för ljudeffekten.

SoundEffect::Initialize-metoden

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

Spela upp ljudet

Utlösare för att spela upp ljudeffekter definieras i Simple3DGame::UpdateDynamics metod eftersom det är här objektens förflyttning uppdateras och kollision mellan objekt bestäms.

Eftersom interaktionen mellan objekt skiljer sig mycket, beroende på spelet, kommer vi inte att diskutera dynamiken i spelet objekt här. Om du är intresserad av att förstå implementeringen går du till Simple3DGame::UpdateDynamics-metoden.

När en kollision inträffar utlöser den i princip ljudeffekten genom att anropa SoundEffect::PlaySound. Den här metoden stoppar alla ljudeffekter som för närvarande spelas upp och köar minnesintern buffert med önskade ljuddata. Den använder källröst för att ställa in volymen, skicka ljuddata och starta uppspelningen.

SoundEffect::PlaySound-metod

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-metod

Metoden Simple3DGame::UpdateDynamics tar hand om interaktionen och kollisionen mellan spelobjekt. När objekt kolliderar (eller korsar varandra) utlöser det den associerade ljudeffekten som ska spelas upp.

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
}

Nästa steg

Vi har gått igenom UWP-ramverket, grafik, kontroller, användargränssnitt och ljud från ett Windows 10-spel. I nästa del av den här självstudien Utöka exempelspelet, förklaras andra alternativ som kan användas när du utvecklar ett spel.

Ljudbegrepp

För utveckling av Windows 10-spel använder du XAudio2 version 2.9. Den här versionen levereras med Windows 10. För mer information, gå till XAudio2 Versioner.

AudioX2 är ett lågnivå-API som tillhandahåller signalbearbetning och blandningsgrund. Mer information finns i XAudio2 Key Concepts.

XAudio2-röster

Det finns tre typer av XAudio2-röstobjekt: källa, undermix och mastering-röster. Röster är de objekt som XAudio2 använder för att bearbeta, manipulera och spela upp ljuddata.

  • Källröster fungerar på ljuddata som tillhandahålls av klienten.
  • Käll- och undermixröster skickar sina utdata till en eller flera undermix- eller masterröster.
  • Undermix- och masterröster blandar ljudet från alla röster som matar dem och fungerar på resultatet.
  • Mastering-röster tar emot data från källröster och undermixröster och skickar dessa data till ljudmaskinvaran.

För mer information, gå till XAudio2-röster.

Ljuddiagram

Ljuddiagram är en samling XAudio2-röster. Ljudet börjar på ena sidan av ett ljuddiagram i källröster, om du vill kan du gå igenom en eller flera undermixröster och avslutas med en mastering-röst. Ett ljuddiagram innehåller en källröst för varje ljud som för närvarande spelas upp, noll eller fler undermixröster och en masterröst. Det enklaste ljuddiagrammet, och det minsta som krävs för att producera ett ljud i XAudio2, är en enda källröst som matar ut direkt till en masterröst. Mer information finns i Ljuddiagram.

Ytterligare läsning

Viktiga .h-filer för ljud

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