OpenTTD Source 20260311-master-g511d3794ce
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../stdafx.h"
11#include "../openttd.h"
12#include "../driver.h"
13#include "../mixer.h"
14#include "../debug.h"
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
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 /* This needs to be virtual because the XAudio2 API declares the interface without
60 * a virtual destructor, even though it has functions that need to be overridden. */
61 virtual ~StreamingVoiceContext() = default;
62
63 HRESULT SubmitBuffer()
64 {
65 /* Ensure we do have a valid voice */
66 if (this->source_voice == nullptr) {
67 return E_FAIL;
68 }
69
70 MxMixSamples(this->buffer.data(), static_cast<uint>(this->buffer.size() / 4));
71
72 XAUDIO2_BUFFER buf = { 0 };
73 buf.AudioBytes = static_cast<UINT32>(this->buffer.size());
74 buf.pAudioData = this->buffer.data();
75
76 return source_voice->SubmitSourceBuffer(&buf);
77 }
78
79 STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32) override
80 {
81 }
82
83 STDMETHOD_(void, OnVoiceProcessingPassEnd)() override
84 {
85 }
86
87 STDMETHOD_(void, OnStreamEnd)() override
88 {
89 }
90
91 STDMETHOD_(void, OnBufferStart)(void*) override
92 {
93 }
94
95 STDMETHOD_(void, OnBufferEnd)(void*) override
96 {
97 SubmitBuffer();
98 }
99
100 STDMETHOD_(void, OnLoopEnd)(void*) override
101 {
102 }
103
104 STDMETHOD_(void, OnVoiceError)(void*, HRESULT) override
105 {
106 }
107};
108
109static HMODULE _xaudio_dll_handle;
110static IXAudio2SourceVoice *_source_voice = nullptr;
111static IXAudio2MasteringVoice *_mastering_voice = nullptr;
112static ComPtr<IXAudio2> _xaudio2;
113static std::unique_ptr<StreamingVoiceContext> _voice_context;
114
120static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
121{
122 HRESULT hr;
123 __try {
124 UINT32 flags = 0;
125 hr = xAudio2Create(_xaudio2.GetAddressOf(), flags, XAUDIO2_DEFAULT_PROCESSOR);
126 } __except (EXCEPTION_EXECUTE_HANDLER) {
127 hr = GetExceptionCode();
128 }
129
130 return hr;
131}
132
140std::optional<std::string_view> SoundDriver_XAudio2::Start(const StringList &parm)
141{
142 HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
143
144 if (FAILED(hr)) {
145 Debug(driver, 0, "xaudio2_s: CoInitializeEx failed ({:08x})", (uint)hr);
146 return "Failed to initialise COM";
147 }
148
149 _xaudio_dll_handle = LoadLibraryA(XAUDIO2_DLL_A);
150
151 if (_xaudio_dll_handle == nullptr) {
152 CoUninitialize();
153
154 Debug(driver, 0, "xaudio2_s: Unable to load " XAUDIO2_DLL_A);
155 return "Failed to load XAudio2 DLL";
156 }
157
158 API_XAudio2Create xAudio2Create = (API_XAudio2Create) GetProcAddress(_xaudio_dll_handle, "XAudio2Create");
159
160 if (xAudio2Create == nullptr) {
161 FreeLibrary(_xaudio_dll_handle);
162 CoUninitialize();
163
164 Debug(driver, 0, "xaudio2_s: Unable to find XAudio2Create function in DLL");
165 return "Failed to load XAudio2 DLL";
166 }
167
168 /* Create the XAudio engine */
169 hr = CreateXAudio(xAudio2Create);
170
171 if (FAILED(hr)) {
172 FreeLibrary(_xaudio_dll_handle);
173 CoUninitialize();
174
175 Debug(driver, 0, "xaudio2_s: XAudio2Create failed ({:08x})", (uint)hr);
176 return "Failed to initialise the XAudio2 engine";
177 }
178
179 /* Create a mastering voice */
180 hr = _xaudio2->CreateMasteringVoice(&_mastering_voice);
181
182 if (FAILED(hr)) {
183 _xaudio2.Reset();
184 FreeLibrary(_xaudio_dll_handle);
185 CoUninitialize();
186
187 Debug(driver, 0, "xaudio2_s: CreateMasteringVoice failed ({:08x})", (uint)hr);
188 return "Failed to create a mastering voice";
189 }
190
191 /* Create a source voice to stream our audio */
192 WAVEFORMATEX wfex;
193
194 wfex.wFormatTag = WAVE_FORMAT_PCM;
195 wfex.nChannels = 2;
196 wfex.wBitsPerSample = 16;
197 wfex.nSamplesPerSec = GetDriverParamInt(parm, "hz", 44100);
198 wfex.nBlockAlign = (wfex.nChannels * wfex.wBitsPerSample) / 8;
199 wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
200
201 /* Limit buffer size to prevent overflows */
202 int bufsize = GetDriverParamInt(parm, "samples", 1024);
203 bufsize = std::min<int>(bufsize, UINT16_MAX);
204
205 _voice_context = std::make_unique<StreamingVoiceContext>(bufsize * 4);
206
207 if (_voice_context == nullptr) {
208 _mastering_voice->DestroyVoice();
209 _xaudio2.Reset();
210 FreeLibrary(_xaudio_dll_handle);
211 CoUninitialize();
212
213 return "Failed to create streaming voice context";
214 }
215
216 hr = _xaudio2->CreateSourceVoice(&_source_voice, &wfex, 0, 1.0f, _voice_context.get());
217
218 if (FAILED(hr)) {
219 _mastering_voice->DestroyVoice();
220 _xaudio2.Reset();
221 FreeLibrary(_xaudio_dll_handle);
222 CoUninitialize();
223
224 Debug(driver, 0, "xaudio2_s: CreateSourceVoice failed ({:08x})", (uint)hr);
225 return "Failed to create a source voice";
226 }
227
228 _voice_context->source_voice = _source_voice;
229 hr = _source_voice->Start(0, 0);
230
231 if (FAILED(hr)) {
232 Debug(driver, 0, "xaudio2_s: _source_voice->Start failed ({:08x})", (uint)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 Debug(driver, 0, "xaudio2_s: _voice_context->SubmitBuffer failed ({:08x})", (uint)hr);
245
246 Stop();
247 return "Failed to submit the first audio buffer";
248 }
249
250 return std::nullopt;
251}
252
257{
258 /* Clean up XAudio2 */
259 _source_voice->DestroyVoice();
260
261 _voice_context = nullptr;
262
263 _mastering_voice->DestroyVoice();
264
265 _xaudio2.Reset();
266
267 FreeLibrary(_xaudio_dll_handle);
268 CoUninitialize();
269}
Functions related to bit mathematics.
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.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
int GetDriverParamInt(const StringList &parm, std::string_view name, int def)
Get an integer parameter the list of parameters.
Definition driver.cpp:79
Base for all drivers (video, sound, music, etc).
Integer math functions.
Functions to mix sound samples.
Some generic types.
A number of safeguards to prevent using unsafe methods.
@ Flags
ScriptConfigFlags defining how/when to use this configuration.
Definition of base types and functions in a cross-platform compatible way.
std::vector< std::string > StringList
Type for a list of strings.
Definition string_type.h:60
Declarations of functions for MS windows systems.
static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
Create XAudio2 context with SEH exception checking.
HRESULT(__stdcall * API_XAudio2Create)(_Outptr_ IXAudio2 **ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor)
Definition of the "XAudio2Create" call used to initialise XAudio2.
Definition xaudio2_s.cpp:38
Base for XAudio2 sound handling.