OpenTTD Source 20250205-master-gfd85ab1e2c
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
33using 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
39typedef HRESULT(__stdcall *API_XAudio2Create)(_Outptr_ IXAudio2 **ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
40
41static FSoundDriver_XAudio2 iFSoundDriver_XAudio2;
42
47class StreamingVoiceContext : public IXAudio2VoiceCallback
48{
49private:
50 std::vector<BYTE> buffer;
51
52public:
53 IXAudio2SourceVoice *source_voice = nullptr;
54
55 StreamingVoiceContext(int buffer_length)
56 {
57 this->buffer.resize(buffer_length);
58 }
59
60 HRESULT SubmitBuffer()
61 {
62 // Ensure we do have a valid voice
63 if (this->source_voice == nullptr) {
64 return E_FAIL;
65 }
66
67 MxMixSamples(this->buffer.data(), static_cast<uint>(this->buffer.size() / 4));
68
69 XAUDIO2_BUFFER buf = { 0 };
70 buf.AudioBytes = static_cast<UINT32>(this->buffer.size());
71 buf.pAudioData = this->buffer.data();
72
73 return source_voice->SubmitSourceBuffer(&buf);
74 }
75
76 STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32) override
77 {
78 }
79
80 STDMETHOD_(void, OnVoiceProcessingPassEnd)() override
81 {
82 }
83
84 STDMETHOD_(void, OnStreamEnd)() override
85 {
86 }
87
88 STDMETHOD_(void, OnBufferStart)(void*) override
89 {
90 }
91
92 STDMETHOD_(void, OnBufferEnd)(void*) override
93 {
94 SubmitBuffer();
95 }
96
97 STDMETHOD_(void, OnLoopEnd)(void*) override
98 {
99 }
100
101 STDMETHOD_(void, OnVoiceError)(void*, HRESULT) override
102 {
103 }
104};
105
106static HMODULE _xaudio_dll_handle;
107static IXAudio2SourceVoice *_source_voice = nullptr;
108static IXAudio2MasteringVoice *_mastering_voice = nullptr;
109static ComPtr<IXAudio2> _xaudio2;
110static std::unique_ptr<StreamingVoiceContext> _voice_context;
111
113static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
114{
115 HRESULT hr;
116 __try {
117 UINT32 flags = 0;
118 hr = xAudio2Create(_xaudio2.GetAddressOf(), flags, XAUDIO2_DEFAULT_PROCESSOR);
119 } __except (EXCEPTION_EXECUTE_HANDLER) {
120 hr = GetExceptionCode();
121 }
122
123 return hr;
124}
125
133std::optional<std::string_view> SoundDriver_XAudio2::Start(const StringList &parm)
134{
135 HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
136
137 if (FAILED(hr)) {
138 Debug(driver, 0, "xaudio2_s: CoInitializeEx failed ({:08x})", (uint)hr);
139 return "Failed to initialise COM";
140 }
141
142 _xaudio_dll_handle = LoadLibraryA(XAUDIO2_DLL_A);
143
144 if (_xaudio_dll_handle == nullptr) {
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 == nullptr) {
154 FreeLibrary(_xaudio_dll_handle);
155 CoUninitialize();
156
157 Debug(driver, 0, "xaudio2_s: Unable to find XAudio2Create function in DLL");
158 return "Failed to load XAudio2 DLL";
159 }
160
161 // Create the XAudio engine
162 hr = CreateXAudio(xAudio2Create);
163
164 if (FAILED(hr)) {
165 FreeLibrary(_xaudio_dll_handle);
166 CoUninitialize();
167
168 Debug(driver, 0, "xaudio2_s: XAudio2Create failed ({:08x})", (uint)hr);
169 return "Failed to inititialise the XAudio2 engine";
170 }
171
172 // Create a mastering voice
173 hr = _xaudio2->CreateMasteringVoice(&_mastering_voice);
174
175 if (FAILED(hr)) {
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, "samples", 1024);
196 bufsize = std::min<int>(bufsize, UINT16_MAX);
197
198 _voice_context = std::make_unique<StreamingVoiceContext>(bufsize * 4);
199
200 if (_voice_context == nullptr) {
201 _mastering_voice->DestroyVoice();
202 _xaudio2.Reset();
203 FreeLibrary(_xaudio_dll_handle);
204 CoUninitialize();
205
206 return "Failed to create streaming voice context";
207 }
208
209 hr = _xaudio2->CreateSourceVoice(&_source_voice, &wfex, 0, 1.0f, _voice_context.get());
210
211 if (FAILED(hr)) {
212 _mastering_voice->DestroyVoice();
213 _xaudio2.Reset();
214 FreeLibrary(_xaudio_dll_handle);
215 CoUninitialize();
216
217 Debug(driver, 0, "xaudio2_s: CreateSourceVoice failed ({:08x})", (uint)hr);
218 return "Failed to create a source voice";
219 }
220
221 _voice_context->source_voice = _source_voice;
222 hr = _source_voice->Start(0, 0);
223
224 if (FAILED(hr)) {
225 Debug(driver, 0, "xaudio2_s: _source_voice->Start failed ({:08x})", (uint)hr);
226
227 Stop();
228 return "Failed to start the source voice";
229 }
230
231 MxInitialize(wfex.nSamplesPerSec);
232
233 // Submit the first buffer
234 hr = _voice_context->SubmitBuffer();
235
236 if (FAILED(hr)) {
237 Debug(driver, 0, "xaudio2_s: _voice_context->SubmitBuffer failed ({:08x})", (uint)hr);
238
239 Stop();
240 return "Failed to submit the first audio buffer";
241 }
242
243 return std::nullopt;
244}
245
250{
251 // Clean up XAudio2
252 _source_voice->DestroyVoice();
253
254 _voice_context = nullptr;
255
256 _mastering_voice->DestroyVoice();
257
258 _xaudio2.Reset();
259
260 FreeLibrary(_xaudio_dll_handle);
261 CoUninitialize();
262}
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:48
#define Debug(category, level, format_string,...)
Ouptut 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:76
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.