Audio toevoegen aan het Marble Maze-voorbeeld

In dit document worden de belangrijkste procedures beschreven die u moet overwegen wanneer u met audio werkt en laat zien hoe Marble Maze deze procedures toepast. Marble Maze maakt gebruik van Microsoft Media Foundation om audiobronnen uit bestanden te laden, en XAudio2 om audio te mixen en af te spelen en effecten op audio toe te passen.

Marble Maze speelt muziek op de achtergrond en gebruikt ook gameplaygeluiden om game-gebeurtenissen aan te geven, zoals wanneer de marmeren muur raakt. Een belangrijk onderdeel van de implementatie is dat Marble Maze gebruikmaakt van een reverb, of echo, effect om het geluid van een marmer te simuleren wanneer het stuitert. De reverb effect implementatie zorgt ervoor dat echo's u sneller en luider bereiken in kleine ruimten; echo's zijn rustiger en bereiken je langzamer in grotere kamers.

Opmerking

De voorbeeldcode die overeenkomt met dit document vindt u in het DirectX Marble Maze-gamevoorbeeld.

Hier volgen enkele van de belangrijkste punten die in dit document worden besproken wanneer u met audio in uw game werkt:

  • Overweeg om Media Foundation te gebruiken om audioassets en XAudio2 te decoderen om audio af te spelen. Als u echter een bestaand mechanisme voor het laden van audioassets hebt dat werkt in een UWP-app (Universal Windows Platform), kunt u dit gebruiken.

  • Een audiografiek bevat één bronstem voor elk actief geluid, nul of meer submixstemmen en één mastering stem. Bronstemmen kunnen worden doorgegeven aan submixstemmen en/of de masteringstem. Submixstemmen voeden zich in andere submixstemmen of de mastering stem.

  • Als uw achtergrondmuziekbestanden groot zijn, kunt u overwegen om uw muziek naar kleinere buffers te streamen, zodat er minder geheugen wordt gebruikt.

  • Als het zinvol is om dit te doen, pauzeert u het afspelen van audio wanneer de app de focus of zichtbaarheid verliest of wordt onderbroken. Afspelen hervatten wanneer uw app weer focus krijgt, zichtbaar wordt of wordt hervat.

  • Stel audiocategorieën in om de rol van elk geluid weer te geven. U gebruikt bijvoorbeeld meestal AudioCategory_GameMedia voor game-achtergrondgeluid en AudioCategory_GameEffects voor geluidseffecten.

  • Afhandelen van apparaatwijzigingen, inclusief hoofdtelefoons, door alle audiobronnen en interfaces vrij te geven en opnieuw te maken.

  • Overweeg of u audiobestanden wilt comprimeren wanneer het noodzakelijk is om schijfruimte en streamingkosten te minimaliseren. Anders kunt u audio ongecomprimeerd laten zodat deze sneller wordt geladen.

Inleiding tot XAudio2 en Microsoft Media Foundation

XAudio2 is een audiobibliotheek op laag niveau voor Windows die specifiek ondersteuning biedt voor game-audio. Het biedt een digitale signaalverwerking (DSP) en audiografiek-engine voor games. XAudio2 breidt zijn voorgangers, DirectSound en XAudio, uit door computingtrends zoals SIMD zwevendekomma-architecturen en HD-audio te ondersteunen. Het ondersteunt ook de complexere geluidsverwerkingsvereisten van de huidige games.

In het document XAudio2 Key Concepts worden de belangrijkste concepten voor het gebruik van XAudio2 uitgelegd. Kortom, de concepten zijn:

  • De IXAudio2-interface is de kern van de XAudio2-engine. Marble Maze gebruikt deze interface om stemmen te maken en meldingen te ontvangen wanneer het uitvoerapparaat wordt gewijzigd of mislukt.

  • Een stem verwerkt, past aan en speelt audiogegevens af.

  • Een bronstem is een verzameling audiokanalen (mono, 5.1, enzovoort) en vertegenwoordigt één stroom audiogegevens. In XAudio2 is een bronstem het beginpunt van de audioverwerking. Geluidsgegevens worden doorgaans geladen vanuit een externe bron, zoals een bestand of een netwerk, en worden verzonden naar een bronstem. Marble Maze gebruikt Media Foundation om geluidsgegevens uit bestanden te laden. Media Foundation wordt verderop in dit document geïntroduceerd.

  • Een submix-stem verwerkt audiogegevens. Deze verwerking kan bestaan uit het wijzigen van de audiostream of het combineren van meerdere streams in één. Marble Maze maakt gebruik van submixen om het reverb-effect te maken.

  • Een mastering voice combineert gegevens uit bron- en submixstemmen en verzendt die gegevens naar de audiohardware.

  • Een audiografiek bevat één bronstem voor elk actief geluid, nul of meer submixstemmen en slechts één mastering stem.

  • Een callback informeert de clientcode dat een bepaalde gebeurtenis heeft plaatsgevonden in een spraak- of engineobject. Door callbacks te gebruiken, kunt u het geheugen opnieuw gebruiken wanneer XAudio2 is voltooid met een buffer, reageren wanneer het audioapparaat verandert (bijvoorbeeld wanneer u een hoofdtelefoon aansluit of loskoppelt), en meer. Behandelen van hoofdtelefoon- en apparaatwijzigingen verderop in dit document legt uit hoe Marble Maze dit mechanisme gebruikt om apparaatwijzigingen af te handelen.

Marble Maze gebruikt twee audio-engines (met andere woorden twee IXAudio2-objecten ) om audio te verwerken. De ene engine verwerkt de achtergrondmuziek en de andere engine verwerkt gameplaygeluiden.

Marble Maze moet ook één mastering-geluid maken voor elke spelengine. Een mastering-engine combineert audiostreams in één stream en verzendt die stream naar de audiohardware. De achtergrondmuziekstroom, een bronstem, voert gegevens uit naar een mastering voice en naar twee submixstemmen. De submixstemmen voeren het reverb-effect uit.

Media Foundation is een multimediabibliotheek die ondersteuning biedt voor veel audio- en video-indelingen. XAudio2 en Media Foundation vullen elkaar aan. Marble Maze maakt gebruik van Media Foundation om audioassets uit bestanden te laden en XAudio2 te gebruiken om audio af te spelen. U hoeft Media Foundation niet te gebruiken om audioassets te laden. Als u een bestaand laadmechanisme voor audioassets hebt dat werkt in UWP-apps (Universal Windows Platform), gebruikt u dit. Audio, video en camera bespreken verschillende manieren om audio in een UWP-app te implementeren.

Zie Programmeerhandleiding voor meer informatie over XAudio2. Zie Microsoft Media Foundation voor meer informatie over Media Foundation.

Audiobronnen initialiseren

Marble Mazes maakt gebruik van een Windows Media Audio-bestand (.wma) voor de achtergrondmuziek en WAV-bestanden (.wav) voor gameplaygeluiden. Deze indelingen worden ondersteund door Media Foundation. Hoewel de .wav-bestandsindeling systeemeigen wordt ondersteund door XAudio2, moet een game de bestandsindeling handmatig parseren om de juiste XAudio2-gegevensstructuren in te vullen. Marble Maze maakt gebruik van Media Foundation om gemakkelijker met .wav bestanden te werken. Zie Ondersteunde media-indelingen in Media Foundation voor de volledige lijst met media-indelingen die worden ondersteund door Media Foundation. Marble Maze maakt geen gebruik van afzonderlijke ontwerp- en runtime audio-indelingen en maakt geen gebruik van XAudio2 ADPCM-compressieondersteuning. Zie ADPCM-overzicht voor meer informatie over ADPCM-compressie in XAudio2.

De methode Audio::CreateResources , die wordt aangeroepen vanuit MarbleMazeMain::LoadDeferredResources, laadt de audiostreams uit de bestanden, initialiseert de XAudio2-engineobjecten en maakt de bron-, submix- en masteringstemmen.

De XAudio2-engines maken

U weet nog dat Marble Maze één IXAudio2-object maakt om elke audio-engine weer te geven die wordt gebruikt. Als u een audio-engine wilt maken, roept u de XAudio2Create-methode aan . In het volgende voorbeeld ziet u hoe Marble Maze de audio-engine maakt die achtergrondmuziek verwerkt.

// In Audio.h
class Audio
{
private:
    IXAudio2*                   m_musicEngine;
// ...
}

// In Audio.cpp
void Audio::CreateResources()
{
    try
    {
        // ...
        DX::ThrowIfFailed(
            XAudio2Create(&m_musicEngine)
            );
        // ...
    }
    // ...
}

Marble Maze voert een vergelijkbare stap uit om de audio-engine te maken die gameplay-geluiden afspeelt.

Het werken met de IXAudio2-interface in een UWP-app verschilt op twee manieren van een desktop-app. Eerst hoeft u CoInitializeEx niet aan te roepen voordat u XAudio2Createaan te roepen. Daarnaast biedt IXAudio2 geen ondersteuning meer voor opsomming van apparaten. Zie Apparaten inventariseren voor meer informatie over het inventariseren van audioapparaten.

De masteringstemmen maken

In het volgende voorbeeld ziet u hoe de methode Audio::CreateResources de mastering voice voor de achtergrondmuziek maakt met behulp van de methode IXAudio2::CreateMasteringVoice . In dit voorbeeld is m_musicMasteringVoice een IXAudio2MasteringVoice-object . We geven twee invoerkanalen op; dit vereenvoudigt de logica voor het reverb-effect.

We geven 48000 op als de invoervoorbeeldfrequentie. We hebben deze samplefrequentie gekozen omdat deze een balans vertegenwoordigde tussen de audiokwaliteit en de hoeveelheid vereiste CPU-verwerking. Een grotere steekproefsnelheid zou meer CPU-verwerking nodig hebben zonder merkbaar kwaliteitsvoordeel te hebben.

Ten slotte geven we AudioCategory_GameMedia op als de categorie audiostream, zodat gebruikers naar muziek van een andere toepassing kunnen luisteren terwijl ze het spel spelen. Wanneer een muziek-app wordt afgespeeld, dempt Windows alle stemmen die zijn gemaakt door de optie AudioCategory_GameMedia . De gebruiker hoort nog steeds gameplaygeluiden omdat ze worden gemaakt door de AudioCategory_GameEffects optie. Zie AUDIO_STREAM_CATEGORY voor meer informatie over audiocategorieën.

// This sample plays the equivalent of background music, which we tag on the  
// mastering voice as AudioCategory_GameMedia. In ordinary usage, if we were  
// playing the music track with no effects, we could route it entirely through 
// Media Foundation. Here, we are using XAudio2 to apply a reverb effect to the 
// music, so we use Media Foundation to decode the data then we feed it through 
// the XAudio2 pipeline as a separate Mastering Voice, so that we can tag it 
// as Game Media. We default the mastering voice to 2 channels to simplify  
// the reverb logic.
DX::ThrowIfFailed(
    m_musicEngine->CreateMasteringVoice(
        &m_musicMasteringVoice,
        2,
        48000,
        0,
        nullptr,
        nullptr,
        AudioCategory_GameMedia
        )
);

De methode Audio::CreateResources voert een vergelijkbare stap uit om de masteringstem voor de gameplay-geluiden te creëren. Hierbij wordt voor de parameter StreamCategory echter AudioCategory_GameEffects opgegeven, wat de standaardwaarde is.

Het reverb-effect maken

Voor elke stem kunt u XAudio2 gebruiken om reeksen effecten te maken die audio verwerken. Een dergelijke reeks wordt een effectketen genoemd. Gebruik effectketens als u een of meer effecten op een stem wilt toepassen. Effectketens kunnen destructief zijn; Dat wil gezegd, elk effect in de keten kan de audiobuffer overschrijven. Deze eigenschap is belangrijk omdat XAudio2 geen garantie geeft dat uitvoerbuffers met stilte worden geïnitialiseerd. Effectobjecten worden weergegeven in XAudio2 door platformoverschrijdende audioverwerkingsobjecten (XAPO). Zie XAPO-overzicht voor meer informatie over XAPO.

Wanneer u een effectketen maakt, voert u de volgende stappen uit:

  1. Maak het effectobject.

  2. Vul een XAUDIO2_EFFECT_DESCRIPTOR structuur in met effectgegevens.

  3. Vul een XAUDIO2_EFFECT_CHAIN structuur in met gegevens.

  4. Pas de effectketen toe op een stem.

  5. Vul een effectparameterstructuur in en pas deze toe op het effect.

  6. Schakel het effect indien van toepassing uit of in.

De audioklasse definieert de Methode CreateReverb om de effectketen te maken waarmee reverb wordt geïmplementeerd. Met deze methode wordt de methode XAudio2CreateReverb aangeroepen om een ComPtr-<IUnknown>-object te maken, soundEffectXAPO, dat fungeert als de submixstem voor het reverb-effect.

Microsoft::WRL::ComPtr<IUnknown> soundEffectXAPO;

DX::ThrowIfFailed(
    XAudio2CreateReverb(&soundEffectXAPO)
    );

De XAUDIO2_EFFECT_DESCRIPTOR structuur bevat informatie over een XAPO voor gebruik in een effectketen, bijvoorbeeld het doelaantal uitvoerkanalen. De methode Audio::CreateReverb maakt een XAUDIO2_EFFECT_DESCRIPTOR-object , soundEffectdescriptor, dat is ingesteld op de uitgeschakelde status, gebruikt twee uitvoerkanalen en verwijst naar soundEffectXAPO voor het reverb-effect. soundEffectdescriptor begint in de uitgeschakelde status omdat het spel parameters moet instellen voordat het effect begint met het wijzigen van gamegeluiden. Marble Maze gebruikt twee uitvoerkanalen om de logica voor het reverb-effect te vereenvoudigen.

soundEffectdescriptor.InitialState = false;
soundEffectdescriptor.OutputChannels = 2;
soundEffectdescriptor.pEffect = soundEffectXAPO.Get();

Als uw effectketen meerdere effecten heeft, is voor elk effect een object vereist. De XAUDIO2_EFFECT_CHAIN-structuur bevat de matrix van XAUDIO2_EFFECT_DESCRIPTOR objecten die aan het effect deelnemen. In het volgende voorbeeld ziet u hoe de methode Audio::CreateReverb het ene effect specificeert om reverb te implementeren.

XAUDIO2_EFFECT_CHAIN soundEffectChain;

// ...

soundEffectChain.EffectCount = 1;
soundEffectChain.pEffectDescriptors = &soundEffectdescriptor;

De methode Audio::CreateReverb roept de methode IXAudio2::CreateSubmixVoice aan om de submixstem voor het effect te maken. Hiermee geeft u het XAUDIO2_EFFECT_CHAIN-object, soundEffectChain, op voor de parameter pEffectChain om de effectketen te koppelen aan de stem. Marble Maze specificeert ook twee uitvoerkanalen en een steekproefsnelheid van 48 kilohertz.

DX::ThrowIfFailed(
    engine->CreateSubmixVoice(newSubmix, 2, 48000, 0, 0, nullptr, &soundEffectChain)
    );

Aanbeveling

Als u een bestaande effectketen wilt koppelen aan een bestaande submixstem of als u de huidige effectketen wilt vervangen, gebruikt u de methode IXAudio2Voice::SetEffectChain .

De methode Audio::CreateReverb roept IXAudio2Voice::SetEffectParameters aan om extra parameters in te stellen die aan het effect zijn gekoppeld. Deze methode gebruikt een parameterstructuur die specifiek is voor het effect. Een XAUDIO2FX_REVERB_PARAMETERS-object , m_reverbParametersSmall, dat de effectparameters voor reverb bevat, wordt geïnitialiseerd in de methode Audio::Initialize omdat elk reverb-effect dezelfde parameters deelt. In het volgende voorbeeld ziet u hoe de methode Audio::Initialize de reverb-parameters initialiseert voor near-field reverb.

m_reverbParametersSmall.ReflectionsDelay = XAUDIO2FX_REVERB_DEFAULT_REFLECTIONS_DELAY;
m_reverbParametersSmall.ReverbDelay = XAUDIO2FX_REVERB_DEFAULT_REVERB_DELAY;
m_reverbParametersSmall.RearDelay = XAUDIO2FX_REVERB_DEFAULT_REAR_DELAY;
m_reverbParametersSmall.PositionLeft = XAUDIO2FX_REVERB_DEFAULT_POSITION;
m_reverbParametersSmall.PositionRight = XAUDIO2FX_REVERB_DEFAULT_POSITION;
m_reverbParametersSmall.PositionMatrixLeft = XAUDIO2FX_REVERB_DEFAULT_POSITION_MATRIX;
m_reverbParametersSmall.PositionMatrixRight = XAUDIO2FX_REVERB_DEFAULT_POSITION_MATRIX;
m_reverbParametersSmall.EarlyDiffusion = 4;
m_reverbParametersSmall.LateDiffusion = 15;
m_reverbParametersSmall.LowEQGain = XAUDIO2FX_REVERB_DEFAULT_LOW_EQ_GAIN;
m_reverbParametersSmall.LowEQCutoff = XAUDIO2FX_REVERB_DEFAULT_LOW_EQ_CUTOFF;
m_reverbParametersSmall.HighEQGain = XAUDIO2FX_REVERB_DEFAULT_HIGH_EQ_GAIN;
m_reverbParametersSmall.HighEQCutoff = XAUDIO2FX_REVERB_DEFAULT_HIGH_EQ_CUTOFF;
m_reverbParametersSmall.RoomFilterFreq = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_FREQ;
m_reverbParametersSmall.RoomFilterMain = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_MAIN;
m_reverbParametersSmall.RoomFilterHF = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_HF;
m_reverbParametersSmall.ReflectionsGain = XAUDIO2FX_REVERB_DEFAULT_REFLECTIONS_GAIN;
m_reverbParametersSmall.ReverbGain = XAUDIO2FX_REVERB_DEFAULT_REVERB_GAIN;
m_reverbParametersSmall.DecayTime = XAUDIO2FX_REVERB_DEFAULT_DECAY_TIME;
m_reverbParametersSmall.Density = XAUDIO2FX_REVERB_DEFAULT_DENSITY;
m_reverbParametersSmall.RoomSize = XAUDIO2FX_REVERB_DEFAULT_ROOM_SIZE;
m_reverbParametersSmall.WetDryMix = XAUDIO2FX_REVERB_DEFAULT_WET_DRY_MIX;
m_reverbParametersSmall.DisableLateField = TRUE;

In dit voorbeeld worden de standaardwaarden voor de meeste reverb-parameters gebruikt, maar wordt DisableLateField ingesteld op WAAR om reverb in near-field op te geven, EarlyDiffusion op 4 om vlakke nabijgelegen oppervlakken te simuleren en LateDiffusion tot 15 om zeer diffuse, verre oppervlakken te simuleren. Vlakkende nabijgelegen oppervlakken zorgen ervoor dat echo's je sneller en luider bereiken; verspreide verre oppervlakken maken echo's zachter en bereiken je langzamer. U kunt experimenteren met reverb-waarden om het gewenste effect in uw game te krijgen of de ReverbConvertI3DL2ToNative-methode gebruiken om industriestandaard I3DL2 -parameters (Interactive 3D Audio Rendering Guidelines Level 2.0) te gebruiken.

In het volgende voorbeeld ziet u hoe Audio::CreateReverb de reverb-parameters instelt. newSubmix is een IXAudio2SubmixVoice** object. parameters is een XAUDIO2FX_REVERB_PARAMETERS* object.

DX::ThrowIfFailed(
    (*newSubmix)->SetEffectParameters(0, parameters, sizeof(m_reverbParametersSmall))
    );

De methode Audio::CreateReverb wordt voltooid door het effect in te schakelen met IXAudio2Voice::EnableEffect als de vlag enableEffect is ingesteld. Ook wordt het volume ingesteld met IXAudio2Voice::SetVolume en de uitvoermatrix ingesteld met IXAudio2Voice::SetOutputMatrix. Dit onderdeel stelt het volume in op vol (1.0) en geeft vervolgens aan dat de volumematrix stilte moet zijn voor zowel linker- als rechterinvoer en linker- en rechteruitvoerluidsprekers. We doen dit omdat andere code later kruisvervaagt tussen de twee reverbs (simuleren dat de overgang zich in de buurt van een muur in een grote ruimte bevindt) of beide reverbs indien nodig dempt. Wanneer het dempen van het galmpad later wordt opgeheven, stelt het spel een matrix in van {1.0f, 0.0f, 0.0f, 1.0f} om de linker galmuitvoer naar de linkerinvoer van de mastering-stem en de rechter galmuitvoer naar de rechterinvoer van de mastering-stem te routeren.

if (enableEffect)
{
    DX::ThrowIfFailed(
        (*newSubmix)->EnableEffect(0)
        );    
}

DX::ThrowIfFailed(
    (*newSubmix)->SetVolume (1.0f)
    );

float outputMatrix[4] = {0, 0, 0, 0};
DX::ThrowIfFailed(
    (*newSubmix)->SetOutputMatrix(masteringVoice, 2, 2, outputMatrix)
    );

Marble Maze roept de methode Audio::CreateReverb vier keer aan: twee keer voor de achtergrondmuziek en twee keer voor de gameplay-geluiden. Hieronder ziet u hoe Marble Maze de Methode CreateReverb aanroept voor de achtergrondmuziek.

CreateReverb(
    m_musicEngine, 
    m_musicMasteringVoice, 
    &m_reverbParametersSmall, 
    &m_musicReverbVoiceSmallRoom, 
    true
    );
CreateReverb(
    m_musicEngine, 
    m_musicMasteringVoice, 
    &m_reverbParametersLarge, 
    &m_musicReverbVoiceLargeRoom, 
    true
    );

Zie XAudio2 Audio-effecten voor een lijst met mogelijke bronnen van effecten voor gebruik met XAudio2.

Audiogegevens laden uit bestand

Marble Maze definieert de MediaStreamer-klasse , die Gebruikmaakt van Media Foundation om audiobronnen uit bestanden te laden. Marble Maze gebruikt één MediaStreamer-object om elk audiobestand te laden.

Marble Maze roept de methode MediaStreamer::Initialize op om elke audiostream te initialiseren. Zo roept de methode Audio::CreateResourcesMediaStreamer::Initialize aan om de audiostream voor de achtergrondmuziek te initialiseren:

// Media Foundation is a convenient way to get both file I/O and format decode for 
// audio assets. You can replace the streamer in this sample with your own file I/O 
// and decode routines.
m_musicStreamer.Initialize(L"Media\\Audio\\background.wma");

De methode MediaStreamer::Initialize begint met het aanroepen van de methode MFStartup om Media Foundation te initialiseren. MF_VERSION is een macro die is gedefinieerd in mfapi.h en is wat we opgeven als de versie van Media Foundation die moet worden gebruikt.

DX::ThrowIfFailed(
    MFStartup(MF_VERSION)
    );

MediaStreamer::Initialize roept vervolgens MFCreateSourceReaderFromURL aan om een IMFSourceReader-object te maken. Een IMFSourceReader-object, m_reader, leest mediagegevens uit het bestand dat is opgegeven door de URL.

DX::ThrowIfFailed(
    MFCreateSourceReaderFromURL(url, nullptr, &m_reader)
    );

De methode MediaStreamer::Initialize maakt vervolgens een IMFMediaType-object met behulp van MFCreateMediaType om de indeling van de audiostream te beschrijven. Een audio-indeling heeft twee typen: een primair type en een subtype. Het primaire type definieert de algehele indeling van de media, zoals video, audio, script, enzovoort. Het subtype definieert de indeling, zoals PCM, ADPCM of WMA.

De methode MediaStreamer::Initialize maakt gebruik van de methode IMFAttributes::SetGUID om het primaire type (MF_MT_MAJOR_TYPE) op te geven als audio (MFMediaType_Audio) en het secundaire type (MF_MT_SUBTYPE) als niet-gecomprimeerde PCM-audio (MFAudioFormat_PCM). MF_MT_MAJOR_TYPE en MF_MT_SUBTYPE zijn Media Foundation-kenmerken. MFMediaType_Audio en MFAudioFormat_PCM zijn type- en subtype-GUID's; zie Audiomediatypen voor meer informatie. De methode IMFSourceReader::SetCurrentMediaType koppelt het mediatype aan de streamlezer.

// Set the decoded output format as PCM. 
// XAudio2 on Windows can process PCM and ADPCM-encoded buffers. 
// When this sample uses Media Foundation, it always decodes into PCM.

DX::ThrowIfFailed(
    MFCreateMediaType(&mediaType)
    );

DX::ThrowIfFailed(
    mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio)
    );

DX::ThrowIfFailed(
    mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM)
    );

DX::ThrowIfFailed(
    m_reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, mediaType.Get())
    );

De MediaStreamer::Initialize methode verkrijgt vervolgens de volledige media-uitvoerindeling van Media Foundation met behulp van IMFSourceReader::GetCurrentMediaType. Vervolgens roept het de MFCreateWaveFormatExFromMFMediaType aan om het audio-mediamedium van Media Foundation te converteren naar een WAVEFORMATEX-structuur. De WAVEFORMATEX structuur definieert het formaat van waveform-audio-gegevens. Marble Maze gebruikt deze structuur om de bronstemmen te maken en het filter met lage pas toe te passen op het marmeren rollende geluid.

// Get the complete WAVEFORMAT from the Media Type.
DX::ThrowIfFailed(
    m_reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &outputMediaType)
    );

uint32 formatSize = 0;
WAVEFORMATEX* waveFormat;
DX::ThrowIfFailed(
    MFCreateWaveFormatExFromMFMediaType(outputMediaType.Get(), &waveFormat, &formatSize)
    );
CopyMemory(&m_waveFormat, waveFormat, sizeof(m_waveFormat));
CoTaskMemFree(waveFormat);

Belangrijk

De methode MFCreateWaveFormatExFromMFMediaType maakt gebruik van CoTaskMemAlloc om het WAVEFORMATEX-object toe te wijzen. Zorg er daarom voor dat u CoTaskMemFree aanroept wanneer u klaar bent met het gebruik van dit object.

 

De methode MediaStreamer::Initialize wordt voltooid door de lengte van de stream, m_maxStreamLengthInBytes, in bytes te berekenen. Hiervoor wordt de METHODE IMFSourceReader::GetPresentationAttribute aangeroepen om de duur van de audiostream in eenheden van 100 nanoseconden op te halen, de duur te converteren naar secties en vervolgens vermenigvuldigt met de gemiddelde gegevensoverdrachtsnelheid in bytes per seconde. Marble Maze gebruikt deze waarde later om de buffer toe te wijzen die elk gameplay-geluid bevat.

// Get the total length of the stream, in bytes.
PROPVARIANT var;
DX::ThrowIfFailed(
    m_reader->
        GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &var)
    );

// duration is in 100ns units; convert to seconds, and round up
// to the nearest whole byte.
ULONGLONG duration = var.uhVal.QuadPart;
m_maxStreamLengthInBytes =
    static_cast<unsigned int>(
        ((duration * static_cast<ULONGLONG>(m_waveFormat.nAvgBytesPerSec)) + 10000000)
        / 10000000
        );

Het maken van de bronstemmen

Marble Maze maakt XAudio2 bronstemmen om elk van zijn spelgeluiden en muziek in bronstemmen te spelen. De klasse Audio definieert een IXAudio2SourceVoice-object voor de achtergrondmuziek en een matrix van SoundEffectData-objecten om de gameplaygeluiden op te slaan. De Structuur SoundEffectData bevat het IXAudio2SourceVoice-object voor een effect en definieert ook andere effectgerelateerde gegevens, zoals de audiobuffer. Audio.h definieert de SoundEvent-opsomming. Marble Maze gebruikt deze opsomming om elk gameplay-geluid te identificeren. De audioklasse gebruikt deze opsomming ook om de matrix van SoundEffectData-objecten te indexeren.

enum SoundEvent
{
    RollingEvent        = 0,
    FallingEvent        = 1,
    CollisionEvent      = 2,
    CheckpointEvent     = 3,
    MenuChangeEvent     = 4,
    MenuSelectedEvent   = 5,
    LastSoundEvent,
};

In de volgende tabel ziet u de relatie tussen elk van deze waarden, het bestand dat de bijbehorende geluidsgegevens bevat en een korte beschrijving van wat elk geluid vertegenwoordigt. De audiobestanden bevinden zich in de map \Media\Audio .

SoundEvent-waarde Bestandsnaam Beschrijving
RollingEvent MarbleRoll.wav Gespeeld terwijl de knikker rolt.
FallingEvent MarbleFall.wav Gespeeld wanneer het marmer uit het doolhof valt.
Botsingsgebeurtenis MarbleHit.wav Gespeeld toen de marmeren botsen met het doolhof.
Controlepuntgebeurtenis Checkpoint.wav Wordt afgespeeld wanneer het marmer een controlepunt passeert.
MenuWijzigingEvent MenuChange.wav Wordt afgespeeld wanneer de gebruiker de huidige menuopdracht wijzigt.
MenuGeselecteerdEvenement MenuSelect.wav Wordt afgespeeld wanneer de gebruiker een menu-item selecteert.

 

In het volgende voorbeeld ziet u hoe de methode Audio::CreateResources de bronstem voor de achtergrondmuziek maakt. De XAUDIO2_SEND_DESCRIPTOR-structuur definieert de doeldoelstem van een andere stem en geeft aan of een filter moet worden gebruikt. Marble Maze roept de methode Audio::SetSoundEffectFilter aan om de filters te gebruiken om het geluid van de bal te wijzigen terwijl deze rolt. De XAUDIO2_VOICE_SENDS-structuur definieert de set stemmen voor het ontvangen van gegevens van één uitvoerstem. Marble Maze verzendt gegevens van de bronstem naar de mastering-stem (voor het droge, of ongealtereerde, deel van een afspeelgeluid) en naar de twee submixstemmen die het natte, of reverberante, gedeelte van een afspeelgeluid implementeren.

De methode IXAudio2::CreateSourceVoice maakt en configureert een bronstem. Er wordt een WAVEFORMATEX-structuur gebruikt die de indeling definieert van de audiobuffers die naar de stem worden verzonden. Zoals eerder vermeld, gebruikt Marble Maze de PCM-indeling.

XAUDIO2_SEND_DESCRIPTOR descriptors[3];
descriptors[0].pOutputVoice = m_musicMasteringVoice;
descriptors[0].Flags = 0;
descriptors[1].pOutputVoice = m_musicReverbVoiceSmallRoom;
descriptors[1].Flags = 0;
descriptors[2].pOutputVoice = m_musicReverbVoiceLargeRoom;
descriptors[2].Flags = 0;
XAUDIO2_VOICE_SENDS sends = {0};
sends.SendCount = 3;
sends.pSends = descriptors;
WAVEFORMATEX& waveFormat = m_musicStreamer.GetOutputWaveFormatEx();

DX::ThrowIfFailed(
    m_musicEngine->CreateSourceVoice(&m_musicSourceVoice, &waveFormat, 0, 1.0f, &m_voiceContext, &sends, nullptr)
    );

DX::ThrowIfFailed(
    m_musicMasteringVoice->SetVolume(0.4f)
    );

Achtergrondmuziek afspelen

Er wordt een bronstem gemaakt in de gestopte status. Marble Maze start de achtergrondmuziek in de gamelus. De eerste aanroep van MarbleMazeMain::Update roept Audio::Start aan om de achtergrondmuziek te starten.

if (!m_audio.m_isAudioStarted)
{
    m_audio.Start();
}

De methode Audio::Start roept IXAudio2SourceVoice::Start aan om te beginnen met het verwerken van de bronstem voor de achtergrondmuziek.

void Audio::Start()
{     
    if (m_engineExperiencedCriticalError)
    {
        return;
    }

    HRESULT hr = m_musicSourceVoice->Start(0);

    if SUCCEEDED(hr) {
        m_isAudioStarted = true;
    }
    else
    {
        m_engineExperiencedCriticalError = true;
    }
}

De bronstem geeft die audiogegevens door aan de volgende fase van de audiografiek. In het geval van Marble Maze bevat de volgende fase twee submixstemmen die de twee reverb-effecten op de audio toepassen. Eén submixstem past een dichte late veldverb toe; de tweede past een ver late veldverb toe.

De hoeveelheid die elke submixstem bijdraagt aan de uiteindelijke mix wordt bepaald door de grootte en vorm van de ruimte. De near-field reverb draagt meer bij wanneer de bal zich in de buurt van een muur of in een kleine kamer bevindt, terwijl de late-field reverb meer effect heeft wanneer de bal zich in een grote ruimte bevindt. Deze techniek produceert een realistischer echo-effect naarmate het marmer door het doolhof beweegt. Zie Audio::SetRoomSize en Physics::CalculateCurrentRoomSize in de Marble Maze-broncode voor meer informatie over hoe Marble Maze dit effect implementeert.

Opmerking

In een spel waarin de meeste kamergrootten relatief hetzelfde zijn, kunt u een meer eenvoudig reverb-model gebruiken. U kunt bijvoorbeeld één reverb-instelling gebruiken voor alle ruimten of u kunt een vooraf gedefinieerde reverb-instelling maken voor elke ruimte.

De methode Audio::CreateResources gebruikt Media Foundation om de achtergrondmuziek te laden. Op dit moment beschikt de bronstem echter niet over audiogegevens om mee te werken. Bovendien moet de bron regelmatig met gegevens worden bijgewerkt, omdat de achtergrondmuziek in een loop wordt afgespeeld, zodat de muziek blijft doorgaan.

Om de bronstem gevuld te houden met gegevens, werkt de gamelus de audiobuffers elk frame bij. De methode MarbleMazeMain::Render roept Audio::Render aan om de audiobuffer voor achtergrondmuziek te verwerken. De klasse Audio definieert een matrix van drie audiobuffers, m_audioBuffers. Elke buffer bevat 64 kB (65536 bytes) aan gegevens. De lus leest gegevens van het Media Foundation-object en schrijft die gegevens naar de bronstem totdat de bronstem drie buffers in de wachtrij heeft.

Waarschuwing

Hoewel Marble Maze een buffer van 64 kB gebruikt om muziekgegevens op te slaan, moet u mogelijk een grotere of kleinere buffer gebruiken. Dit bedrag is afhankelijk van de vereisten van uw game.

// This sample processes audio buffers during the render cycle of the application.
// As long as the sample maintains a high-enough frame rate, this approach should
// not glitch audio. In game code, it is best for audio buffers to be processed
// on a separate thread that is not synced to the main render loop of the game.
void Audio::Render()
{
    if (m_engineExperiencedCriticalError)
    {
        m_engineExperiencedCriticalError = false;
        ReleaseResources();
        Initialize();
        CreateResources();
        Start();
        if (m_engineExperiencedCriticalError)
        {
            return;
        }
    }

    try
    {
        bool streamComplete;
        XAUDIO2_VOICE_STATE state;
        uint32 bufferLength;
        XAUDIO2_BUFFER buf = {0};

        // Use MediaStreamer to stream the buffers.
        m_musicSourceVoice->GetState(&state);
        while (state.BuffersQueued <= MAX_BUFFER_COUNT - 1)
        {
            streamComplete = m_musicStreamer.GetNextBuffer(
                m_audioBuffers[m_currentBuffer],
                STREAMING_BUFFER_SIZE,
                &bufferLength
                );

            if (bufferLength > 0)
            {
                buf.AudioBytes = bufferLength;
                buf.pAudioData = m_audioBuffers[m_currentBuffer];
                buf.Flags = (streamComplete) ? XAUDIO2_END_OF_STREAM : 0;
                buf.pContext = 0;
                DX::ThrowIfFailed(
                    m_musicSourceVoice->SubmitSourceBuffer(&buf)
                    );

                m_currentBuffer++;
                m_currentBuffer %= MAX_BUFFER_COUNT;
            }

            if (streamComplete)
            {
                // Loop the stream.
                m_musicStreamer.Restart();
                break;
            }

            m_musicSourceVoice->GetState(&state);
        }
    }
    catch (...)
    {
        m_engineExperiencedCriticalError = true;
    }
}

De lus behandelt ook wanneer het Media Foundation-object het einde van de stream bereikt. In dit geval wordt de METHODE IMFSourceReader::SetCurrentPosition aangeroepen om de positie van de audiobron opnieuw in te stellen.

void MediaStreamer::Restart()
{
    if (m_reader == nullptr)
    {
        return;
    }

    PROPVARIANT var = {0};
    var.vt = VT_I8;

    DX::ThrowIfFailed(
        m_reader->SetCurrentPosition(GUID_NULL, var)
        );
}

Als u audiolussen wilt implementeren voor één buffer (of voor een volledig geluid dat volledig in het geheugen is geladen), kunt u het veld XAUDIO2_BUFFER::LoopCount instellen op XAUDIO2_LOOP_INFINITE wanneer u het geluid initialiseert. Marble Maze gebruikt deze techniek om het rollende geluid voor het marmer af te spelen.

if (sound == RollingEvent)
{
    m_soundEffects[sound].m_audioBuffer.LoopCount = XAUDIO2_LOOP_INFINITE;
}

Voor de achtergrondmuziek beheert Marble Maze de buffers echter rechtstreeks, zodat het de hoeveelheid geheugen die wordt gebruikt beter kan beheren. Wanneer uw muziekbestanden groot zijn, kunt u de muziekgegevens streamen naar kleinere buffers. Dit kan helpen om geheugengrootte te verdelen met de frequentie van de mogelijkheid van het spel om audiogegevens te verwerken en te streamen.

Aanbeveling

Als uw game een lage of verschillende framesnelheid heeft, kan het verwerken van audio op de hoofdthread onverwachte pauzes of pops in de audio produceren omdat de audio-engine onvoldoende gebufferde audiogegevens heeft om mee te werken. Als uw game gevoelig is voor dit probleem, kunt u overwegen om audio te verwerken op een afzonderlijke thread die geen rendering uitvoert. Deze aanpak is vooral handig op computers met meerdere processors omdat uw game niet-actieve processors kan gebruiken.

Reageren op game-gebeurtenissen

De audioklasse biedt methoden zoals PlaySoundEffect, IsSoundEffectStarted, StopSoundEffect, SetSoundEffectVolume, SetSoundEffectPitch en SetSoundEffectFilter om het spel in staat te stellen om te bepalen wanneer geluiden worden afgespeeld en gestopt, en om geluidseigenschappen zoals volume en toonhoogte te regelen. Als het marmer bijvoorbeeld van het doolhof valt, roept MarbleMazeMain::Update de methode Audio::PlaySoundEffect aan om het FallingEvent geluid af te spelen.

m_audio.PlaySoundEffect(FallingEvent);

De methode Audio::PlaySoundEffect roept de methode IXAudio2SourceVoice::Start aan om het geluid af te spelen. Als de methode IXAudio2SourceVoice::Start al is aangeroepen, wordt deze niet opnieuw gestart. Audio::PlaySoundEffect voert vervolgens aangepaste logica uit voor bepaalde geluiden.

void Audio::PlaySoundEffect(SoundEvent sound)
{
    XAUDIO2_BUFFER buf = {0};
    XAUDIO2_VOICE_STATE state = {0};

    if (m_engineExperiencedCriticalError)
    {
        // If there's an error, then we'll recreate the engine on the next
        // render pass.
        return;
    }

    SoundEffectData* soundEffect = &m_soundEffects[sound];
    HRESULT hr = soundEffect->m_soundEffectSourceVoice->Start();

    if FAILED(hr)
    {
        m_engineExperiencedCriticalError = true;
        return;
    }

    // For one-off voices, submit a new buffer if there's none queued up,
    // and allow up to two collisions to be queued up. 
    if (sound != RollingEvent)
    {
        XAUDIO2_VOICE_STATE state = {0};

        soundEffect->m_soundEffectSourceVoice->
            GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);

        if (state.BuffersQueued == 0)
        {
            soundEffect->m_soundEffectSourceVoice->
                SubmitSourceBuffer(&soundEffect->m_audioBuffer);
        }
        else if (state.BuffersQueued < 2 && sound == CollisionEvent)
        {
            soundEffect->m_soundEffectSourceVoice->
                SubmitSourceBuffer(&soundEffect->m_audioBuffer);
        }

        // For the menu clicks, we want to stop the voice and replay the click
        // right away.
        // Note that stopping and then flushing could cause a glitch due to the
        // waveform not being at a zero-crossing, but due to the nature of the 
        // sound (fast and 'clicky'), we don't mind.
        if (state.BuffersQueued > 0 && sound == MenuChangeEvent)
        {
            soundEffect->m_soundEffectSourceVoice->Stop();
            soundEffect->m_soundEffectSourceVoice->FlushSourceBuffers();

            soundEffect->m_soundEffectSourceVoice->
                SubmitSourceBuffer(&soundEffect->m_audioBuffer);

            soundEffect->m_soundEffectSourceVoice->Start();
        }
    }

    m_soundEffects[sound].m_soundEffectStarted = true;
}

Voor andere geluiden dan rollen roept de methode Audio::PlaySoundEffectIXAudio2SourceVoice::GetState op om het aantal buffers dat door de bronstem wordt afgespeeld te bepalen. Hiermee wordt IXAudio2SourceVoice::SubmitSourceBuffer aangeroepen om de audiogegevens voor het geluid toe te voegen aan de invoerwachtrij van de stem als er geen buffers actief zijn. Met de methode Audio::PlaySoundEffect kan het botsingsgeluid ook twee keer op volgorde worden afgespeeld. Dit gebeurt bijvoorbeeld wanneer de marmeren botsen met een hoek van het doolhof.

Zoals al is beschreven, gebruikt de klasse Audio de vlag XAUDIO2_LOOP_INFINITE wanneer het geluid voor de rolling gebeurtenis wordt geïnitialiseerd. Het geluid begint met herhaalde weergave op het moment dat Audio::PlaySoundEffect voor het eerst wordt aangeroepen voor deze gebeurtenis. Om de afspeellogica voor het rollende geluid te vereenvoudigen, dempt Marble Maze het geluid in plaats van het te stoppen. Naarmate de marmeren snelheid verandert, verandert Marble Maze de toonhoogte en het volume van het geluid om het een realistischer effect te geven. Hieronder ziet u hoe de methode MarbleMazeMain::Update de toonhoogte en het volume van het marmer bijwerkt naarmate de snelheid verandert en hoe het geluid wordt gedempt door het volume in te stellen op nul wanneer het marmer stopt.

// Play the roll sound only if the marble is actually rolling.
if (ci.isRollingOnFloor && volume > 0)
{
    if (!m_audio.IsSoundEffectStarted(RollingEvent))
    {
        m_audio.PlaySoundEffect(RollingEvent);
    }

    // Update the volume and pitch by the velocity.
    m_audio.SetSoundEffectVolume(RollingEvent, volume);
    m_audio.SetSoundEffectPitch(RollingEvent, pitch);

    // The rolling sound has at most 8000Hz sounds, so we linearly
    // ramp up the low-pass filter the faster we go.
    // We also reduce the Q-value of the filter, starting with a
    // relatively broad cutoff and get progressively tighter.
    m_audio.SetSoundEffectFilter(
        RollingEvent,
        600.0f + 8000.0f * volume,
        XAUDIO2_MAX_FILTER_ONEOVERQ - volume*volume
        );
}
else
{
    m_audio.SetSoundEffectVolume(RollingEvent, 0);
}

Reageren op onderbrekings- en hervattingsgebeurtenissen

Marble Maze-toepassingsstructuur beschrijft hoe Marble Maze ondersteuning biedt voor onderbreken en hervatten. Wanneer het spel wordt onderbroken, pauzeert het de audio. Wanneer het spel wordt hervat, wordt de audio hervat waar het was gebleven. We doen dit om de best practice te volgen om resources niet te gebruiken wanneer u weet dat ze niet nodig zijn.

De methode Audio::SuspendAudio wordt aangeroepen wanneer het spel wordt onderbroken. Met deze methode wordt de IXAudio2::StopEngine methode aangeroepen om alle audio te stoppen. Hoewel IXAudio2::StopEngine alle audio-uitvoer onmiddellijk stopt, blijven de audiografiek en de effectparameters behouden (bijvoorbeeld het reverb-effect dat wordt toegepast wanneer de marmeren stuiteren).

// Uses the IXAudio2::StopEngine method to stop all audio immediately.  
// It leaves the audio graph untouched, which preserves all effect parameters   
// and effect histories (like reverb effects) voice states, pending buffers,  
// cursor positions and so on. 
// When the engines are restarted, the resulting audio will sound as if it had  
// never been stopped except for the period of silence. 
void Audio::SuspendAudio()
{
    if (m_engineExperiencedCriticalError)
    {
        return;
    }

    if (m_isAudioStarted)
    {
        m_musicEngine->StopEngine();
        m_soundEffectEngine->StopEngine();
    }

    m_isAudioStarted = false;
}

De methode Audio::ResumeAudio wordt aangeroepen wanneer het spel wordt hervat. Deze methode maakt gebruik van de methode IXAudio2::StartEngine om de audio opnieuw op te starten. Omdat de aanroep naar IXAudio2::StopEngine de audiografiekstructuur en de effectparameters behoudt, hervat de audio-uitvoer vanaf het punt waar het was gestopt.

// Restarts the audio streams. A call to this method must match a previous call
// to SuspendAudio. This method causes audio to continue where it left off.
// If there is a problem with the restart, the m_engineExperiencedCriticalError
// flag is set. The next call to Render will recreate all the resources and
// reset the audio pipeline.
void Audio::ResumeAudio()
{
    if (m_engineExperiencedCriticalError)
    {
        return;
    }

    HRESULT hr = m_musicEngine->StartEngine();
    HRESULT hr2 = m_soundEffectEngine->StartEngine();

    if (FAILED(hr) || FAILED(hr2))
    {
        m_engineExperiencedCriticalError = true;
    }
}

Het verwerken van hoofdtelefoons en apparaatwijzigingen

Marble Maze maakt gebruik van engine-callbacks om XAudio2-enginefouten af te handelen, zoals wanneer het audioapparaat verandert. Een waarschijnlijke oorzaak van een apparaatwijziging is wanneer de gamegebruiker verbinding maakt of de hoofdtelefoon loskoppelt. We raden u aan de engine callback te implementeren waarmee apparaatwijzigingen worden verwerkt. Anders stopt uw game met het afspelen van geluid wanneer de gebruiker een hoofdtelefoon aansluit of verwijdert, totdat het spel opnieuw wordt opgestart.

Audio.h definieert de klasse AudioEngineCallbacks . Deze klasse implementeert de IXAudio2EngineCallback-interface .

class AudioEngineCallbacks: public IXAudio2EngineCallback
{
private:
    Audio* m_audio;

public :
    AudioEngineCallbacks(){};
    void Initialize(Audio* audio);

    // Called by XAudio2 just before an audio processing pass begins.
    void _stdcall OnProcessingPassStart(){};

    // Called just after an audio processing pass ends.
    void  _stdcall OnProcessingPassEnd(){};

    // Called when a critical system error causes XAudio2
    // to be closed and restarted. The error code is given in Error.
    void  _stdcall OnCriticalError(HRESULT Error);
};

Met de IXAudio2EngineCallback-interface kan uw code worden gewaarschuwd wanneer audioverwerkingsevenementen optreden en wanneer de engine een kritieke fout tegenkomt. Als u zich wilt registreren voor callbacks, roept Marble Maze de methode IXAudio2::RegisterForCallbacks in Audio::CreateResources aan, nadat het het IXAudio2-object voor de muziekengine heeft gemaakt.

m_musicEngineCallback.Initialize(this);
m_musicEngine->RegisterForCallbacks(&m_musicEngineCallback);

Marble Maze vereist geen melding wanneer de audioverwerking begint of eindigt. Daarom implementeert het de methoden IXAudio2EngineCallback::OnProcessingPassStart en IXAudio2EngineCallback::OnProcessingPassEnd om niets te doen. Voor de methode IXAudio2EngineCallback::OnCriticalError roept Marble Maze de SetEngineExperiencedCriticalError methode aan, waarmee de m_engineExperiencedCriticalError vlag wordt ingesteld.

// Audio.cpp

// Called when a critical system error causes XAudio2 
// to be closed and restarted. The error code is given in Error. 
void  _stdcall AudioEngineCallbacks::OnCriticalError(HRESULT Error)
{
    m_audio->SetEngineExperiencedCriticalError();
}
// Audio.h (Audio class)

// This flag can be used to tell when the audio system 
// is experiencing critical errors.
// XAudio2 gives a critical error when the user unplugs
// the headphones and a new speaker configuration is generated.
void SetEngineExperiencedCriticalError()
{
    m_engineExperiencedCriticalError = true;
}

Wanneer er een kritieke fout optreedt, stopt de audioverwerking en mislukken alle extra aanroepen naar XAudio2. Als u wilt herstellen van deze situatie, moet u het XAudio2-exemplaar vrijgeven en een nieuwe maken. De methode Audio::Render, die elk frame wordt aangeroepen vanuit de gamelus, controleert eerst de m_engineExperiencedCriticalError vlag. Indien deze vlag is ingesteld, wordt het wissen van de vlag uitgevoerd, de huidige XAudio2-instantie vrijgegeven, de voorzieningen geïnitieerd en vervolgens de achtergrondmuziek gestart.

if (m_engineExperiencedCriticalError)
{
    m_engineExperiencedCriticalError = false;
    ReleaseResources();
    Initialize();
    CreateResources();
    Start();
    if (m_engineExperiencedCriticalError)
    {
        return;
    }
}

Marble Maze gebruikt ook de m_engineExperiencedCriticalError vlag om te beschermen tegen het bellen naar XAudio2 wanneer er geen audioapparaat beschikbaar is. De methode MarbleMazeMain::Update verwerkt bijvoorbeeld geen audio voor rol- of botsingsevenementen wanneer dit vlaggetje is ingesteld. De app probeert in elk frame de audio-engine te repareren als dat nodig is; echter, de m_engineExperiencedCriticalError vlag kan altijd worden ingesteld als de computer geen audioapparaat heeft of als de hoofdtelefoon is losgekoppeld en er geen ander beschikbaar audioapparaat is.

Waarschuwing

Voer in de regel geen blokkeringsbewerkingen uit in de hoofdtekst van een engine callback. Dit kan prestatieproblemen veroorzaken. Marble Maze stelt een vlag in de OnCriticalError callback en verwerkt later de fout tijdens de reguliere audioverwerkingsfase. Zie XAudio2 Callbacks voor meer informatie over XAudio2-callbacks.

Conclusie

Dat rondt het voorbeeld van het Marble Maze-spel af! Hoewel het een relatief eenvoudig spel is, bevat het veel van de belangrijke onderdelen die in een UWP DirectX-game gaan en is het een goed voorbeeld om te volgen bij het maken van uw eigen spel.

Nu u klaar bent met volgen, kunt u proberen te experimenteren met de broncode en te zien wat er gebeurt. Of bekijk een eenvoudig UWP-spel met DirectX, een ander UWP DirectX-gamevoorbeeld.

Klaar om verder te gaan met DirectX? Bekijk vervolgens onze handleidingen bij DirectX-programmering.

Als u geïnteresseerd bent in de ontwikkeling van games op UWP in het algemeen, raadpleegt u de documentatie bij Game programming.