OpenTTD Source 20250312-master-gcdcc6b491d
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/bitmath_func.hpp"
16#include "../core/math_func.hpp"
17
18// Windows 8 SDK required for XAudio2
19#undef NTDDI_VERSION
20#undef _WIN32_WINNT
21
22#define NTDDI_VERSION NTDDI_WIN8
23#define _WIN32_WINNT _WIN32_WINNT_WIN8
24
25#include "xaudio2_s.h"
26
27#include <windows.h>
28#include <mmsystem.h>
29#include <wrl\client.h>
30#include <xaudio2.h>
31
32using Microsoft::WRL::ComPtr;
33
34#include "../os/windows/win32.h"
35#include "../safeguards.h"
36
37// Definition of the "XAudio2Create" call used to initialise XAudio2
38typedef HRESULT(__stdcall *API_XAudio2Create)(_Outptr_ IXAudio2 **ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
39
40static FSoundDriver_XAudio2 iFSoundDriver_XAudio2;
41
46class StreamingVoiceContext : public IXAudio2VoiceCallback
47{
48private:
49 std::vector<BYTE> buffer;
50
51public:
52 IXAudio2SourceVoice *source_voice = nullptr;
53
54 StreamingVoiceContext(int buffer_length)
55 {
56 this->buffer.resize(buffer_length);
57 }
58
59 virtual ~StreamingVoiceContext() = default;
60
61 HRESULT SubmitBuffer()
62 {
63 // Ensure we do have a valid voice
64 if (this->source_voice == nullptr) {
65 return E_FAIL;
66 }
67
68 MxMixSamples(this->buffer.data(), static_cast<uint>(this->buffer.size() / 4));
69
70 XAUDIO2_BUFFER buf = { 0 };
71 buf.AudioBytes = static_cast<UINT32>(this->buffer.size());
72 buf.pAudioData = this->buffer.data();
73
74 return source_voice->SubmitSourceBuffer(&buf);
75 }
76
77 STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32) override
78 {
79 }
80
81 STDMETHOD_(void, OnVoiceProcessingPassEnd)() override
82 {
83 }
84
85 STDMETHOD_(void, OnStreamEnd)() override
86 {
87 }
88
89 STDMETHOD_(void, OnBufferStart)(void*) override
90 {
91 }
92
93 STDMETHOD_(void, OnBufferEnd)(void*) override
94 {
95 SubmitBuffer();
96 }
97
98 STDMETHOD_(void, OnLoopEnd)(void*) override
99 {
100 }
101
102 STDMETHOD_(void, OnVoiceError)(void*, HRESULT) override
103 {
104 }
105};
106
107static HMODULE _xaudio_dll_handle;
108static IXAudio2SourceVoice *_source_voice = nullptr;
109static IXAudio2MasteringVoice *_mastering_voice = nullptr;
110static ComPtr<IXAudio2> _xaudio2;
111static std::unique_ptr<StreamingVoiceContext> _voice_context;
112
114static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
115{
116 HRESULT hr;
117 __try {
118 UINT32 flags = 0;
119 hr = xAudio2Create(_xaudio2.GetAddressOf(), flags, XAUDIO2_DEFAULT_PROCESSOR);
120 } __except (EXCEPTION_EXECUTE_HANDLER) {
121 hr = GetExceptionCode();
122 }
123
124 return hr;
125}
126
134std::optional<std::string_view> SoundDriver_XAudio2::Start(const StringList &parm)
135{
136 HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
137
138 if (FAILED(hr)) {
139 Debug(driver, 0, "xaudio2_s: CoInitializeEx failed ({:08x})", (uint)hr);
140 return "Failed to initialise COM";
141 }
142
143 _xaudio_dll_handle = LoadLibraryA(XAUDIO2_DLL_A);
144
145 if (_xaudio_dll_handle == nullptr) {
146 CoUninitialize();
147
148 Debug(driver, 0, "xaudio2_s: Unable to load " XAUDIO2_DLL_A);
149 return "Failed to load XAudio2 DLL";
150 }
151
152 API_XAudio2Create xAudio2Create = (API_XAudio2Create) GetProcAddress(_xaudio_dll_handle, "XAudio2Create");
153
154 if (xAudio2Create == nullptr) {
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 hr = CreateXAudio(xAudio2Create);
164
165 if (FAILED(hr)) {
166 FreeLibrary(_xaudio_dll_handle);
167 CoUninitialize();
168
169 Debug(driver, 0, "xaudio2_s: XAudio2Create failed ({:08x})", (uint)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 _xaudio2.Reset();
178 FreeLibrary(_xaudio_dll_handle);
179 CoUninitialize();
180
181 Debug(driver, 0, "xaudio2_s: CreateMasteringVoice failed ({:08x})", (uint)hr);
182 return "Failed to create a mastering voice";
183 }
184
185 // Create a source voice to stream our audio
186 WAVEFORMATEX wfex;
187
188 wfex.wFormatTag = WAVE_FORMAT_PCM;
189 wfex.nChannels = 2;
190 wfex.wBitsPerSample = 16;
191 wfex.nSamplesPerSec = GetDriverParamInt(parm, "hz", 44100);
192 wfex.nBlockAlign = (wfex.nChannels * wfex.wBitsPerSample) / 8;
193 wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
194
195 // Limit buffer size to prevent overflows
196 int bufsize = GetDriverParamInt(parm, "samples", 1024);
197 bufsize = std::min<int>(bufsize, UINT16_MAX);
198
199 _voice_context = std::make_unique<StreamingVoiceContext>(bufsize * 4);
200
201 if (_voice_context == nullptr) {
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.get());
211
212 if (FAILED(hr)) {
213 _mastering_voice->DestroyVoice();
214 _xaudio2.Reset();
215 FreeLibrary(_xaudio_dll_handle);
216 CoUninitialize();
217
218 Debug(driver, 0, "xaudio2_s: CreateSourceVoice failed ({:08x})", (uint)hr);
219 return "Failed to create a source voice";
220 }
221
222 _voice_context->source_voice = _source_voice;
223 hr = _source_voice->Start(0, 0);
224
225 if (FAILED(hr)) {
226 Debug(driver, 0, "xaudio2_s: _source_voice->Start failed ({:08x})", (uint)hr);
227
228 Stop();
229 return "Failed to start the source voice";
230 }
231
232 MxInitialize(wfex.nSamplesPerSec);
233
234 // Submit the first buffer
235 hr = _voice_context->SubmitBuffer();
236
237 if (FAILED(hr)) {
238 Debug(driver, 0, "xaudio2_s: _voice_context->SubmitBuffer failed ({:08x})", (uint)hr);
239
240 Stop();
241 return "Failed to submit the first audio buffer";
242 }
243
244 return std::nullopt;
245}
246
251{
252 // Clean up XAudio2
253 _source_voice->DestroyVoice();
254
255 _voice_context = nullptr;
256
257 _mastering_voice->DestroyVoice();
258
259 _xaudio2.Reset();
260
261 FreeLibrary(_xaudio_dll_handle);
262 CoUninitialize();
263}
Factory for the XAudio2 sound driver.
Definition xaudio2_s.h:25
std::optional< std::string_view > Start(const StringList &param) override
Initialises the XAudio2 driver.
void Stop() override
Terminates the XAudio2 driver.
Implementation of the IXAudio2VoiceCallback interface.
Definition xaudio2_s.cpp:47
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
int GetDriverParamInt(const StringList &parm, const char *name, int def)
Get an integer parameter the list of parameters.
Definition driver.cpp:77
std::vector< std::string > StringList
Type for a list of strings.
Definition string_type.h:60
static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
Create XAudio2 context with SEH exception checking.
Base for XAudio2 sound handling.