Bagikan melalui


Tambahkan suara

Catatan

Topik ini adalah bagian dari membuat permainan Platform Windows Universal sederhana (UWP) dengan seri tutorial DirectX. Topik di tautan tersebut mengatur konteks untuk seri.

Dalam topik ini, kami membuat mesin suara sederhana menggunakan API XAudio2 . Jika Anda baru menggunakan XAudio2, kami telah menyertakan intro singkat di bawah Konsep audio.

Catatan

Jika Anda belum mengunduh kode game terbaru untuk sampel ini, buka game sampel Direct3D. Sampel ini adalah bagian dari koleksi besar sampel fitur UWP. Untuk petunjuk tentang cara mengunduh sampel, lihat Contoh aplikasi untuk pengembangan Windows.

Tujuan

Tambahkan suara ke dalam permainan sampel menggunakan XAudio2.

Menentukan mesin audio

Dalam permainan sampel, objek audio dan perilaku didefinisikan dalam tiga file:

  • Audio.h/.cpp: Menentukan objek Audio , yang berisi sumber daya XAudio2 untuk pemutaran suara. Ini juga mendefinisikan metode untuk menangguhkan dan melanjutkan pemutaran audio jika game dijeda atau dinonaktifkan.
  • MediaReader.h/.cpp: Menentukan metode untuk membaca file .wav audio dari penyimpanan lokal.
  • SoundEffect.h/.cpp: Menentukan objek untuk pemutaran suara dalam game.

Gambaran Umum

Ada tiga bagian utama dalam menyiapkan pemutaran audio ke dalam game Anda.

  1. Membuat dan menginisialisasi sumber daya audio
  2. Memuat file audio
  3. Mengaitkan suara ke objek

Semuanya didefinisikan dalam metode Simple3DGame::Initialize . Jadi mari kita pertama-tama periksa metode ini dan kemudian selusin detail lebih lanjut di setiap bagian.

Setelah menyiapkan, kita belajar cara memicu efek suara untuk diputar. Untuk informasi selengkapnya, buka Memutar suara.

Simple3DGame::Inisialisasi metode

Dalam Simple3DGame::Initialize, di mana m_controller dan m_renderer juga diinisialisasi, kami mengatur mesin audio dan menyiapkannya untuk memutar suara.

  • Buat m_audioController, yang merupakan instans kelas Audio .
  • Buat sumber daya audio yang diperlukan menggunakan metode Audio::CreateDeviceIndependentResources . Di sini, dua objek XAudio2 - objek mesin musik dan objek mesin suara, dan suara yang menguasai untuk masing-masing objek dibuat. Objek mesin musik dapat digunakan untuk memainkan musik latar belakang untuk permainan Anda. Mesin suara dapat digunakan untuk memainkan efek suara dalam permainan Anda. Untuk informasi selengkapnya, lihat Membuat dan menginisialisasi sumber daya audio.
  • Buat mediaReader, yang merupakan instans kelas MediaReader . MediaReader, yang merupakan kelas pembantu untuk kelas SoundEffect , membaca file audio kecil secara sinkron dari lokasi file dan mengembalikan data suara sebagai array byte.
  • Gunakan MediaReader::LoadMedia untuk memuat file suara dari lokasinya dan membuat variabel targetHitSound untuk menyimpan data suara .wav yang dimuat. Untuk informasi selengkapnya, lihat Memuat file audio.

Efek suara dikaitkan dengan objek permainan. Jadi ketika tabrakan terjadi dengan objek game itu, itu memicu efek suara untuk dimainkan. Dalam permainan sampel ini, kita memiliki efek suara untuk amunisi (apa yang kita gunakan untuk menembak target dengan) dan untuk target.

  • Di kelas GameObject, ada properti HitSound yang digunakan untuk mengaitkan efek suara ke objek.
  • Buat instans baru kelas SoundEffect dan inisialisasikan. Selama inisialisasi, suara sumber untuk efek suara dibuat.
  • Kelas ini memutar suara menggunakan suara mastering yang disediakan dari kelas Audio . Data suara dibaca dari lokasi file menggunakan kelas MediaReader . Untuk informasi selengkapnya, lihat Mengaitkan suara ke objek.

Catatan

Pemicu aktual untuk memainkan suara ditentukan oleh gerakan dan tabrakan objek game ini. Oleh karena itu, panggilan untuk benar-benar memutar suara ini didefinisikan dalam metode Simple3DGame::UpdateDynamics . Untuk informasi selengkapnya, buka Memutar suara.

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

Membuat dan menginisialisasi sumber daya audio

  • Gunakan XAudio2Create, API XAudio2, untuk membuat dua objek XAudio2 baru yang menentukan mesin musik dan efek suara. Metode ini mengembalikan penunjuk ke antarmuka IXAudio2 objek yang mengelola semua status mesin audio, utas pemrosesan audio, grafik suara, dan banyak lagi.
  • Setelah mesin dibuat, gunakan IXAudio2::CreateMasteringVoice untuk membuat suara penguasaan untuk setiap objek mesin suara.

Untuk informasi selengkapnya, buka Cara: Menginisialisasi XAudio2.

Audio::CreateDeviceIndependentResources metode

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

Memuat file audio

Dalam permainan sampel, kode untuk membaca file format audio didefinisikan dalam MediaReader.h/cpp__. Untuk membaca file audio .wav yang dikodekan, panggil MediaReader::LoadMedia, meneruskan nama file .wav sebagai parameter input.

Metode MediaReader::LoadMedia

Metode ini menggunakan API Media Foundation untuk dibaca dalam file audio .wav sebagai buffer Modulasi Kode Pulse (PCM).

Menyiapkan Pembaca Sumber

  1. Gunakan MFCreateSourceReaderFromURL untuk membuat pembaca sumber media (IMFSourceReader).
  2. Gunakan MFCreateMediaType untuk membuat objek jenis media (IMFMediaType) (mediaType). Ini mewakili deskripsi format media.
  3. Tentukan bahwa output yang didekodekan mediaType adalah audio PCM, yang merupakan jenis audio yang dapat digunakan XAudio2 .
  4. Mengatur jenis media output yang didekodekan untuk pembaca sumber dengan memanggil IMFSourceReader::SetCurrentMediaType.

Untuk informasi selengkapnya tentang mengapa kami menggunakan Pembaca Sumber, buka Pembaca Sumber.

Menjelaskan format data aliran audio

  1. Gunakan IMFSourceReader::GetCurrentMediaType untuk mendapatkan jenis media saat ini untuk aliran.
  2. Gunakan IMFMediaType::MFCreateWaveFormatExFromMFMediaType untuk mengonversi jenis media audio saat ini menjadi buffer WAVEFORMATEX , menggunakan hasil operasi sebelumnya sebagai input. Struktur ini menentukan format data aliran audio gelombang yang digunakan setelah audio dimuat.

Format WAVEFORMATEX dapat digunakan untuk menjelaskan buffer PCM. Dibandingkan dengan struktur WAVEFORMATEXTENSIBLE , itu hanya dapat digunakan untuk menggambarkan subset format gelombang audio. Untuk informasi selengkapnya tentang perbedaan antara WAVEFORMATEX dan WAVEFORMATEXTENSIBLE, lihat Deskriptor Format Gelombang yang Dapat Diperluas.

Membaca aliran audio

  1. Dapatkan durasi, dalam detik, aliran audio dengan memanggil IMFSourceReader::GetPresentationAttribute lalu mengonversi durasi menjadi byte.
  2. Baca file audio sebagai aliran dengan memanggil IMFSourceReader::ReadSample. ReadSample membaca sampel berikutnya dari sumber media.
  3. Gunakan IMFSample::ConvertToContiguousBuffer untuk menyalin konten buffer sampel audio (sampel) ke dalam array (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;
}

Mengaitkan suara ke objek

Mengaitkan suara ke objek terjadi ketika game menginisialisasi, dalam metode Simple3DGame::Initialize .

Rekap:

  • Di kelas GameObject, ada properti HitSound yang digunakan untuk mengaitkan efek suara ke objek.
  • Buat instans baru objek kelas SoundEffect dan kaitkan dengan objek game. Kelas ini memutar suara menggunakan API XAudio2 . Ini menggunakan suara mastering yang disediakan oleh kelas Audio . Data suara dapat dibaca dari lokasi file menggunakan kelas MediaReader .

SoundEffect::Initialize digunakan untuk memulai instans SoundEffect dengan parameter input berikut: pointer ke objek mesin suara (objek IXAudio2 yang dibuat dalam metode Audio::CreateDeviceIndependentResources ), pointer untuk memformat file .wav menggunakan MediaReader::GetOutputWaveFormatEx, dan data suara yang dimuat menggunakan metode MediaReader::LoadMedia . Selama inisialisasi, suara sumber untuk efek suara juga dibuat.

SoundEffect::Inisialisasi metode

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

Putar suara

Pemicu untuk memutar efek suara didefinisikan dalam metode Simple3DGame::UpdateDynamics karena di sinilah pergerakan objek diperbarui dan tabrakan antar objek ditentukan.

Karena interaksi antara objek sangat berbeda, tergantung pada permainan, kita tidak akan membahas dinamika objek game di sini. Jika Anda tertarik untuk memahami implementasinya, buka metode Simple3DGame::UpdateDynamics .

Pada prinsipnya, ketika tabrakan terjadi, itu memicu efek suara untuk dimainkan dengan memanggil SoundEffect::P laySound. Metode ini menghentikan efek suara yang saat ini diputar dan mengantrekan buffer dalam memori dengan data suara yang diinginkan. Ini menggunakan suara sumber untuk mengatur volume, mengirimkan data suara, dan memulai pemutaran.

Metode SoundEffect::P laySound

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

Metode Simple3DGame::UpdateDynamics

Metode Simple3DGame::UpdateDynamics mengurus interaksi dan tabrakan antar objek game. Ketika objek bertabrakan (atau berpotangan), objek memicu efek suara terkait untuk diputar.

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
}

Langkah berikutnya

Kami telah membahas kerangka kerja UWP, grafis, kontrol, antarmuka pengguna, dan audio game Windows 10. Bagian berikutnya dari tutorial ini, Memperluas permainan sampel, menjelaskan opsi lain yang dapat digunakan saat mengembangkan game.

Konsep audio

Untuk pengembangan game Windows 10, gunakan XAudio2 versi 2.9. Versi ini dikirim dengan Windows 10. Untuk informasi selengkapnya, buka Versi XAudio2.

AudioX2 adalah API tingkat rendah yang menyediakan fondasi pemrosesan dan pencampuran sinyal. Untuk informasi selengkapnya, lihat Konsep Kunci XAudio2.

Suara XAudio2

Ada tiga jenis objek suara XAudio2: sumber, submix, dan suara penguasaan. Suara adalah objek yang digunakan XAudio2 untuk memproses, memanipulasi, dan memutar data audio.

  • Suara sumber beroperasi pada data audio yang disediakan oleh klien.
  • Suara sumber dan submix mengirimkan outputnya ke satu atau beberapa submix atau suara penguasaan.
  • Submix dan suara mastering mencampur audio dari semua suara yang memberinya makan, dan beroperasi pada hasilnya.
  • Menguasai suara menerima data dari suara sumber dan suara submix, dan mengirim data tersebut ke perangkat keras audio.

Untuk informasi selengkapnya, buka suara XAudio2.

Grafik audio

Grafik audio adalah kumpulan suara XAudio2. Audio dimulai di satu sisi grafik audio di suara sumber, secara opsional melewati satu atau beberapa suara submix, dan berakhir pada suara yang menguasai. Grafik audio akan berisi suara sumber untuk setiap suara yang saat ini diputar, nol atau lebih suara submix, dan satu suara penguasaan. Grafik audio paling sederhana, dan minimum yang diperlukan untuk membuat kebisingan di XAudio2, adalah satu sumber suara yang dihasilkan langsung ke suara penguasaan. Untuk informasi selengkapnya, buka Grafik audio.

Pembacaan tambahan

File .h audio kunci

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