OpenTTD Source  20240919-master-gdf0233f4c2
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 
121 static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
122 {
123  HRESULT hr;
124  __try {
125  UINT32 flags = 0;
126  hr = xAudio2Create(_xaudio2.GetAddressOf(), flags, XAUDIO2_DEFAULT_PROCESSOR);
127  } __except (EXCEPTION_EXECUTE_HANDLER) {
128  hr = GetExceptionCode();
129  }
130 
131  return hr;
132 }
133 
141 std::optional<std::string_view> SoundDriver_XAudio2::Start(const StringList &parm)
142 {
143  HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
144 
145  if (FAILED(hr))
146  {
147  Debug(driver, 0, "xaudio2_s: CoInitializeEx failed ({:08x})", (uint)hr);
148  return "Failed to initialise COM";
149  }
150 
151  _xaudio_dll_handle = LoadLibraryA(XAUDIO2_DLL_A);
152 
153  if (_xaudio_dll_handle == nullptr)
154  {
155  CoUninitialize();
156 
157  Debug(driver, 0, "xaudio2_s: Unable to load " XAUDIO2_DLL_A);
158  return "Failed to load XAudio2 DLL";
159  }
160 
161  API_XAudio2Create xAudio2Create = (API_XAudio2Create) GetProcAddress(_xaudio_dll_handle, "XAudio2Create");
162 
163  if (xAudio2Create == nullptr)
164  {
165  FreeLibrary(_xaudio_dll_handle);
166  CoUninitialize();
167 
168  Debug(driver, 0, "xaudio2_s: Unable to find XAudio2Create function in DLL");
169  return "Failed to load XAudio2 DLL";
170  }
171 
172  // Create the XAudio engine
173  hr = CreateXAudio(xAudio2Create);
174 
175  if (FAILED(hr))
176  {
177  FreeLibrary(_xaudio_dll_handle);
178  CoUninitialize();
179 
180  Debug(driver, 0, "xaudio2_s: XAudio2Create failed ({:08x})", (uint)hr);
181  return "Failed to inititialise the XAudio2 engine";
182  }
183 
184  // Create a mastering voice
185  hr = _xaudio2->CreateMasteringVoice(&_mastering_voice);
186 
187  if (FAILED(hr))
188  {
189  _xaudio2.Reset();
190  FreeLibrary(_xaudio_dll_handle);
191  CoUninitialize();
192 
193  Debug(driver, 0, "xaudio2_s: CreateMasteringVoice failed ({:08x})", (uint)hr);
194  return "Failed to create a mastering voice";
195  }
196 
197  // Create a source voice to stream our audio
198  WAVEFORMATEX wfex;
199 
200  wfex.wFormatTag = WAVE_FORMAT_PCM;
201  wfex.nChannels = 2;
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;
206 
207  // Limit buffer size to prevent overflows
208  int bufsize = GetDriverParamInt(parm, "samples", 1024);
209  bufsize = std::min<int>(bufsize, UINT16_MAX);
210 
211  _voice_context = new StreamingVoiceContext(bufsize * 4);
212 
213  if (_voice_context == nullptr)
214  {
215  _mastering_voice->DestroyVoice();
216  _xaudio2.Reset();
217  FreeLibrary(_xaudio_dll_handle);
218  CoUninitialize();
219 
220  return "Failed to create streaming voice context";
221  }
222 
223  hr = _xaudio2->CreateSourceVoice(&_source_voice, &wfex, 0, 1.0f, _voice_context);
224 
225  if (FAILED(hr))
226  {
227  _mastering_voice->DestroyVoice();
228  _xaudio2.Reset();
229  FreeLibrary(_xaudio_dll_handle);
230  CoUninitialize();
231 
232  Debug(driver, 0, "xaudio2_s: CreateSourceVoice failed ({:08x})", (uint)hr);
233  return "Failed to create a source voice";
234  }
235 
236  _voice_context->SourceVoice = _source_voice;
237  hr = _source_voice->Start(0, 0);
238 
239  if (FAILED(hr))
240  {
241  Debug(driver, 0, "xaudio2_s: _source_voice->Start failed ({:08x})", (uint)hr);
242 
243  Stop();
244  return "Failed to start the source voice";
245  }
246 
247  MxInitialize(wfex.nSamplesPerSec);
248 
249  // Submit the first buffer
250  hr = _voice_context->SubmitBuffer();
251 
252  if (FAILED(hr))
253  {
254  Debug(driver, 0, "xaudio2_s: _voice_context->SubmitBuffer failed ({:08x})", (uint)hr);
255 
256  Stop();
257  return "Failed to submit the first audio buffer";
258  }
259 
260  return std::nullopt;
261 }
262 
267 {
268  // Clean up XAudio2
269  _source_voice->DestroyVoice();
270 
271  delete _voice_context;
272  _voice_context = nullptr;
273 
274  _mastering_voice->DestroyVoice();
275 
276  _xaudio2.Reset();
277 
278  FreeLibrary(_xaudio_dll_handle);
279  CoUninitialize();
280 }
FSoundDriver_XAudio2
Factory for the XAudio2 sound driver.
Definition: xaudio2_s.h:25
SoundDriver_XAudio2::Start
std::optional< std::string_view > Start(const StringList &param) override
Initialises the XAudio2 driver.
Definition: xaudio2_s.cpp:141
StreamingVoiceContext
Implementation of the IXAudio2VoiceCallback interface.
Definition: xaudio2_s.cpp:47
Debug
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
free
void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:334
StringList
std::vector< std::string > StringList
Type for a list of strings.
Definition: string_type.h:60
GetDriverParamInt
int GetDriverParamInt(const StringList &parm, const char *name, int def)
Get an integer parameter the list of parameters.
Definition: driver.cpp:76
SoundDriver_XAudio2::Stop
void Stop() override
Terminates the XAudio2 driver.
Definition: xaudio2_s.cpp:266
CreateXAudio
static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
Create XAudio2 context with SEH exception checking.
Definition: xaudio2_s.cpp:121
xaudio2_s.h