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