Compartir a través de


Agregar sonido

Nota:

Este tema forma parte de la serie de tutoriales Crear un juego sencillo para la Plataforma universal de Windows (UWP) con DirectX. El tema de ese vínculo establece el contexto de la serie.

En este tema, creamos un motor de sonido sencillo mediante las API XAudio2 . Si no está familiarizado con XAudio2, hemos incluido una breve introducción en Conceptos de audio.

Nota:

Si no has descargado el código de juego más reciente para este ejemplo, ve al juego de ejemplo de Direct3D. Este ejemplo forma parte de una gran colección de ejemplos de características de UWP. Para obtener instrucciones sobre cómo descargar el ejemplo, vea Aplicaciones de ejemplo para el desarrollo de Windows.

Objetivo

Agregue sonidos al juego de ejemplo mediante XAudio2.

Definición del motor de audio

En el juego de ejemplo, los objetos y comportamientos de audio se definen en tres archivos:

  • Audio.h/.cpp: define el objeto Audio , que contiene los recursos XAudio2 para la reproducción de sonido. También define el método para suspender y reanudar la reproducción de audio si el juego está en pausa o desactivado.
  • MediaReader.h/.cpp: define los métodos para leer archivos de audio .wav del almacenamiento local.
  • SoundEffect.h/.cpp: define un objeto para la reproducción de sonido en el juego.

Información general

Hay tres partes principales para configurar la reproducción de audio en el juego.

  1. Creación e inicialización de los recursos de audio
  2. Cargar archivo de audio
  3. Asociar sonido al objeto

Todos se definen en el método Simple3DGame::Initialize . Por lo tanto, vamos a examinar primero este método y, a continuación, vamos a profundizar en más detalles en cada una de las secciones.

Después de configurar, aprendemos a desencadenar los efectos de sonido para reproducir. Para obtener más información, vaya a Reproducir el sonido.

Método Simple3DGame::Initialize

En Simple3DGame::Initialize, donde también se inicializan m_controller y m_renderer, configuramos el motor de audio y lo preparamos para reproducir sonidos.

  • Cree m_audioController, que es una instancia de la clase Audio .
  • Cree los recursos de audio necesarios mediante el método Audio::CreateDeviceIndependentResources . Aquí se crearon dos objetos XAudio2 : un objeto de motor de música y un objeto de motor de sonido, y una voz de maestro para cada uno de ellos. El objeto de motor de música se puede usar para reproducir música de fondo para tu juego. El motor de sonido se puede usar para reproducir efectos de sonido en tu juego. Para obtener más información, consulta Crear e inicializar los recursos de audio.
  • Cree mediaReader, que es una instancia de la clase MediaReader. MediaReader, que es una clase auxiliar para la clase SoundEffect , lee archivos de audio pequeños de forma sincrónica desde la ubicación del archivo y devuelve datos de sonido como una matriz de bytes.
  • Use MediaReader::LoadMedia para cargar archivos de sonido desde su ubicación y crear una variable targetHitSound para contener los datos de sonido cargados .wav. Para obtener más información, consulta Cargar archivo de audio.

Los efectos de sonido están asociados al objeto del juego. Por lo tanto, cuando se produce una colisión con ese objeto de juego, desencadena el efecto de sonido que se va a reproducir. En este juego de ejemplo, tenemos efectos de sonido para la munición (lo que usamos para disparar objetivos con) y para el objetivo.

  • En la clase GameObject , hay una propiedad HitSound que se usa para asociar el efecto de sonido al objeto.
  • Cree una nueva instancia de la clase SoundEffect e inicialícela. Durante la inicialización, se crea una voz de origen para el efecto de sonido.
  • Esta clase reproduce un sonido mediante una voz de maestro proporcionada desde la clase Audio . Los datos de sonido se leen desde la ubicación del archivo mediante la clase MediaReader . Para obtener más información, consulta Asociar sonido a objeto.

Nota:

El desencadenador real para reproducir el sonido viene determinado por el movimiento y la colisión de estos objetos de juego. Por lo tanto, la llamada a reproducir realmente estos sonidos se define en el método Simple3DGame::UpdateDynamics . Para obtener más información, vaya a Reproducir el sonido.

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

Creación e inicialización de los recursos de audio

  • Use XAudio2Create, una API XAudio2, para crear dos nuevos objetos XAudio2 que definen los motores de efectos de sonido y música. Este método devuelve un puntero a la interfaz IXAudio2 del objeto que administra todos los estados del motor de audio, el subproceso de procesamiento de audio, el gráfico de voz, etc.
  • Una vez creados los motores, use IXAudio2::CreateMasteringVoice para crear una voz de maestro para cada uno de los objetos del motor de sonido.

Para obtener más información, vaya a Cómo: Inicializar XAudio2.

Método Audio::CreateDeviceIndependentResources

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

Cargar archivo de audio

En el juego de ejemplo, el código para leer archivos de formato de audio se define en MediaReader.h/cpp__. Para leer un archivo de audio codificado .wav, llame a MediaReader::LoadMedia y pase el nombre de archivo del .wav como parámetro de entrada.

Método MediaReader::LoadMedia

Este método usa las API de Media Foundation para leer en el archivo de audio .wav como un búfer de modulación de código de pulso (PCM).

Configuración del Lector de origen

  1. Use MFCreateSourceReaderFromURL para crear un lector de origen multimedia (IMFSourceReader).
  2. Use MFCreateMediaType para crear un objeto de tipo multimedia (IMFMediaType) (mediaType). Representa una descripción de un formato multimedia.
  3. Especifique que la salida descodificada de mediaType es audio PCM, que es un tipo de audio que XAudio2 puede usar.
  4. Establece el tipo de medio de salida descodificado para el lector de origen llamando a IMFSourceReader::SetCurrentMediaType.

Para obtener más información sobre por qué usamos el Lector de origen, vaya a Lector de origen.

Describir el formato de datos de la secuencia de audio

  1. Use IMFSourceReader::GetCurrentMediaType para obtener el tipo de medio actual para la secuencia.
  2. Use IMFMediaType::MFCreateWaveFormatExFromMFMediaType para convertir el tipo de medio de audio actual en un búfer WAVEFORMATEX , utilizando los resultados de la operación anterior como entrada. Esta estructura especifica el formato de datos de la secuencia de audio de onda que se usa después de cargar el audio.

El formato WAVEFORMATEX se puede usar para describir el búfer PCM. En comparación con la estructura WAVEFORMATEXTENSIBLE , solo se puede usar para describir un subconjunto de formatos de onda de audio. Para obtener más información sobre las diferencias entre WAVEFORMATEX y WAVEFORMATEXTENSIBLE, consulta Descriptores extensibles de formato de onda.

Leer la secuencia de audio

  1. Obtenga la duración, en segundos, de la secuencia de audio llamando a IMFSourceReader::GetPresentationAttribute y, a continuación, convierte la duración en bytes.
  2. Lea el archivo de audio en como una secuencia llamando a IMFSourceReader::ReadSample. ReadSample lee el ejemplo siguiente del origen multimedia.
  3. Use IMFSample::ConvertToContiguousBuffer para copiar el contenido del búfer de muestra de audio (ejemplo) en una matriz (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;
}

Asociar sonido al objeto

La asociación de sonidos al objeto tiene lugar cuando el juego se inicializa, en el método Simple3DGame::Initialize .

Recauchutar:

  • En la clase GameObject , hay una propiedad HitSound que se usa para asociar el efecto de sonido al objeto.
  • Cree una nueva instancia del objeto de clase SoundEffect y asócielo al objeto de juego. Esta clase reproduce un sonido mediante las API XAudio2 . Usa una voz de maestro proporcionada por la clase Audio . Los datos de sonido se pueden leer desde la ubicación del archivo mediante la clase MediaReader .

SoundEffect::Initialize se usa para initalizar la instancia de SoundEffect con los parámetros de entrada siguientes: puntero al objeto del motor de sonido (objetos IXAudio2 creados en el método Audio::CreateDeviceIndependentResources), puntero al formato del archivo .wav mediante MediaReader::GetOutputWaveFormatEx y los datos de sonido cargados mediante el método MediaReader::LoadMedia. Durante la inicialización, también se crea la voz de origen para el efecto de sonido.

Método SoundEffect::Initialize

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

Reproducir el sonido

Los desencadenadores para reproducir efectos de sonido se definen en el método Simple3DGame::UpdateDynamics porque aquí es donde se actualiza el movimiento de los objetos y se determina la colisión entre objetos.

Dado que la interacción entre objetos difiere en gran medida, dependiendo del juego, no vamos a discutir la dinámica de los objetos del juego aquí. Si está interesado en comprender su implementación, vaya al método Simple3DGame::UpdateDynamics .

En principio, cuando se produce una colisión, desencadena el efecto de sonido para reproducir llamando a SoundEffect::P laySound. Este método detiene los efectos de sonido que se están reproduciendo actualmente y pone en cola el búfer en memoria con los datos de sonido deseados. Usa la voz de origen para establecer el volumen, enviar datos de sonido e iniciar la reproducción.

Método SoundEffect::P laySound

  • Usa el objeto de voz de origen m_sourceVoice para iniciar la reproducción del búfer de datos de sonido m_soundData
  • Crea un XAUDIO2_BUFFER, al que proporciona una referencia al búfer de datos de sonido y, a continuación, lo envía con una llamada a IXAudio2SourceVoice::SubmitSourceBuffer.
  • Con los datos de sonido en cola, SoundEffect::P laySound comienza la reproducción llamando a IXAudio2SourceVoice::Start.
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()
        );
}

Método Simple3DGame::UpdateDynamics

El método Simple3DGame::UpdateDynamics se encarga de la interacción y colisión entre objetos de juego. Cuando los objetos colisionan (o intersectan), desencadena el efecto de sonido asociado que se reproducirá.

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
}

Pasos siguientes

Hemos tratado el marco de UWP, gráficos, controles, interfaz de usuario y audio de un juego de Windows 10. En la siguiente parte de este tutorial, Extender el juego de ejemplo, se explican otras opciones que se pueden usar al desarrollar un juego.

Conceptos de audio

Para el desarrollo de juegos de Windows 10, usa XAudio2 versión 2.9. Esta versión se incluye con Windows 10. Para obtener más información, vaya a XAudio2 Versions( Versiones de XAudio2).

AudioX2 es una API de bajo nivel que proporciona base de procesamiento y mezcla de señales. Para obtener más información, consulta Conceptos clave de XAudio2.

Voces de XAudio2

Hay tres tipos de objetos de voz XAudio2: voces de origen, submezcla y maestro. Las voces son los objetos que XAudio2 usan para procesar, manipular y reproducir datos de audio.

  • Las voces de origen funcionan en los datos de audio proporcionados por el cliente.
  • Las voces de origen y submezcla envían su salida a una o varias voces de submezcla o patrón.
  • Las voces de submezcla y maestro mezclan el audio de todas las voces que los alimentan y operan en el resultado.
  • Las voces de maestro reciben datos de voces de origen y voces de submezcla y envían esos datos al hardware de audio.

Para obtener más información, vaya a XAudio2 voces.

Gráfico de audio

El gráfico de audio es una colección de voces XAudio2. El audio se inicia en un lado de un gráfico de audio en voces de origen, pasa opcionalmente a través de una o varias voces de submezcla y termina en una voz de maestro. Un gráfico de audio contendrá una voz de origen para cada sonido que se reproduce actualmente, cero o más voces de submezcla y una voz de masterización. El gráfico de audio más sencillo, y el mínimo necesario para hacer un ruido en XAudio2, es una única salida de voz de origen directamente a una voz de maestro. Para obtener más información, vaya a Gráficos de audio.

Otras lecturas

Archivos .h de audio clave

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