OpenTTD
xaudio2_s.cpp
Go to the documentation of this file.
1 /*
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/>.
6  */
7 
10 #ifdef WITH_XAUDIO2
11 
12 #include "../stdafx.h"
13 #include "../openttd.h"
14 #include "../driver.h"
15 #include "../mixer.h"
16 #include "../debug.h"
17 #include "../core/alloc_func.hpp"
18 #include "../core/bitmath_func.hpp"
19 #include "../core/math_func.hpp"
20 
21 // Windows 8 SDK required for XAudio2
22 #undef NTDDI_VERSION
23 #undef _WIN32_WINNT
24 
25 #define NTDDI_VERSION NTDDI_WIN8
26 #define _WIN32_WINNT _WIN32_WINNT_WIN8
27 
28 #include "xaudio2_s.h"
29 
30 #include <windows.h>
31 #include <mmsystem.h>
32 #include <wrl\client.h>
33 #include <xaudio2.h>
34 
35 using Microsoft::WRL::ComPtr;
36 
37 #include "../os/windows/win32.h"
38 #include "../safeguards.h"
39 
40 // Definition of the "XAudio2Create" call used to initialise XAudio2
41 typedef HRESULT(__stdcall *API_XAudio2Create)(_Outptr_ IXAudio2** ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
42 
43 static FSoundDriver_XAudio2 iFSoundDriver_XAudio2;
44 
49 class StreamingVoiceContext : public IXAudio2VoiceCallback
50 {
51 private:
52  int bufferLength;
53  char *buffer;
54 
55 public:
56  IXAudio2SourceVoice* SourceVoice;
57 
58  StreamingVoiceContext(int bufferLength)
59  {
60  this->bufferLength = bufferLength;
61  this->buffer = MallocT<char>(bufferLength);
62  }
63 
64  virtual ~StreamingVoiceContext()
65  {
66  free(this->buffer);
67  }
68 
69  HRESULT SubmitBuffer()
70  {
71  // Ensure we do have a valid voice
72  if (this->SourceVoice == nullptr)
73  {
74  return E_FAIL;
75  }
76 
77  MxMixSamples(this->buffer, this->bufferLength / 4);
78 
79  XAUDIO2_BUFFER buf = { 0 };
80  buf.AudioBytes = this->bufferLength;
81  buf.pAudioData = (const BYTE *) this->buffer;
82 
83  return SourceVoice->SubmitSourceBuffer(&buf);
84  }
85 
86  STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32) override
87  {
88  }
89 
90  STDMETHOD_(void, OnVoiceProcessingPassEnd)() override
91  {
92  }
93 
94  STDMETHOD_(void, OnStreamEnd)() override
95  {
96  }
97 
98  STDMETHOD_(void, OnBufferStart)(void*) override
99  {
100  }
101 
102  STDMETHOD_(void, OnBufferEnd)(void*) override
103  {
104  SubmitBuffer();
105  }
106 
107  STDMETHOD_(void, OnLoopEnd)(void*) override
108  {
109  }
110 
111  STDMETHOD_(void, OnVoiceError)(void*, HRESULT) override
112  {
113  }
114 };
115 
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;
121 
129 const char *SoundDriver_XAudio2::Start(const char * const *parm)
130 {
131  HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
132 
133  if (FAILED(hr))
134  {
135  DEBUG(driver, 0, "xaudio2_s: CoInitializeEx failed (%08x)", hr);
136  return "Failed to initialise COM";
137  }
138 
139  _xaudio_dll_handle = LoadLibraryA(XAUDIO2_DLL_A);
140 
141  if (_xaudio_dll_handle == nullptr)
142  {
143  CoUninitialize();
144 
145  DEBUG(driver, 0, "xaudio2_s: Unable to load " XAUDIO2_DLL_A);
146  return "Failed to load XAudio2 DLL";
147  }
148 
149  API_XAudio2Create xAudio2Create = (API_XAudio2Create) GetProcAddress(_xaudio_dll_handle, "XAudio2Create");
150 
151  if (xAudio2Create == nullptr)
152  {
153  FreeLibrary(_xaudio_dll_handle);
154  CoUninitialize();
155 
156  DEBUG(driver, 0, "xaudio2_s: Unable to find XAudio2Create function in DLL");
157  return "Failed to load XAudio2 DLL";
158  }
159 
160  // Create the XAudio engine
161  UINT32 flags = 0;
162  hr = xAudio2Create(_xaudio2.GetAddressOf(), flags, XAUDIO2_DEFAULT_PROCESSOR);
163 
164  if (FAILED(hr))
165  {
166  FreeLibrary(_xaudio_dll_handle);
167  CoUninitialize();
168 
169  DEBUG(driver, 0, "xaudio2_s: XAudio2Create failed (%08x)", hr);
170  return "Failed to inititialise the XAudio2 engine";
171  }
172 
173  // Create a mastering voice
174  hr = _xaudio2->CreateMasteringVoice(&_mastering_voice);
175 
176  if (FAILED(hr))
177  {
178  _xaudio2.Reset();
179  FreeLibrary(_xaudio_dll_handle);
180  CoUninitialize();
181 
182  DEBUG(driver, 0, "xaudio2_s: CreateMasteringVoice failed (%08x)", hr);
183  return "Failed to create a mastering voice";
184  }
185 
186  // Create a source voice to stream our audio
187  WAVEFORMATEX wfex;
188 
189  wfex.wFormatTag = WAVE_FORMAT_PCM;
190  wfex.nChannels = 2;
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;
195 
196  // Limit buffer size to prevent overflows
197  int bufsize = GetDriverParamInt(parm, "bufsize", 8192);
198  bufsize = min(bufsize, UINT16_MAX);
199 
200  _voice_context = new StreamingVoiceContext(bufsize * 4);
201 
202  if (_voice_context == nullptr)
203  {
204  _mastering_voice->DestroyVoice();
205  _xaudio2.Reset();
206  FreeLibrary(_xaudio_dll_handle);
207  CoUninitialize();
208 
209  return "Failed to create streaming voice context";
210  }
211 
212  hr = _xaudio2->CreateSourceVoice(&_source_voice, &wfex, 0, 1.0f, _voice_context);
213 
214  if (FAILED(hr))
215  {
216  _mastering_voice->DestroyVoice();
217  _xaudio2.Reset();
218  FreeLibrary(_xaudio_dll_handle);
219  CoUninitialize();
220 
221  DEBUG(driver, 0, "xaudio2_s: CreateSourceVoice failed (%08x)", hr);
222  return "Failed to create a source voice";
223  }
224 
225  _voice_context->SourceVoice = _source_voice;
226  hr = _source_voice->Start(0, 0);
227 
228  if (FAILED(hr))
229  {
230  DEBUG(driver, 0, "xaudio2_s: _source_voice->Start failed (%08x)", hr);
231 
232  Stop();
233  return "Failed to start the source voice";
234  }
235 
236  MxInitialize(wfex.nSamplesPerSec);
237 
238  // Submit the first buffer
239  hr = _voice_context->SubmitBuffer();
240 
241  if (FAILED(hr))
242  {
243  DEBUG(driver, 0, "xaudio2_s: _voice_context->SubmitBuffer failed (%08x)", hr);
244 
245  Stop();
246  return "Failed to submit the first audio buffer";
247  }
248 
249  return nullptr;
250 }
251 
256 {
257  // Clean up XAudio2
258  _source_voice->DestroyVoice();
259 
260  delete _voice_context;
261  _voice_context = nullptr;
262 
263  _mastering_voice->DestroyVoice();
264 
265  _xaudio2.Reset();
266 
267  FreeLibrary(_xaudio_dll_handle);
268  CoUninitialize();
269 }
270 
271 #endif
Factory for the XAudio2 sound driver.
Definition: xaudio2_s.h:25
void Stop() override
Stop this driver.
static T min(const T a, const T b)
Returns the minimum of two values.
Definition: math_func.hpp:40
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:35
const char * Start(const char *const *param) override
Start this driver.
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: depend.cpp:129
int GetDriverParamInt(const char *const *parm, const char *name, int def)
Get an integer parameter the list of parameters.
Definition: driver.cpp:73
Base for XAudio2 sound handling.