Megosztás a következőn keresztül:


Hang hozzáadása

Megjegyzés:

Ez a témakör a Egyszerű Univerzális Windows Platform (UWP) játék készítése DirectX-szel című oktatóanyag-sorozat része. A hivatkozás témaköre beállítja a sorozat kontextusát.

Ebben a témakörben egy egyszerű hangmotort hozunk létre XAudio2 API-k használatával. Ha új az XAudio2, mellékeltünk egy rövid bevezetőt a Hangfogalmak alatt.

Megjegyzés:

Ha még nem töltötte le a minta legújabb játékkódját, lépjen tovább a Direct3D minta játék-ra. Ez a minta az UWP-szolgáltatásminták nagy gyűjteményének része. A minta letöltésére vonatkozó utasításokért lásd a Windows-fejlesztéshez készült mintaalkalmazásokat.

Célkitűzés

Vegyen fel hangokat a mintajátékba az XAudio2 használatával.

A hangmotor definiálása

A mintajátékban a hangobjektumok és a viselkedések három fájlban vannak definiálva:

  • Audio.h/.cpp: Meghatározza a hangobjektumot , amely a hanglejátszáshoz szükséges XAudio2-erőforrásokat tartalmazza. Emellett meghatározza a hanglejátszás felfüggesztésének és folytatásának módját is, ha a játék szünetel vagy inaktivált.
  • MediaReader.h/.cpp: Meghatározza a hang .wav fájlok helyi tárolóból való olvasásának módszereit.
  • SoundEffect.h/.cpp: Egy objektumot határoz meg a játékon belüli hanglejátszáshoz.

Áttekintés

Három fő részből áll a hanglejátszás beállítása a játékba.

  1. A hangerőforrások létrehozása és inicializálása
  2. Hangfájl betöltése
  3. Hang társítása objektumhoz

Ezek mindegyike a Simple3DGame::Initialize metódusban van definiálva. Először vizsgáljuk meg ezt a módszert, majd vizsgáljuk meg az egyes szakaszok további részleteit.

A beállítás után megtanuljuk, hogyan aktiválható a hangeffektusok lejátszása. További információ: Hang lejátszása.

Simple3DGame::Inicializálási módszer

A Simple3DGame::Initializefüggvényben, ahol a m_controller és a m_renderer is inicializálva van, beállítjuk a hangmotort, és felkészítjük a hangok lejátszására.

  • Hozza létre m_audioController-et, amely a Audio osztály példánya.
  • Hozza létre a szükséges hangerőforrásokat a Hang::CreateDeviceIndependentResources metódussal. Itt két XAudio2 objektumot hoztak létre – egy zenemotor-objektumot és egy hangmotor-objektumot, valamint mindegyikhez egy mesterhangot. A zenemotor objektum használható háttérzene lejátszására a játékhoz. A hangmotor használható hangeffektusok lejátszására a játékban. További információ: Hangerőforrások létrehozása és inicializálása.
  • Hozzon létre mediaReadert, amely a MediaReader osztály egy példánya. A MediaReader, amely a SoundEffect osztály segédosztálya, szinkron módon olvassa be a kis hangfájlokat a fájl helyéről, és hangadatokat ad vissza bájttömbként.
  • A MediaReader::LoadMedia használatával betölthet hangfájlokat a helyéről, és létrehozhat egy targetHitSound változót a betöltött .wav hangadatok tárolásához. További információért lásd: Hangfájl betöltése.

A hangeffektusok a játékobjektumhoz vannak társítva. Tehát amikor ütközés történik a játék objektummal, az aktiválja a lejátszandó hangeffektust. Ebben a mintajátékban hangeffektusokkal rendelkezünk a lőszerhez (a célpontok lőéséhez használt eszközökkel) és a célhoz.

  • A GameObject osztályban van egy HitSound tulajdonság, amely a hangeffektus objektumhoz való társítására szolgál.
  • Hozzon létre egy új példányt a SoundEffect osztályból, és inicializálja azt. Az inicializálás során létrejön a hangeffektus forráshangja.
  • Ez az osztály a Hang osztályból származó mesterhang használatával játszik le hangot. A hangadatok a MediaReader osztály használatával olvashatók a fájl helyről. További információ: Hang társítása objektumhoz.

Megjegyzés:

A hang lejátszásának tényleges eseményindítóját a játékobjektumok mozgása és ütközése határozza meg. Ezért ezeknek a hangoknak a lejátszására irányuló hívás a Simple3DGame::UpdateDynamics metódusban van definiálva. További információ: Hang lejátszása.

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

A hangerőforrások létrehozása és inicializálása

  • A XAudio2Create, egy XAudio2 API használatával hozzon létre két új XAudio2 objektumot, amelyek meghatározzák a zene- és hangeffektusmotorokat. Ez a módszer egy mutatót ad vissza az objektum IXAudio2 felületére, amely kezeli az összes hangmotor-állapotot, a hangfeldolgozási szálat, a hanggráfot stb.
  • A motorok példányosítása után a IXAudio2::CreateMasteringVoice használatával hozzon létre egy mesterhangzást minden egyes hangmotor objektumhoz.

További információ : Útmutató: XAudio2 inicializálása.

Audió::CreateDeviceIndependentResources metódus

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

Hangfájl betöltése

A mintajátékban a hangformátum-fájlok olvasására szolgáló kód meg van határozva a MediaReader.h/cpp__. Kódolt .wav hangfájl olvasásához hívja meg a MediaReader::LoadMedia parancsot, és adja meg a .wav fájlnevét bemeneti paraméterként.

MediaReader::LoadMedia módszer

Ez a módszer a Media Foundation API-kkal olvassa be a .wav hangfájlt Pulse Code Modulation (PCM) pufferként.

A Forrásolvasó beállítása

  1. Az MFCreateSourceReaderFromURL használatával hozzon létre egy médiaforrás-olvasót (IMFSourceReader).
  2. Az MFCreateMediaType használatával hozzon létre egy médiatípusú (IMFMediaType) objektumot (mediaType). Ez egy médiaformátum leírását jelöli.
  3. Adja meg, hogy a mediaType dekódolt kimenete PCM-hang, amely az XAudio2 által használható hangtípus.
  4. Beállítja a forrásolvasó dekódolt kimeneti médiatípusát az IMFSourceReader::SetCurrentMediaType meghívásával.

A Forrásolvasó használatának okáról a Forrásolvasóban talál további információt.

A hangstream adatformátumának ismertetése

  1. Használja az IMFSourceReader::GetCurrentMediaType parancsot a stream aktuális médiatípusának lekéréséhez.
  2. Az IMFMediaType::MFCreateWaveFormatExFromMFMediaType használatával konvertálja az aktuális hanganyagtípust WAVEFORMATEX pufferzé a korábbi művelet eredményeinek bemenetként való felhasználásával. Ez a struktúra a hang betöltése után használt hullámos hangstream adatformátumát határozza meg.

A WAVEFORMATEX formátum a PCM-puffer leírására használható. A WAVEFORMATEXTENSIBLE struktúrához képest csak a hanghullám-formátumok egy részhalmazának leírására használható. A WAVEFORMATEX és a WAVEFORMATEXTENSIBLE közötti különbségekről további információt az Extensible Wave-Format Leírók című témakörben talál.

Hangfolyam olvasása

  1. Az IMFSourceReader::GetPresentationAttribute meghívásával másodpercben lekérheti a hangstream időtartamát, majd bájttá alakítja az időtartamot.
  2. Olvassa be a hangfájlt folyamként IMFSourceReader::ReadSamplemeghívásával. ReadSample leolvassa a következő mintát a médiaforrásból.
  3. Használja a IMFSample::ConvertToContiguousBuffer függvényt, hogy a hangmintapuffer tartalmát (minta) belemásolja egy tömbbe (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;
}

Hang társítása objektumhoz

A hangok társítása az objektumhoz a játék inicializálásakor történik a Simple3DGame::Initialize metódusban.

Emlékeztető:

  • A GameObject osztályban van egy HitSound tulajdonság, amely a hangeffektus objektumhoz való társítására szolgál.
  • Hozzon létre egy új példányt a SoundEffect osztály objektumából, és társítsa azt a játékobjektumhoz. Ez az osztály XAudio2 API-kkal játszik le hangot. A Audio osztály által biztosított mastering hangot használja. A hangadatok a MediaReader osztály használatával olvashatók a fájl helyről.

SoundEffect::Initialize a SoundEffect-példány inicializálására szolgál a következő bemeneti paraméterekkel: mutató a hangmotor objektumára (IXAudio2 objektumok a Audio::CreateDeviceIndependentResources metódusban létrehozva), mutató a .wav fájl formátumára a MediaReader::GetOutputWaveFormatExhasználatával, valamint a hangadatokra, amelyeket a MediaReader::LoadMedia metódussal töltöttek be. Az inicializálás során a hangeffektus forráshangja is létrejön.

SoundEffect::Inicializálási módszer

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

A hang lejátszása

A hangeffektusok lejátszására szolgáló triggerek a Simple3DGame::UpdateDynamics metódusban vannak definiálva, mivel itt frissülnek az objektumok mozgása, és az objektumok közötti ütközés lesz meghatározva.

Mivel az objektumok közötti interakció nagyban különbözik, a játéktól függően itt nem fogjuk megvitatni a játékobjektumok dinamikáját. Ha szeretné megismerni a megvalósítást, nyissa meg a Simple3DGame::UpdateDynamics metódust .

Elvileg ütközés esetén az SoundEffect::PlaySoundmeghívásával aktiválódik a hangeffektus. Ez a módszer leállítja a jelenleg lejátszott hangeffektusokat, és a kívánt hangadatokkal várólistára állítja a memóriabeli puffert. Forráshang használatával állítja be a hangerőt, hangadatokat küld, és elindítja a lejátszást.

SoundEffect::PlaySound metódus

  • A forrás hangobjektum m_sourceVoice használatával indítja el a hangadat-puffer lejátszását m_soundData
  • Létrehoz egy XAUDIO2_BUFFER-t, amely a hangadatpufferre mutat, majd az IXAudio2SourceVoice::SubmitSourceBufferfüggvényt meghívva továbbítja azt.
  • A hangadatok várólistára helyezése után a SoundEffect::PlaySound a IXAudio2SourceVoice::Starthívásával indítja el a visszajátszást.
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 metódus

A Simple3DGame::UpdateDynamics metódus gondoskodik a játékobjektumok közötti interakcióról és ütközésről. Amikor az objektumok ütköznek (vagy metszenek), a kapcsolódó hangeffektust aktiválja a lejátszáshoz.

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
}

Következő lépések

A Windows 10-es játékok UWP-keretrendszerével, grafikáival, vezérlőivel, felhasználói felületével és hanganyagával foglalkoztunk. Az oktatóanyag következő része, a mintajáték kiterjesztése ismerteti a játék fejlesztésekor használható egyéb lehetőségeket.

Hangkoncepciók

Windows 10-es játékok fejlesztéséhez használja az XAudio2 2.9-es verzióját. Ezt a verziót a Windows 10-ben szállítjuk. További információ: XAudio2-verziók.

Az AudioX2 egy alacsony szintű API, amely jelfeldolgozási és keverési alapokat biztosít. További információ: XAudio2 Key Concepts.

XAudio2-hangok

Az XAudio2 hangobjektumoknak három típusa van: forrás, részmix és mesterhangok. A hangok azok az objektumok, amelyeket az XAudio2 használ a hangadatok feldolgozására, manipulálására és lejátszására.

  • A forráshangok az ügyfél által biztosított hangadatokon működnek.
  • A forráshangok és részkeverék hangok egy vagy több részmixbe vagy master hangra küldik a kimenetüket.
  • A szubmix és a mastering hangok összekeverik az őket tápláló összes hangot, és dolgoznak az eredménnyel.
  • A mesterhangok a forráshangoktól és a szubmix hangoktól fogadják az adatokat, és továbbítják az adatokat a hanghardvernek.

További információért látogasson el XAudio2 hangok.

Hanggráf

A hanggráf XAudio2 hanggyűjteménye. A hang egy hanggráf egyik oldalán indul el a forráshangokban, opcionálisan átmegy egy vagy több szubmix hangon, és mesterhangra végződik. A hanggráfok minden lejátszott hanghoz tartalmaznak forráshangot, nulla vagy több submix hangot és egy mesterhangot. Az XAudio2-ben a legegyszerűbb hanggráf, és a zaj létrehozásához szükséges minimális konfiguráció az, amikor egyetlen forráshang közvetlenül egy mastering hangra ad ki. További információ: Hangdiagramok.

További olvasnivaló

Kulcsfontosságú hang .h fájlok

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