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. */
12 #include "../stdafx.h"
13 #include "../openttd.h"
14 #include "../driver.h"
17 #include "../core/alloc_func.hpp"
18 #include "../core/bitmath_func.hpp"
19 #include "../core/math_func.hpp"
21 // Windows 8 SDK required for XAudio2
25 #define NTDDI_VERSION NTDDI_WIN8
26 #define _WIN32_WINNT _WIN32_WINNT_WIN8
28 #include "xaudio2_s.h"
32 #include <wrl\client.h>
35 using Microsoft::WRL::ComPtr
;
37 #include "../os/windows/win32.h"
38 #include "../safeguards.h"
40 // Definition of the "XAudio2Create" call used to initialise XAudio2
41 typedef HRESULT(__stdcall
*API_XAudio2Create
)(_Outptr_ IXAudio2
** ppXAudio2
, UINT32 Flags
, XAUDIO2_PROCESSOR XAudio2Processor
);
43 static FSoundDriver_XAudio2 iFSoundDriver_XAudio2
;
46 * Implementation of the IXAudio2VoiceCallback interface.
47 * Provides buffered audio to XAudio2 from the OpenTTD mixer.
49 class StreamingVoiceContext
: public IXAudio2VoiceCallback
56 IXAudio2SourceVoice
* SourceVoice
;
58 StreamingVoiceContext(int bufferLength
)
60 this->bufferLength
= bufferLength
;
61 this->buffer
= MallocT
<char>(bufferLength
);
64 virtual ~StreamingVoiceContext()
69 HRESULT
SubmitBuffer()
71 // Ensure we do have a valid voice
72 if (this->SourceVoice
== nullptr)
77 MxMixSamples(this->buffer
, this->bufferLength
/ 4);
79 XAUDIO2_BUFFER buf
= { 0 };
80 buf
.AudioBytes
= this->bufferLength
;
81 buf
.pAudioData
= (const BYTE
*) this->buffer
;
83 return SourceVoice
->SubmitSourceBuffer(&buf
);
86 STDMETHOD_(void, OnVoiceProcessingPassStart
)(UINT32
) override
90 STDMETHOD_(void, OnVoiceProcessingPassEnd
)() override
94 STDMETHOD_(void, OnStreamEnd
)() override
98 STDMETHOD_(void, OnBufferStart
)(void*) override
102 STDMETHOD_(void, OnBufferEnd
)(void*) override
107 STDMETHOD_(void, OnLoopEnd
)(void*) override
111 STDMETHOD_(void, OnVoiceError
)(void*, HRESULT
) override
116 static HMODULE _xaudio_dll_handle
;
117 static IXAudio2SourceVoice
* _source_voice
= nullptr;
118 static IXAudio2MasteringVoice
* _mastering_voice
= nullptr;
119 static ComPtr
<IXAudio2
> _xaudio2
;
120 static StreamingVoiceContext
* _voice_context
= nullptr;
123 * Initialises the XAudio2 driver.
125 * @param parm Driver parameters.
126 * @return An error message if unsuccessful, or nullptr otherwise.
129 const char *SoundDriver_XAudio2::Start(const char * const *parm
)
131 HRESULT hr
= CoInitializeEx(nullptr, COINIT_MULTITHREADED
);
135 DEBUG(driver
, 0, "xaudio2_s: CoInitializeEx failed (%08x)", hr
);
136 return "Failed to initialise COM";
139 _xaudio_dll_handle
= LoadLibraryA(XAUDIO2_DLL_A
);
141 if (_xaudio_dll_handle
== nullptr)
145 DEBUG(driver
, 0, "xaudio2_s: Unable to load " XAUDIO2_DLL_A
);
146 return "Failed to load XAudio2 DLL";
149 API_XAudio2Create xAudio2Create
= (API_XAudio2Create
) GetProcAddress(_xaudio_dll_handle
, "XAudio2Create");
151 if (xAudio2Create
== nullptr)
153 FreeLibrary(_xaudio_dll_handle
);
156 DEBUG(driver
, 0, "xaudio2_s: Unable to find XAudio2Create function in DLL");
157 return "Failed to load XAudio2 DLL";
160 // Create the XAudio engine
162 hr
= xAudio2Create(_xaudio2
.GetAddressOf(), flags
, XAUDIO2_DEFAULT_PROCESSOR
);
166 FreeLibrary(_xaudio_dll_handle
);
169 DEBUG(driver
, 0, "xaudio2_s: XAudio2Create failed (%08x)", hr
);
170 return "Failed to inititialise the XAudio2 engine";
173 // Create a mastering voice
174 hr
= _xaudio2
->CreateMasteringVoice(&_mastering_voice
);
179 FreeLibrary(_xaudio_dll_handle
);
182 DEBUG(driver
, 0, "xaudio2_s: CreateMasteringVoice failed (%08x)", hr
);
183 return "Failed to create a mastering voice";
186 // Create a source voice to stream our audio
189 wfex
.wFormatTag
= WAVE_FORMAT_PCM
;
191 wfex
.wBitsPerSample
= 16;
192 wfex
.nSamplesPerSec
= GetDriverParamInt(parm
, "hz", 44100);
193 wfex
.nBlockAlign
= (wfex
.nChannels
* wfex
.wBitsPerSample
) / 8;
194 wfex
.nAvgBytesPerSec
= wfex
.nSamplesPerSec
* wfex
.nBlockAlign
;
196 // Limit buffer size to prevent overflows
197 int bufsize
= GetDriverParamInt(parm
, "bufsize", 8192);
198 bufsize
= min(bufsize
, UINT16_MAX
);
200 _voice_context
= new StreamingVoiceContext(bufsize
* 4);
202 if (_voice_context
== nullptr)
204 _mastering_voice
->DestroyVoice();
206 FreeLibrary(_xaudio_dll_handle
);
209 return "Failed to create streaming voice context";
212 hr
= _xaudio2
->CreateSourceVoice(&_source_voice
, &wfex
, 0, 1.0f
, _voice_context
);
216 _mastering_voice
->DestroyVoice();
218 FreeLibrary(_xaudio_dll_handle
);
221 DEBUG(driver
, 0, "xaudio2_s: CreateSourceVoice failed (%08x)", hr
);
222 return "Failed to create a source voice";
225 _voice_context
->SourceVoice
= _source_voice
;
226 hr
= _source_voice
->Start(0, 0);
230 DEBUG(driver
, 0, "xaudio2_s: _source_voice->Start failed (%08x)", hr
);
233 return "Failed to start the source voice";
236 MxInitialize(wfex
.nSamplesPerSec
);
238 // Submit the first buffer
239 hr
= _voice_context
->SubmitBuffer();
243 DEBUG(driver
, 0, "xaudio2_s: _voice_context->SubmitBuffer failed (%08x)", hr
);
246 return "Failed to submit the first audio buffer";
253 * Terminates the XAudio2 driver.
255 void SoundDriver_XAudio2::Stop()
258 _source_voice
->DestroyVoice();
260 delete _voice_context
;
261 _voice_context
= nullptr;
263 _mastering_voice
->DestroyVoice();
267 FreeLibrary(_xaudio_dll_handle
);