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