Tambahkan suara

Catatan

Topik ini adalah bagian dari buat game 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 contoh permainan Direct3D. Sampel ini adalah bagian dari kumpulan besar sampel fitur UWP. Untuk petunjuk tentang cara mengunduh sampel, lihat Aplikasi sampel untuk pengembangan Windows.

Tujuan

Tambahkan suara ke dalam game sampel menggunakan XAudio2.

Menentukan mesin audio

Dalam permainan sampel, objek dan perilaku audio 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. Kaitkan suara ke objek

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

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

Metode Simple3DGame::Initialize

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 master untuk masing-masing dibuat. Objek mesin musik dapat digunakan untuk memutar 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, kami memiliki efek suara untuk amunisi (apa yang kami 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 master 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 master 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 ditentukan 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 membaca 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 mediaType yang didekode adalah audio PCM, yang merupakan jenis audio yang dapat digunakan XAudio2 .
  4. Mengatur jenis media output yang didekode 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 Extensible Wave-Format Descriptors.

Membaca aliran audio

  1. Dapatkan durasi, dalam detik, aliran audio dengan memanggil IMFSourceReader::GetPresentationAttribute lalu konversikan 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;
}

Kaitkan 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 memainkan 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 ), penunjuk ke format 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 permainan 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 mengantre 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()
        );
}

Simple3DGame::UpdateDynamics method

Metode Simple3DGame::UpdateDynamics mengurus interaksi dan tabrakan antar objek game. Ketika objek bertabrakan (atau berpotangan), objek akan 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 game 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 pemrosesan sinyal dan fondasi pencampuran. Untuk informasi selengkapnya, lihat Konsep Kunci XAudio2.

Suara XAudio2

Ada tiga jenis objek suara XAudio2: sumber, submix, dan suara master. 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 master.
  • Submix dan suara master 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 dalam suara sumber, secara opsional melewati satu atau beberapa suara submix, dan berakhir dengan suara yang menguasai. Grafik audio akan berisi suara sumber untuk setiap suara yang saat ini diputar, nol atau lebih suara submix, dan satu suara mastering. Grafik audio paling sederhana, dan minimum yang diperlukan untuk membuat kebisingan di XAudio2, adalah satu sumber suara yang dihasilkan langsung ke suara yang menguasai. Untuk informasi selengkapnya, buka Grafik audio.

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