Marble Maze 샘플에 오디오 추가
이 문서에서는 오디오 작업 시 고려해야 할 주요 사례를 설명하고 Marble Maze가 이러한 사례를 적용하는 방법을 보여 줍니다. Marble Maze는 Microsoft 미디어 파운데이션을 사용하여 파일에서 오디오 리소스를 로드하고 XAudio2를 사용하여 오디오를 믹싱 및 재생하고 오디오에 효과를 적용합니다.
Marble Maze는 백그라운드에서 음악을 재생할 뿐 아니라 게임 플레이 소리를 사용하여 구슬이 벽에 부딪치는 경우와 같은 게임 이벤트를 나타냅니다. 구현의 중요한 부분은 Marble Maze가 반향 또는 에코 효과를 사용하여 구슬이 튀어나갈 때의 소리를 시뮬레이션한다는 것입니다. 반향 효과를 구현하면 작은 방에서는 에코가 더 빠르고 큰 소리로 도달하고, 큰 방에서는 에코가 더 조용하며 더 느리게 도달합니다.
참고 항목
이 문서에 해당하는 샘플 코드는 DirectX Marble Maze 게임 샘플에 있습니다.
이 문서에서 게임의 오디오 작업을 하는 경우에 대해 논의하는 주요 사항은 다음과 같습니다.
미디어 파운데이션을 사용하여 오디오 자산을 디코딩하고 XAudio2를 사용하여 오디오를 재생하는 것이 좋습니다. 그러나 UWP(유니버설 Windows 플랫폼) 앱에서 작동하는 기존 오디오 자산 로드 메커니즘이 있는 경우 이를 사용할 수 있습니다.
오디오 그래프에는 각 활성 사운드에 대한 하나의 원본 음성, 0개 이상의 서브믹스 음성 및 하나의 마스터링 음성이 포함됩니다. 원본 음성은 서브믹스 음성 및/또는 마스터 음성에 제공될 수 있습니다. 서브믹스 음성은 다른 서브믹스 음성 또는 마스터링 음성에 제공됩니다.
배경 음악 파일이 큰 경우 메모리가 적게 사용되도록 음악을 더 작은 버퍼로 스트리밍하는 것이 좋습니다.
앱이 포커스나 가시성을 잃거나 일시 중단될 때 오디오 재생을 일시 중지하는 것이 합리적이면 그렇게 합니다. 앱이 포커스를 되찾거나, 표시되거나, 재개되면 재생을 다시 시작합니다.
각 소리의 역할을 반영하도록 오디오 범주를 설정합니다. 예를 들어 일반적으로 게임 배경 오디오에는 AudioCategory_GameMedia를 사용하고 소리 효과에는 AudioCategory_GameEffects를 사용합니다.
모든 오디오 리소스와 인터페이스를 해제하고 다시 만들어 헤드폰을 비롯한 디바이스 변경을 처리합니다.
디스크 공간 및 스트리밍 비용을 최소화하는 것이 요구 사항인 경우 오디오 파일을 압축할지 여부를 고려합니다. 그렇지 않으면 오디오가 더 빠르게 로드되도록 오디오를 압축 해제 상태로 둘 수 있습니다.
XAudio2 및 Microsoft 미디어 파운데이션 소개
XAudio2는 특히 게임 오디오를 지원하는 Windows용 하위 수준 오디오 라이브러리입니다. 게임용 DSP(디지털 신호 처리) 및 오디오 그래프 엔진을 제공합니다. XAudio2는 SIMD 부동 소수점 아키텍처 및 HD 오디오와 같은 컴퓨팅 추세를 지원하여 이전 모델인 DirectSound 및 XAudio를 확장합니다. 또한 오늘날 게임의 더 복잡한 사운드 처리 요구 사항을 지원합니다.
XAudio2 주요 개념 문서에서는 XAudio2 사용에 대한 주요 개념을 설명합니다. 이 개념은 요약하면 다음과 같습니다.
IXAudio2 인터페이스는 XAudio2 엔진의 핵심입니다. Marble Maze는 이 인터페이스를 사용하여 음성을 만들고 출력 디바이스가 변경되거나 실패할 때 알림을 받습니다.
음성은 오디오 데이터를 처리, 조정, 재생합니다.
원본 음성은 오디오 채널 모음(모노, 5.1 등)이며 하나의 오디오 데이터 스트림을 나타냅니다. XAudio2에서 원본 음성은 오디오 처리가 시작되는 곳입니다. 일반적으로 소리 데이터는 파일 또는 네트워크와 같은 외부 원본에서 로드되고 원본 음성으로 전송됩니다. Marble Maze는 미디어 파운데이션을 사용하여 파일에서 사운드 데이터를 로드합니다. 미디어 파운데이션은 이 문서의 뒷부분에서 소개합니다.
서브믹스 음성은 오디오 데이터를 처리합니다. 이 처리에는 오디오 스트림을 변경하거나 여러 스트림을 하나로 결합하는 것이 포함될 수 있습니다. Marble Maze는 서브믹스를 사용하여 반향 효과를 만듭니다.
마스터 음성은 원본 및 서브믹스 음성의 데이터를 결합하고 해당 데이터를 오디오 하드웨어에 보냅니다.
오디오 그래프에는 각 활성 소리의 원본 음성 1개, 서브믹스 음성 0개 이상, 마스터 음성 1개만 포함됩니다.
콜백은 음성 또는 엔진 개체에서 일부 이벤트가 발생했다고 클라이언트 코드에 알립니다. 콜백을 사용하면 XAudio2가 버퍼를 완료할 때 메모리를 다시 사용하고, 오디오 디바이스가 변경되면(예: 헤드폰을 연결하거나 연결을 끊을 때) 반응할 수 있습니다. 이 문서의 뒷부분에 있는 헤드폰 및 디바이스 변경 처리에서는 Marble Maze가 이 메커니즘을 사용하여 디바이스 변경을 처리하는 방법을 설명합니다.
Marble Maze는 두 개의 오디오 엔진(즉, 두 개의 IXAudio2 개체)을 사용하여 오디오를 처리합니다. 한 엔진은 배경 음악을 처리하고, 다른 엔진은 게임 플레이 소리를 처리합니다.
또한 Marble Maze는 각 엔진에 대해 하나의 마스터링 음성을 만들어야 합니다. 마스터링 엔진은 오디오 스트림을 하나의 스트림으로 결합하고 해당 스트림을 오디오 하드웨어로 보냅니다. 원본 음성인 배경 음악 스트림은 마스터링 음성과 두 개의 서브믹스 음성으로 데이터를 출력합니다. 서브믹스 음성은 반향 효과를 수행합니다.
미디어 파운데이션은 다양한 오디오 및 비디오 형식을 지원하는 멀티미디어 라이브러리입니다. XAudio2와 미디어 파운데이션은 상호 보완적입니다. Marble Maze는 미디어 파운데이션을 사용하여 파일에서 오디오 자산을 로드하고 XAudio2를 사용하여 오디오를 재생합니다. 미디어 파운데이션을 사용하여 오디오 자산을 로드할 필요가 없습니다. UWP(유니버설 Windows 플랫폼) 앱에서 작동하는 기존 오디오 자산 로드 메커니즘이 있는 경우 이를 사용합니다. 오디오, 동영상, 카메라에서는 UWP 앱의 오디오를 구현하는 여러 가지 방법을 설명합니다.
XAudio2에 대한 자세한 내용은 프로그래밍 가이드를 참조하세요. 미디어 파운데이션에 대한 자세한 내용은 Microsoft 미디어 파운데이션을 참조하세요.
오디오 리소스 초기화
Marble Maze는 배경 음악에 Windows Media 오디오(.wma) 파일을 사용하고, 게임 플레이 소리에 WAV(.wav) 파일을 사용합니다. 이러한 형식은 미디어 파운데이션에서 지원됩니다. .wav 파일 형식은 XAudio2에서 기본적으로 지원되지만 게임에서 파일 형식을 수동으로 구문 분석하여 적절한 XAudio2 데이터 구조를 작성해야 합니다. Marble Maze는 미디어 파운데이션을 사용하여 .wav 파일로 더 쉽게 작업합니다. 미디어 파운데이션에서 지원하는 미디어 형식의 전체 목록은 미디어 파운데이션에서 지원되는 미디어 형식을 참조하세요. Marble Maze는 별도의 디자인 타임 및 런타임 오디오 형식을 사용하지 않으며 XAudio2 ADPCM 압축 지원을 사용하지 않습니다. XAudio2의 ADPCM 압축에 대한 자세한 내용은 ADPCM 개요를 참조하세요.
MarbleMazeMain::LoadDeferredResources에서 호출되는 Audio::CreateResources 메서드는 파일에서 오디오 스트림을 로드하고, XAudio2 엔진 개체를 초기화하고, 원본, 서브믹스, 마스터 음성을 만듭니다.
XAudio2 엔진 만들기
Marble Maze는 사용하는 각 오디오 엔진을 나타내는 하나의 IXAudio2 개체를 만든다는 것을 기억하십시오. 오디오 엔진을 만들려면 XAudio2Create 메서드를 호출합니다. 다음 예제에서는 Marble Maze가 배경 음악을 처리하는 오디오 엔진을 만드는 방법을 보여줍니다.
// In Audio.h
class Audio
{
private:
IXAudio2* m_musicEngine;
// ...
}
// In Audio.cpp
void Audio::CreateResources()
{
try
{
// ...
DX::ThrowIfFailed(
XAudio2Create(&m_musicEngine)
);
// ...
}
// ...
}
Marble Maze는 유사한 단계를 수행하여 게임 플레이 소리를 재생하는 오디오 엔진을 만듭니다.
UWP 앱에서 IXAudio2 인터페이스를 사용하는 방법은 데스크톱 앱과 두 가지 면에서 다릅니다. 첫째, XAudio2Create를 호출하기 전에 CoInitializeEx를 호출할 필요가 없습니다. 또한 IXAudio2는 더 이상 디바이스 열거를 지원하지 않습니다. 오디오 디바이스를 열거하는 방법에 대한 자세한 내용은 디바이스 열거를 참조하세요.
마스터링 음성 만들기
다음 예제에서는 Audio::CreateResources 메서드가 IXAudio2::CreateMasteringVoice 메서드를 사용하여 배경 음악에 대한 마스터 음성을 만드는 방법을 보여 줍니다. 이 예제에서 m_musicMasteringVoice는 IXAudio2MasteringVoice 개체입니다. Marble Maze에서는 입력 채널 2개를 지정하여 반향 효과에 대한 논리를 간소화합니다.
입력 샘플 속도는 48000으로 지정되어 있습니다. 이 샘플 속도를 선택한 이유는 오디오 품질과 필요한 CPU 처리량 사이의 균형을 맞추기 위해서입니다. 샘플 속도가 더 빠르면 눈에 띄는 품질 이점 없이 더 많은 CPU 처리가 필요했을 것입니다.
마지막으로 Marble Maze에서는 사용자가 게임을 플레이할 때 다른 애플리케이션에서 음악을 들을 수 있도록 AudioCategory_GameMedia를 오디오 스트림 범주로 지정합니다. 음악 앱이 재생 중이면 Windows는 AudioCategory_GameMedia 옵션으로 생성된 음성을 모두 음소거합니다. 게임 플레이 소리는 AudioCategory_GameEffects 옵션으로 생성되기 때문에 사용자가 들을 수 있습니다. 오디오 범주에 대한 자세한 내용은 AUDIO_STREAM_CATEGORY 를 참조하세요.
// 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
)
);
Audio::CreateResources 메서드는 StreamCategory 매개 변수에 대해 기본값인 AudioCategory_GameEffects를 지정한다는 점을 제외하고, 유사한 단계를 수행하여 게임 플레이 소리에 대한 마스터 음성을 만듭니다.
반향 효과 만들기
각 음성에 대해 XAudio2를 사용하여 오디오를 처리하는 효과 시퀀스를 만들 수 있습니다. 이러한 시퀀스를 효과 체인이라고 합니다. 음성에 하나 이상의 효과를 적용하려는 경우 효과 체인을 사용합니다. 효과 체인은 파괴적일 수 있습니다. 즉, 체인의 각 효과는 오디오 버퍼를 덮어쓸 수 있습니다. XAudio2는 출력 버퍼가 무음으로 초기화된다는 보장이 없기 때문에 이 속성이 중요합니다. 효과 개체는 XAPO(플랫폼 간 오디오 처리 개체)로 XAudio2에 표시됩니다. XAPO에 대한 자세한 내용은 XAPO 개요를 참조하세요.
효과 체인을 만들 때 다음 단계를 수행합니다.
효과 개체를 만듭니다.
XAUDIO2_EFFECT_DESCRIPTOR 구조체를 효과 데이터로 채웁니다.
XAUDIO2_EFFECT_CHAIN 구조체를 데이터로 채웁니다.
효과 체인을 음성에 적용합니다.
효과 매개 변수 구조를 채우고 효과에 적용합니다.
적절한 경우 효과를 사용하지 않도록 설정하거나 사용하도록 설정합니다.
Audio 클래스는 반향을 구현하는 효과 체인을 만드는 CreateReverb 메서드를 정의합니다. 이 메서드는 XAudio2CreateReverb 메서드를 호출하여 반향 효과를 위한 서브믹스 음성 역할을 하는 ComPtr<IUnknown> 개체인 soundEffectXAPO를 만듭니다.
Microsoft::WRL::ComPtr<IUnknown> soundEffectXAPO;
DX::ThrowIfFailed(
XAudio2CreateReverb(&soundEffectXAPO)
);
XAUDIO2_EFFECT_DESCRIPTOR 구조체에는 효과 체인에 사용할 XAPO 정보(예: 출력 채널의 대상 번호)가 포함됩니다. Audio::CreateReverb 메서드는 XAUDIO2_EFFECT_DESCRIPTOR 개체인 soundEffectdescriptor를 만듭니다. 이 개체는 사용 안 함 상태로 설정되고, 출력 채널 2개를 사용하며, 반향 효과를 위해 soundEffectXAPO를 참조합니다. 효과가 게임 소리 수정을 시작하려면 게임에서 매개 변수를 설정해야 하므로 soundEffectdescriptor가 사용 안 함 상태로 시작됩니다. Marble Maze는 두 개의 출력 채널을 사용하여 반향 효과에 대한 논리를 간소화합니다.
soundEffectdescriptor.InitialState = false;
soundEffectdescriptor.OutputChannels = 2;
soundEffectdescriptor.pEffect = soundEffectXAPO.Get();
효과 체인에 여러 효과가 있는 경우 각 효과에는 개체가 필요합니다. XAUDIO2_EFFECT_CHAIN 구조체는 효과에 참여하는 XAUDIO2_EFFECT_DESCRIPTOR 개체의 배열을 저장합니다. 다음 예제에서는 Audio::CreateReverb 메서드가 반향을 구현하는 하나의 효과를 지정하는 방법을 보여줍니다.
XAUDIO2_EFFECT_CHAIN soundEffectChain;
// ...
soundEffectChain.EffectCount = 1;
soundEffectChain.pEffectDescriptors = &soundEffectdescriptor;
Audio::CreateReverb 메서드는 IXAudio2::CreateSubmixVoice 메서드를 호출하여 효과의 서브믹스 음성을 만듭니다. 효과 체인을 음성에 연결하기 위해 pEffectChain 매개 변수에 대한 XAUDIO2_EFFECT_CHAIN 개체인 soundEffectChain을 지정합니다. 또한 Marble Maze는 2개의 출력 채널과 48킬로헤르츠의 샘플 속도를 지정합니다.
DX::ThrowIfFailed(
engine->CreateSubmixVoice(newSubmix, 2, 48000, 0, 0, nullptr, &soundEffectChain)
);
팁
기존 효과 체인을 기존 서브믹스 음성에 연결하거나 현재 효과 체인을 바꾸려는 경우 IXAudio2Voice::SetEffectChain 메서드를 사용합니다.
Audio::CreateReverb 메서드는 IXAudio2Voice::SetEffectParameters를 호출하여 효과와 관련된 추가 매개 변수를 설정합니다. 이 메서드는 효과와 관련된 매개 변수 구조를 사용합니다. 모든 반향 효과가 동일한 매개 변수를 공유하기 때문에 반향에 대한 효과 매개 변수가 포함된 XAUDIO2FX_REVERB_PARAMETERS 개체인 m_reverbParametersSmall이 Audio::Initialize 메서드에서 초기화됩니다. 다음 예제에서는 Audio::Initialize 메서드가 근거리 반향에 대한 반향 매개 변수를 초기화하는 방법을 보여 줍니다.
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;
이 예제에서는 대부분의 반향 매개 변수에 대한 기본값을 사용하지만 DisableLateField를 TRUE로 설정하여 근거리 반향을 지정하고, EarlyDiffusion을 4로 지정하여 표면 근처의 평면을 시뮬레이션하고, LateDiffusion을 15로 설정하여 매우 확산된 먼 표면을 시뮬레이션합니다. 평평한 가까운 표면은 에코를 더 빠르고 큰 소리로 도달하게 하며, 확산된 먼 표면은 에코가 더 조용하고 더 느리게 도달하도록 합니다. 게임에서 원하는 효과를 얻기 위해 반향 값을 실험하거나, ReverbConvertI3DL2ToNative 메서드를 통해 산업 표준 I3DL2(Interactive 3D Audio Rendering Guidelines Level 2.0) 매개 변수를 사용할 수 있습니다.
다음 예제에서는 Audio::CreateReverb에서 반향 매개 변수를 설정하는 방법을 보여줍니다. newSubmix는 IXAudio2SubmixVoice** 개체입니다. 매개 변수는 XAUDIO2FX_REVERB_PARAMETERS* 개체입니다.
DX::ThrowIfFailed(
(*newSubmix)->SetEffectParameters(0, parameters, sizeof(m_reverbParametersSmall))
);
Audio::CreateReverb 메서드는 enableEffect 플래그가 설정된 경우 IXAudio2Voice::EnableEffect를 사용하도록 설정하여 완료합니다. 또한 IXAudio2Voice::SetVolume을 사용하여 볼륨을 설정하고 IXAudio2Voice::SetOutputMatrix를 사용하여 출력 매트릭스를 설정합니다. 이 파트는 볼륨을 전체(1.0)로 설정한 다음, 왼쪽 및 오른쪽 입력과 왼쪽 및 오른쪽 출력 스피커 모두에 대해 무음으로 볼륨 행렬을 지정합니다. 나중에 다른 코드가 두 반향 간에 교차 페이드(벽 근처에서 큰 방으로 전환 시뮬레이션)하거나 필요한 경우 두 반향을 모두 음소거하기 때문에 이렇게 합니다. 나중에 반향 경로가 음소거 해제되면 게임에서 {1.0f, 0.0f, 0.0f, 1.0f} 행렬을 설정하여 왼쪽 반향 출력을 마스터링 음성의 왼쪽 입력으로 라우팅하고 오른쪽 반향 출력을 마스터링 음성의 오른쪽 입력으로 라우팅합니다.
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는 배경 음악에 대해 2번, 게임 플레이 소리에 대해 2번, 총 4번 Audio::CreateReverb 메서드를 호출합니다. 다음은 Marble Maze가 배경 음악에 대해 CreateReverb 메서드를 호출하는 방법을 보여줍니다.
CreateReverb(
m_musicEngine,
m_musicMasteringVoice,
&m_reverbParametersSmall,
&m_musicReverbVoiceSmallRoom,
true
);
CreateReverb(
m_musicEngine,
m_musicMasteringVoice,
&m_reverbParametersLarge,
&m_musicReverbVoiceLargeRoom,
true
);
XAudio2와 함께 사용할 수 있는 효과 원본 목록은 XAudio2 오디오 효과를 참조하세요.
파일에서 오디오 데이터 로드
Marble Maze에서는 미디어 파운데이션을 사용하여 파일에서 오디오 리소스를 로드하는 MediaStreamer 클래스를 정의합니다. Marble Maze는 하나의 MediaStreamer 개체를 사용하여 각 오디오 파일을 로드합니다.
Marble Maze는 MediaStreamer::Initialize 메서드를 호출하여 각 오디오 스트림을 초기화합니다. Audio::CreateResources 메서드가 MediaStreamer::Initialize를 호출하여 배경 음악의 오디오 스트림을 초기화하는 방법은 다음과 같습니다.
// 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");
MediaStreamer::Initialize 메서드는 먼저 MFStartup 메서드를 호출하여 미디어 파운데이션을 초기화합니다. MF_VERSION은 mfapi.h에 정의된 매크로이며 사용할 미디어 파운데이션 버전으로 지정됩니다.
DX::ThrowIfFailed(
MFStartup(MF_VERSION)
);
그런 다음 MediaStreamer::Initialize는 MFCreateSourceReaderFromURL을 호출하여 IMFSourceReader 개체를 만듭니다. IMFSourceReader 개체인 m_reader는 url에 지정된 파일에서 미디어 데이터를 읽습니다.
DX::ThrowIfFailed(
MFCreateSourceReaderFromURL(url, nullptr, &m_reader)
);
그런 다음 MediaStreamer::Initialize 메서드는 MFCreateMediaType을 사용하여 IMFMediaType 개체를 만들어 오디오 스트림의 형식을 설명합니다. 오디오 형식에는 주 형식과 하위 형식의 두 가지 유형이 있습니다. 주 형식은 비디오, 오디오, 스크립트 등과 같은 미디어의 전체 형식을 정의합니다. 하위 형식은 PCM, ADPCM 또는 WMA와 같은 형식을 정의합니다.
MediaStreamer::Initialize 메서드는 IMFAttributes::SetGUID 메서드를 사용하여 주 형식(MF_MT_MAJOR_TYPE)을 오디오(MFMediaType_Audio)로 지정하고, 보조 형식(MF_MT_SUBTYPE)을 압축되지 않은 PCM 오디오(MFAudioFormat_PCM)로 지정합니다. MF_MT_MAJOR_TYPE 및 MF_MT_SUBTYPE은 미디어 파운데이션 특성입니다. MFMediaType_Audio 및 MFAudioFormat_PCM은 형식 및 하위 형식 GUID입니다. 자세한 내용은 오디오 미디어 형식을 참조하세요. IMFSourceReader::SetCurrentMediaType 메서드는 미디어 형식을 스트림 판독기와 연결합니다.
// 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())
);
그런 다음 MediaStreamer::Initialize 메서드는 IMFSourceReader::GetCurrentMediaType을 사용하여 미디어 파운데이션에서 전체 출력 미디어 형식을 가져오고 MFCreateWaveFormatExFromMFMediaType 메서드를 호출하여 미디어 파운데이션 오디오 미디어 형식을 WAVEFORMATEX 구조체로 변환합니다. WAVEFORMATEX 구조체는 파형 오디오 데이터의 형식을 정의합니다. Marble Maze는 이 구조를 사용하여 원본 음성을 만들고 구슬 구르는 소리에 저역 필터를 적용합니다.
// 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);
Important
MFCreateWaveFormatExFromMFMediaType 메서드는 CoTaskMemAlloc을 사용하여 WAVEFORMATEX 개체를 할당합니다. 따라서 이 개체 사용을 마쳤을 때 CoTaskMemFree를 호출해야 합니다.
MediaStreamer::Initialize 메서드는 바이트 단위의 스트림 길이인 m_maxStreamLengthInBytes를 컴퓨팅하여 완료됩니다. 이 작업을 위해 IMFSourceReader::GetPresentationAttribute 메서드를 호출하여 오디오 스트림 기간을 100 나노초 단위로 가져오고, 기간을 구역으로 변환한 다음, 초당 바이트 수인 평균 데이터 전송 속도를 곱합니다. Marble Maze에서는 나중에 이 값을 사용하여 각 게임 플레이 소리가 저장된 버퍼를 할당합니다.
// 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
);
원본 음성 만들기
Marble Maze는 XAudio2 원본 음성을 만들어 원본 음성에서 각 게임 사운드와 음악을 재생합니다. Audio 클래스는 배경 음악에 사용할 IXAudio2SourceVoice 개체와 게임 플레이 소리를 저장할 SoundEffectData 개체 배열을 정의합니다. SoundEffectData 구조체는 효과에 대한 IXAudio2SourceVoice 개체를 보유하며 오디오 버퍼와 같은 다른 효과 관련 데이터도 정의합니다. Audio.h는 SoundEvent 열거형을 정의합니다. Marble Maze에서는 이 열거형을 사용하여 각 게임 플레이 소리를 식별합니다. 또한 Audio 클래스는 이 열거형을 사용하여 SoundEffectData 개체의 배열을 인덱싱합니다.
enum SoundEvent
{
RollingEvent = 0,
FallingEvent = 1,
CollisionEvent = 2,
CheckpointEvent = 3,
MenuChangeEvent = 4,
MenuSelectedEvent = 5,
LastSoundEvent,
};
다음 표에서는 이러한 각 값 간의 관계, 연결된 사운드 데이터가 포함된 파일 및 각 사운드가 나타내는 내용에 대한 간략한 설명을 보여 줍니다. 오디오 파일은 \Media\Audio 폴더에 있습니다.
SoundEvent 값 | File name | 설명 |
---|---|---|
RollingEvent | MarbleRoll.wav | 구슬이 구를 때 재생됩니다. |
FallingEvent | MarbleFall.wav | 구슬이 미로에서 떨어질 때 재생됩니다. |
CollisionEvent | MarbleHit.wav | 구슬이 미로와 충돌할 때 재생됩니다. |
CheckpointEvent | Checkpoint.wav | 구슬이 체크포인트를 통과할 때 재생됩니다. |
MenuChangeEvent | MenuChange.wav | 사용자가 현재 메뉴 항목을 변경할 때 재생됩니다. |
MenuSelectedEvent | MenuSelect.wav | 사용자가 메뉴 항목을 선택할 때 재생됩니다. |
다음 예제에서는 Audio::CreateResources 메서드가 배경 음악에 대한 원본 음성을 만드는 방법을 보여 줍니다. XAUDIO2_SEND_DESCRIPTOR 구조체는 다른 음성에서 대상 음성을 정의하고 필터를 사용할지 여부를 지정합니다. Marble Maze에서는 Audio::SetSoundEffectFilter 메서드를 호출하여 필터를 통해 구슬이 구를 때 나는 소리를 변경합니다. XAUDIO2_VOICE_SENDS 구조체는 단일 출력 음성에서 데이터를 수신할 음성 집합을 정의합니다. Marble Maze는 원본 음성의 데이터를 마스터링 음성(재생 소리의 건식 또는 변경할 수 없는 부분) 및 재생 소리의 습식 또는 반향을 구현하는 두 개의 서브믹스 음성으로 보냅니다.
IXAudio2::CreateSourceVoice 메서드는 원본 음성을 만들고 구성합니다. 음성으로 전송되는 오디오 버퍼의 형식을 정의하는 WAVEFORMATEX 구조를 사용합니다. 앞에서 설명한 대로 Marble Maze는 PCM 형식을 사용합니다.
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)
);
배경 음악 재생
원본 음성이 중지된 상태로 만들어집니다. Marble Maze는 게임 루프에서 배경 음악을 시작합니다. MarbleMazeMain::Update를 처음 호출하면 Audio::Start가 호출되어 배경 음악을 시작합니다.
if (!m_audio.m_isAudioStarted)
{
m_audio.Start();
}
Audio::Start 메서드는 IXAudio2SourceVoice::Start를 호출하여 배경 음악에 대한 원본 음성 처리를 시작합니다.
void Audio::Start()
{
if (m_engineExperiencedCriticalError)
{
return;
}
HRESULT hr = m_musicSourceVoice->Start(0);
if SUCCEEDED(hr) {
m_isAudioStarted = true;
}
else
{
m_engineExperiencedCriticalError = true;
}
}
원본 음성은 해당 오디오 데이터를 오디오 그래프의 다음 단계로 전달합니다. Marble Maze의 경우 다음 단계는 오디오에 두 개의 반향 효과를 적용하는 두 개의 서브믹스 음성을 포함합니다. 하나의 서브믹스 음성은 가까운 후기 필드 반향을 적용하고, 두 번째는 훨씬 먼 후기 필드 반향을 적용합니다.
각 서브믹스 음성이 최종 믹스에 기여하는 양은 방의 크기와 모양에 따라 결정됩니다. 공이 벽 근처 또는 작은 방에 있을 때는 근거리 반향이 더 많은 기여를 하고, 공이 넓은 공간에 있을 때는 후기 필드 반향이 더 많은 기여를 합니다. 이 기술은 구슬이 미로를 통과할 때 보다 사실적인 에코 효과를 생성합니다. Marble Maze가 이 효과를 구현하는 방법에 대한 자세한 내용은 Marble Maze 소스 코드에서 Audio::SetRoomSize 및 Physics::CalculateCurrentRoomSize를 참조하세요.
참고 항목
대부분의 방 크기가 비교적 동일한 게임에서는 더 기본적인 반향 모델을 사용할 수 있습니다. 예를 들어 모든 회의실에 대해 하나의 반향 설정을 사용하거나 각 회의실에 대해 미리 정의된 반향 설정을 만들 수 있습니다.
Audio::CreateResources 메서드는 미디어 파운데이션을 사용하여 배경 음악을 로드합니다. 그러나 이 시점에서 원본 음성에는 작업할 오디오 데이터가 없습니다. 또한 배경 음악이 반복되기 때문에 음악이 계속 재생되도록 원본 음성을 데이터로 정기적으로 업데이트해야 합니다.
원본 음성을 데이터로 채우도록 게임 루프는 모든 프레임마다 오디오 버퍼를 업데이트합니다. MarbleMazeMain::Render 메서드는 Audio::Render를 호출하여 배경 음악 오디오 버퍼를 처리합니다. Audio 클래스는 3개의 오디오 버퍼로 구성된 배열인 m_audioBuffers를 정의합니다. 각 버퍼는 64KB(65536바이트)의 데이터를 보유합니다. 이 루프는 미디어 파운데이션 개체에서 데이터를 읽고 원본 음성에 큐에 대기 중인 버퍼가 3개가 될 때까지 해당 데이터를 원본 음성에 씁니다.
주의
Marble Maze에서는 64KB 버퍼를 사용하여 음악 데이터를 저장하지만, 더 크거나 작은 버퍼를 사용해야 할 수도 있습니다. 이 용량은 게임의 요구 사항에 따라 달라집니다.
// 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;
}
}
또한 루프는 미디어 파운데이션 개체가 스트림의 끝에 도달할 때도 처리합니다. 이 경우 IMFSourceReader::SetCurrentPosition 메서드를 호출하여 오디오 원본 위치를 다시 설정합니다.
void MediaStreamer::Restart()
{
if (m_reader == nullptr)
{
return;
}
PROPVARIANT var = {0};
var.vt = VT_I8;
DX::ThrowIfFailed(
m_reader->SetCurrentPosition(GUID_NULL, var)
);
}
단일 버퍼(또는 완전히 메모리에 로드된 전체 소리)에 대해 오디오 루프를 구현하기 위해 소리를 초기화할 때 XAUDIO2_BUFFER::LoopCount 필드를 XAUDIO2_LOOP_INFINITE로 설정할 수 있습니다. Marble Maze는 이 기술을 사용하여 구슬이 구르는 소리를 재생합니다.
if (sound == RollingEvent)
{
m_soundEffects[sound].m_audioBuffer.LoopCount = XAUDIO2_LOOP_INFINITE;
}
그러나 배경 음악의 경우 Marble Maze는 버퍼를 직접 관리하므로 사용되는 메모리 양을 더 잘 제어할 수 있습니다. 음악 파일이 크면 음악 데이터를 더 작은 버퍼로 스트리밍할 수 있습니다. 이렇게 하면 오디오 데이터를 처리하고 스트리밍하는 게임의 기능 빈도와 메모리 크기의 균형을 맞출 수 있습니다.
팁
게임의 프레임 속도가 부족하거나 변하는 경우 주 스레드에서 오디오를 처리하면 오디오 엔진에 작업할 버퍼링된 오디오 데이터가 부족하기 때문에 오디오가 예기치 않게 일시 중지되거나 팝업이 발생할 수 있습니다. 게임이 이 문제에 민감한 경우 렌더링을 수행하지 않는 별도의 스레드에서 오디오를 처리하는 것이 좋습니다. 이 방법은 게임이 유휴 프로세서를 사용할 수 있기 때문에 프로세서가 여러 개인 컴퓨터에서 특히 유용합니다.
게임 이벤트에 대응
Audio 클래스는 PlaySoundEffect, IsSoundEffectStarted, StopSoundEffect, SetSoundEffectVolume, SetSoundEffectPitch, SetSoundEffectFilter 등의 메서드를 제공하여 게임에서 소리가 재생 및 중지되는 시기를 제어하고 볼륨, 피치 등의 소리 속성을 제어할 수 있게 합니다. 예를 들어 구슬이 미로에서 떨어질 경우 MarbleMazeMain::Update는 Audio::PlaySoundEffect 메서드를 호출하여 FallingEvent 소리를 재생합니다.
m_audio.PlaySoundEffect(FallingEvent);
Audio::PlaySoundEffect 메서드는 IXAudio2SourceVoice::Start 메서드를 호출하여 소리 재생을 시작합니다. IXAudio2SourceVoice::Start 메서드가 이미 호출된 경우 다시 시작되지 않습니다. Audio::P laySoundEffect는 특정 사운드에 대한 사용자 지정 논리를 수행합니다.
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;
}
구르는 소리가 아닌 소리의 경우 Audio::PlaySoundEffect 메서드는 IXAudio2SourceVoice::GetState를 호출하여 원본 음성이 재생 중인 버퍼 수를 확인합니다. 활성 버퍼가 없는 경우 IXAudio2SourceVoice::SubmitSourceBuffer를 호출하여 소리에 대한 오디오 데이터를 음성의 입력 큐에 추가합니다. Audio::P laySoundEffect 메서드를 사용하면 충돌 소리를 순서대로 두 번 재생할 수도 있습니다. 예를 들어 구슬이 미로의 모퉁이와 충돌할 때 발생합니다.
이미 설명했듯이, Audio 클래스는 구르기 이벤트의 소리를 초기화할 때 XAUDIO2_LOOP_INFINITE 플래그를 사용합니다. 이 이벤트에 대해 Audio::P laySoundEffect를 처음 호출할 때 소리가 반복 재생되기 시작합니다. 구르는 소리에 대한 재생 논리를 간소화하기 위해 Marble Maze는 소리를 중지하는 대신 음소거합니다. 구슬이 속도를 변경하면 Marble Maze는 소리의 피치와 볼륨을 변경하여 보다 사실적인 효과를 줍니다. 다음 예제는 MarbleMazeMain::Update 메서드가 구슬 속도가 변경될 때 구슬의 피치와 볼륨을 업데이트하는 방법 및 구슬이 멈출 때 볼륨을 0으로 설정하여 소리를 음소거하는 방법을 보여 줍니다.
// 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);
}
일시 중단 및 다시 시작 이벤트에 대응
Marble Maze 애플리케이션 구조에서는 Marble Maze에서 일시 중단 및 다시 시작을 지원하는 방법을 설명합니다. 게임이 일시 중단되면 게임이 오디오를 일시 중지합니다. 게임이 다시 시작되면 게임이 중단된 오디오를 다시 시작합니다. 리소스가 필요하지 않다는 것을 알고 있는 경우 리소스를 사용하지 않는 모범 사례를 따르도록 합니다.
Audio::SuspendAudio 메서드는 게임이 일시 중단될 때 호출됩니다. 이 메서드는 IXAudio2::StopEngine 메서드를 호출하여 모든 오디오를 중지합니다. IXAudio2::StopEngine은 모든 오디오 출력을 즉시 중지하지만 오디오 그래프 및 효과 매개 변수(예: 구슬이 튀어나올 때 적용되는 반향 효과)를 유지합니다.
// 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;
}
Audio::ResumeAudio 메서드는 게임이 다시 시작될 때 호출됩니다. 이 메서드는 IXAudio2::StartEngine 메서드를 사용하여 오디오를 다시 시작합니다. IXAudio2::StopEngine 호출은 오디오 그래프와 해당 효과 매개 변수를 유지하므로 오디오 출력이 중단된 위치에서 다시 시작됩니다.
// 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;
}
}
헤드폰 및 디바이스 변경 처리
Marble Maze는 엔진 콜백을 사용하여 오디오 디바이스가 변경되는 경우 등의 XAudio2 엔진 오류를 처리합니다. 디바이스 변경의 원인은 게임 사용자가 헤드폰에 연결하거나 연결을 끊을 때입니다. 디바이스 변경 내용을 처리하는 엔진 콜백을 구현하는 것이 좋습니다. 그렇지 않으면 게임이 다시 시작될 때까지 사용자가 헤드폰을 연결하거나 제거할 때 게임이 소리 재생을 중지합니다.
Audio.h에서는 AudioEngineCallbacks 클래스를 정의합니다. 이 클래스는 IXAudio2EngineCallback 인터페이스를 구현합니다.
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);
};
IXAudio2EngineCallback 인터페이스를 사용하면 오디오 처리 이벤트가 발생하고 엔진에 심각한 오류가 발생할 때 코드를 알 수 있습니다. 콜백을 등록하기 위해 Marble Maze에서는 음악 엔진에 대한 IXAudio2 개체를 만든 후 Audio::CreateResources에서 IXAudio2::RegisterForCallbacks 메서드를 호출합니다.
m_musicEngineCallback.Initialize(this);
m_musicEngine->RegisterForCallbacks(&m_musicEngineCallback);
Marble Maze는 오디오 처리가 시작되거나 종료되는 경우 알림이 필요하지 않습니다. 따라서 IXAudio2EngineCallback::OnProcessingPassStart 및 IXAudio2EngineCallback::OnProcessingPassEnd 메서드를 구현하여 아무 작업도 수행하지 않습니다. IXAudio2EngineCallback::OnCriticalError 메서드의 경우 Marble Maze에서는 m_engineExperiencedCriticalError 플래그를 설정하는 SetEngineExperiencedCriticalError 메서드를 호출합니다.
// 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;
}
심각한 오류가 발생하면 오디오 처리가 중지되고 XAudio2에 대한 모든 추가 호출이 실패합니다. 이 상황에서 복구하려면 XAudio2 인스턴스를 해제하고 새 인스턴스를 만들어야 합니다. 매 프레임마다 게임 루프에서 호출되는 Audio::Render 메서드는 먼저 m_engineExperiencedCriticalError 플래그를 확인합니다. 이 플래그가 설정되면 플래그를 지우고, 현재 XAudio2 인스턴스를 해제하고, 리소스를 초기화한 다음, 배경 음악을 시작합니다.
if (m_engineExperiencedCriticalError)
{
m_engineExperiencedCriticalError = false;
ReleaseResources();
Initialize();
CreateResources();
Start();
if (m_engineExperiencedCriticalError)
{
return;
}
}
또한 Marble Maze에서는 사용 가능한 오디오 디바이스가 없을 경우 m_engineExperiencedCriticalError 플래그를 사용하여 XAudio2가 호출되지 않도록 합니다. 예를 들어 MarbleMazeMain::Update 메서드는 이 플래그가 설정된 경우 구르기 또는 충돌 이벤트에 대한 오디오를 처리하지 않습니다. 필요한 경우 앱은 매 프레임마다 오디오 엔진을 복구하려고 합니다. 그러나 컴퓨터에 오디오 디바이스가 없거나 헤드폰이 분리되었으며 사용 가능한 다른 오디오 디바이스가 없을 경우 m_engineExperiencedCriticalError 플래그가 항상 설정될 수도 있습니다.
주의
일반적으로 엔진 콜백 본문에서 차단 작업을 수행하지 마세요. 이렇게 하면 성능 문제가 발생할 수 있습니다. Marble Maze는 OnCriticalError 콜백에서 플래그를 설정하고 나중에 일반 오디오 처리 단계에서 오류를 처리합니다. XAudio2 콜백에 대한 자세한 내용은 XAudio2 콜백을 참조하세요.
결론
이것으로 Marble Maze 게임 샘플을 마치겠습니다! 상대적으로 간단한 게임이지만 여기에는 UWP DirectX 게임에 들어가는 중요한 요소들이 다수 포함되어 있기 때문에 자체적으로 게임을 만들 때 참조하기에 좋은 예제입니다.
지금까지 이 예제를 통해 소스 코드를 서투르게나마 고쳐보고 어떤 일이 벌어지는지 확인해봤습니다. 아니면 또 다른 UWP DirectX 게임 샘플인 DirectX로 간단한 UWP 게임 만들기를 확인하셔도 좋습니다.
DirectX를 더 사용해 볼 준비가 되었나요? 그렇다면 DirectX 프로그래밍에서 가이드를 확인하세요.
일반적으로 UWP에서의 게임 개발에 관심이 있다면 게임 프로그래밍에서 설명서를 참조하세요.