2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file xaudio2_s.cpp XAudio2 sound driver. */
10 #include "../stdafx.h"
11 #include "../openttd.h"
12 #include "../driver.h"
15 #include "../core/alloc_func.hpp"
16 #include "../core/bitmath_func.hpp"
17 #include "../core/math_func.hpp"
19 // Windows 8 SDK required for XAudio2
23 #define NTDDI_VERSION NTDDI_WIN8
24 #define _WIN32_WINNT _WIN32_WINNT_WIN8
26 #include "xaudio2_s.h"
30 #include <wrl\client.h>
33 using Microsoft::WRL::ComPtr
;
35 #include "../os/windows/win32.h"
36 #include "../safeguards.h"
38 // Definition of the "XAudio2Create" call used to initialise XAudio2
39 typedef HRESULT(__stdcall
*API_XAudio2Create
)(_Outptr_ IXAudio2
**ppXAudio2
, UINT32 Flags
, XAUDIO2_PROCESSOR XAudio2Processor
);
41 static FSoundDriver_XAudio2 iFSoundDriver_XAudio2
;
44 * Implementation of the IXAudio2VoiceCallback interface.
45 * Provides buffered audio to XAudio2 from the OpenTTD mixer.
47 class StreamingVoiceContext
: public IXAudio2VoiceCallback
54 IXAudio2SourceVoice
*SourceVoice
;
56 StreamingVoiceContext(int bufferLength
)
58 this->bufferLength
= bufferLength
;
59 this->buffer
= MallocT
<char>(bufferLength
);
62 virtual ~StreamingVoiceContext()
67 HRESULT
SubmitBuffer()
69 // Ensure we do have a valid voice
70 if (this->SourceVoice
== nullptr)
75 MxMixSamples(this->buffer
, this->bufferLength
/ 4);
77 XAUDIO2_BUFFER buf
= { 0 };
78 buf
.AudioBytes
= this->bufferLength
;
79 buf
.pAudioData
= (const BYTE
*) this->buffer
;
81 return SourceVoice
->SubmitSourceBuffer(&buf
);
84 STDMETHOD_(void, OnVoiceProcessingPassStart
)(UINT32
) override
88 STDMETHOD_(void, OnVoiceProcessingPassEnd
)() override
92 STDMETHOD_(void, OnStreamEnd
)() override
96 STDMETHOD_(void, OnBufferStart
)(void*) override
100 STDMETHOD_(void, OnBufferEnd
)(void*) override
105 STDMETHOD_(void, OnLoopEnd
)(void*) override
109 STDMETHOD_(void, OnVoiceError
)(void*, HRESULT
) override
114 static HMODULE _xaudio_dll_handle
;
115 static IXAudio2SourceVoice
*_source_voice
= nullptr;
116 static IXAudio2MasteringVoice
*_mastering_voice
= nullptr;
117 static ComPtr
<IXAudio2
> _xaudio2
;
118 static StreamingVoiceContext
*_voice_context
= nullptr;
120 /** Create XAudio2 context with SEH exception checking. */
121 static HRESULT
CreateXAudio(API_XAudio2Create xAudio2Create
)
126 hr
= xAudio2Create(_xaudio2
.GetAddressOf(), flags
, XAUDIO2_DEFAULT_PROCESSOR
);
127 } __except (EXCEPTION_EXECUTE_HANDLER
) {
128 hr
= GetExceptionCode();
135 * Initialises the XAudio2 driver.
137 * @param parm Driver parameters.
138 * @return An error message if unsuccessful, or std::nullopt otherwise.
141 std::optional
<std::string_view
> SoundDriver_XAudio2::Start(const StringList
&parm
)
143 HRESULT hr
= CoInitializeEx(nullptr, COINIT_MULTITHREADED
);
147 Debug(driver
, 0, "xaudio2_s: CoInitializeEx failed ({:08x})", (uint
)hr
);
148 return "Failed to initialise COM";
151 _xaudio_dll_handle
= LoadLibraryA(XAUDIO2_DLL_A
);
153 if (_xaudio_dll_handle
== nullptr)
157 Debug(driver
, 0, "xaudio2_s: Unable to load " XAUDIO2_DLL_A
);
158 return "Failed to load XAudio2 DLL";
161 API_XAudio2Create xAudio2Create
= (API_XAudio2Create
) GetProcAddress(_xaudio_dll_handle
, "XAudio2Create");
163 if (xAudio2Create
== nullptr)
165 FreeLibrary(_xaudio_dll_handle
);
168 Debug(driver
, 0, "xaudio2_s: Unable to find XAudio2Create function in DLL");
169 return "Failed to load XAudio2 DLL";
172 // Create the XAudio engine
173 hr
= CreateXAudio(xAudio2Create
);
177 FreeLibrary(_xaudio_dll_handle
);
180 Debug(driver
, 0, "xaudio2_s: XAudio2Create failed ({:08x})", (uint
)hr
);
181 return "Failed to inititialise the XAudio2 engine";
184 // Create a mastering voice
185 hr
= _xaudio2
->CreateMasteringVoice(&_mastering_voice
);
190 FreeLibrary(_xaudio_dll_handle
);
193 Debug(driver
, 0, "xaudio2_s: CreateMasteringVoice failed ({:08x})", (uint
)hr
);
194 return "Failed to create a mastering voice";
197 // Create a source voice to stream our audio
200 wfex
.wFormatTag
= WAVE_FORMAT_PCM
;
202 wfex
.wBitsPerSample
= 16;
203 wfex
.nSamplesPerSec
= GetDriverParamInt(parm
, "hz", 44100);
204 wfex
.nBlockAlign
= (wfex
.nChannels
* wfex
.wBitsPerSample
) / 8;
205 wfex
.nAvgBytesPerSec
= wfex
.nSamplesPerSec
* wfex
.nBlockAlign
;
207 // Limit buffer size to prevent overflows
208 int bufsize
= GetDriverParamInt(parm
, "samples", 1024);
209 bufsize
= std::min
<int>(bufsize
, UINT16_MAX
);
211 _voice_context
= new StreamingVoiceContext(bufsize
* 4);
213 if (_voice_context
== nullptr)
215 _mastering_voice
->DestroyVoice();
217 FreeLibrary(_xaudio_dll_handle
);
220 return "Failed to create streaming voice context";
223 hr
= _xaudio2
->CreateSourceVoice(&_source_voice
, &wfex
, 0, 1.0f
, _voice_context
);
227 _mastering_voice
->DestroyVoice();
229 FreeLibrary(_xaudio_dll_handle
);
232 Debug(driver
, 0, "xaudio2_s: CreateSourceVoice failed ({:08x})", (uint
)hr
);
233 return "Failed to create a source voice";
236 _voice_context
->SourceVoice
= _source_voice
;
237 hr
= _source_voice
->Start(0, 0);
241 Debug(driver
, 0, "xaudio2_s: _source_voice->Start failed ({:08x})", (uint
)hr
);
244 return "Failed to start the source voice";
247 MxInitialize(wfex
.nSamplesPerSec
);
249 // Submit the first buffer
250 hr
= _voice_context
->SubmitBuffer();
254 Debug(driver
, 0, "xaudio2_s: _voice_context->SubmitBuffer failed ({:08x})", (uint
)hr
);
257 return "Failed to submit the first audio buffer";
264 * Terminates the XAudio2 driver.
266 void SoundDriver_XAudio2::Stop()
269 _source_voice
->DestroyVoice();
271 delete _voice_context
;
272 _voice_context
= nullptr;
274 _mastering_voice
->DestroyVoice();
278 FreeLibrary(_xaudio_dll_handle
);