comparison foo_out_sdl.cc @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents e9bb126753e7
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 /* SDL output for foobar2000. 1 #include <algorithm>
2 * Somewhat scuffed since SDL does not provide latency */ 2 #include <cstdlib>
3 3 #include <cstdio>
4 #include <SDL3/SDL.h> 4 #include <cstdarg>
5 #include <cmath>
6 #include <atomic>
7
8 #include "SDL3/SDL.h"
9
10 #include "foobar2000/helpers/foobar2000+atl.h"
11 #include "foobar2000/SDK/core_api.h"
12 #include "foobar2000/SDK/output.h"
5 13
6 #if audio_sample_size == 32 14 #if audio_sample_size == 32
7 # define OUTSDL_USE_NATIVE_F32 1 15 # define OUTSDL_USE_NATIVE_F32 1
8 # define OUTSDL_FORMAT SDL_AUDIO_F32 16 # define OUTSDL_FORMAT SDL_AUDIO_F32
9 #else 17 #else
10 # define OUTSDL_FORMAT SDL_AUDIO_S32 18 # define OUTSDL_FORMAT SDL_AUDIO_S32
11 #endif 19 #endif
12 20
13 class OutputSDL : public output { 21 /* ----------------------------------------------------------------------------------------- */
22 /* logf: printf-like interface for printing into the console
23 * a.k.a. "paper hates std::stringstream"
24 * doing it this way results in a smaller binary as well */
25
26 enum class logf_level {
27 info,
28 error,
29 };
30
31 static int vlogf(logf_level level, const char* format, std::va_list ap)
32 {
33 char buf[1024];
34 int r;
35
36 r = std::vsnprintf(buf, sizeof(buf), format, ap);
37 if (r < 0)
38 return -1;
39
40 switch (level) {
41 case logf_level::info:
42 console::info(buf);
43 break;
44 case logf_level::error:
45 console::error(buf);
46 break;
47 }
48
49 return r;
50 }
51
52 static int logf(logf_level level, const char* format, ...)
53 {
54 std::va_list ap;
55 int r;
56
57 va_start(ap, format);
58
59 r = vlogf(level, format, ap);
60
61 va_end(ap);
62
63 return r;
64 }
65
66 /* ----------------------------------------------------------------------------------------- */
67
68 class OutputSDL : public output_v4 {
14 public: 69 public:
15 OutputSDL(); 70 OutputSDL(const GUID& p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth);
16 ~OutputSDL(); 71 ~OutputSDL();
17 72
18 virtual double get_latency() override; 73 virtual double get_latency() override;
19 virtual void process_samples(const audio_chunk &p_chunk) override; 74 virtual void process_samples(const audio_chunk &p_chunk) override;
20 virtual void update(bool &p_ready) override; 75 virtual void update(bool &p_ready) override;
21 virtual void pause(bool p_state) override; 76 virtual void pause(bool p_state) override;
22 virtual void flush() override; 77 virtual void flush() override;
23 virtual void force_play() override; 78 virtual void force_play() override;
24 virtual void volume_set(double p_val) override; 79 virtual void volume_set(double p_val) override;
80 virtual bool is_progressing() override;
81 virtual size_t update_v2() override;
82
83 static GUID g_get_guid() { return { 0x90033ef5, 0x6703, 0x44a0, { 0x98, 0x47, 0x53, 0x99, 0x1, 0x53, 0x3b, 0xd6 } }; }
84 static void g_enum_devices(output_device_enum_callback &p_callback);
85 static bool g_advanced_settings_query() { return false; }
86 static bool g_needs_bitdepth_config() { return false; }
87 static bool g_needs_dither_config() { return false; }
88 static bool g_needs_device_list_prefixes() { return true; }
89 static bool g_supports_multiple_streams() { return false; }
90 static bool g_is_high_latency() { return true; }
91 static uint32_t g_extra_flags() { return 0; }
92 static void g_advanced_settings_popup(HWND p_parent, POINT p_menupoint) {}
93 static const char* g_get_name() { return "SDL"; }
25 94
26 private: 95 private:
96 // ---------- Static members --------------
97 // These are for loading SDL, as well as
98 // converting to/from GUIDs.
99 static HMODULE sdl_;
100
101 static GUID DevIDtoGUID(SDL_AudioDeviceID id);
102 static SDL_AudioDeviceID GUIDtoDevID(const GUID &id);
103
104 static bool LoadSDL();
105 static bool IsSDLLoaded();
106 static void UnloadSDL();
107 // ----------------------------------------
108
109 static void SDLCALL AudioStreamCallback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount);
110
111 void StreamCallback(int amount);
112
27 void ReinitStream(unsigned int channels, unsigned int freq); 113 void ReinitStream(unsigned int channels, unsigned int freq);
28 114
115 // the audio stream itself
29 SDL_AudioStream *stream_; 116 SDL_AudioStream *stream_;
30 #ifndef OUTSDL_USE_NATIVE_F32 117 #ifndef OUTSDL_USE_NATIVE_F32
31 std::vector<t_int32> buffer_; 118 // buffer to convert f64 -> f32
119 std::vector<float> buffer_;
32 #endif 120 #endif
121 // the current spec of the audio stream
33 SDL_AudioSpec spec_; 122 SDL_AudioSpec spec_;
34 HANDLE sdl_; 123
35 124 std::atomic<int> needed_;
36 #define FUNC(type, x, params, callparams) decltype(&SDL_##x) sdl3_##x; 125
126 #define FUNC(type, x, params, callparams) static decltype(&SDL_##x) sdl3_##x;
37 #include "foo_out_sdl_funcs.h" 127 #include "foo_out_sdl_funcs.h"
38 }; 128 };
39 129
40 OutputSDL::OutputSDL() 130 HMODULE OutputSDL::sdl_ = nullptr;
41 : sdl_(nullptr) 131 #define FUNC(type, x, params, callparams) decltype(&SDL_##x) OutputSDL::sdl3_##x = nullptr;
42 , stream_(nullptr) 132 #include "foo_out_sdl_funcs.h"
133
134 OutputSDL::OutputSDL(const GUID &p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth)
135 : stream_(nullptr)
43 #ifndef OUTSDL_USE_NATIVE_F32 136 #ifndef OUTSDL_USE_NATIVE_F32
44 /* sane default size */ 137 /* sane default size */
45 , buffer_(8096) 138 , buffer_(8096)
46 #endif 139 #endif
47 { 140 {
48 sdl_ = LoadLibraryA("foo_out_sdl_wrapper.dll"); 141 // uhhhh
49 if (!sdl3_) 142 needed_.store(0);
50 return; 143
51 144 if (!LoadSDL())
52 #define FUNC(type, x, params, callparams) \ 145 return; // uh oh
53 sdl3_##x = (decltype(&SDL_##x))GetProcAddress(sdl_, "SDL_" #x); \
54 if (!sdl3_##x) \
55 return;
56 #include "foo_out_sdl_funcs.h"
57 146
58 // increment subsystem counter 147 // increment subsystem counter
59 // hope this succeeds!!! 148 // hope this succeeds!!!
60 sdl3_InitSubSystem(SDL_INIT_AUDIO); 149 if (!sdl3_InitSubSystem(SDL_INIT_AUDIO))
150 logf(logf_level::error, sdl3_GetError());
61 151
62 /* make a guess */ 152 /* make a guess */
63 spec_.format = OUTSDL_FORMAT; 153 spec_.format = OUTSDL_FORMAT;
64 spec_.channels = 2; /* Stereo is most likely. Who cares about surround? :) */ 154 spec_.channels = 2; /* Stereo is most likely. Who cares about surround? :) */
65 spec_.freq = 44100; /* guess CD quality */ 155 spec_.freq = 44100; /* guess CD quality */
66 156
67 // TODO supply output devices 157 // TODO supply output devices
68 stream_ = sdl3_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec_, nullptr, nullptr); 158 stream_ = sdl3_OpenAudioDeviceStream(GUIDtoDevID(p_device), &spec_, OutputSDL::AudioStreamCallback, this);
159 if (!stream_)
160 logf(logf_level::error, "%s", sdl3_GetError());
69 } 161 }
70 162
71 OutputSDL::~OutputSDL() 163 OutputSDL::~OutputSDL()
72 { 164 {
73 if (sdl_ && stream_ && sdl3_DestroyAudioStream) 165 if (!IsSDLLoaded())
74 sdl3_DestroyAudioStream(stream_); 166 return; /* nothing to do */
75 if (sdl_ && sdl3_QuitSubSystem) 167
76 sdl3_QuitSubSystem(SDL_INIT_AUDIO); 168 sdl3_DestroyAudioStream(stream_);
77 if (sdl_) 169 sdl3_QuitSubSystem(SDL_INIT_AUDIO);
78 FreeLibrary(sdl_); 170 UnloadSDL();
171 }
172
173 void OutputSDL::StreamCallback(int total_amount)
174 {
175 /* Don't need to do anything when additional_amount == 0 */
176 if (!total_amount) return;
177
178 needed_ += total_amount;
179 }
180
181 void SDLCALL OutputSDL::AudioStreamCallback(void *userdata, SDL_AudioStream *stream, int a, int t)
182 {
183 // Simply forwards to the main stream callback
184 OutputSDL *This = reinterpret_cast<OutputSDL *>(userdata);
185
186 // assert(This->stream_ == stream);
187
188 // Seems to work fine ?
189 This->StreamCallback(t);
79 } 190 }
80 191
81 void OutputSDL::ReinitStream(unsigned int channels, unsigned int freq) 192 void OutputSDL::ReinitStream(unsigned int channels, unsigned int freq)
82 { 193 {
83 // Fast path; no change 194 /* Fast path; no change
84 // I'm assuming that the channel input config is the same for fb2k 195 * This is the common case. Most music is 44.1khz and stereo. */
85 // and for SDL. This may not be the case. Whatever..
86 if (spec_.channels == channels && spec_.freq == freq) 196 if (spec_.channels == channels && spec_.freq == freq)
87 return; 197 return;
88 198
199 spec_.format = OUTSDL_FORMAT;
89 spec_.channels = channels; 200 spec_.channels = channels;
90 spec_.freq = freq; 201 spec_.freq = freq;
91 202
203 logf(logf_level::info, "SDL: setting freq and channels %d, %d", channels, freq);
204
92 // tell SDL about our change 205 // tell SDL about our change
93 sdl3_SetAudioStreamFormat(stream_, &spec_, nullptr); 206 if (!sdl3_SetAudioStreamFormat(stream_, &spec_, nullptr))
94 } 207 logf(logf_level::error, sdl3_GetError());
95 208 }
96 virtual double OutputSDL::get_latency() 209
97 { 210 double OutputSDL::get_latency()
98 // TODO assume that one buffer's worth is queued 211 {
99 return 0.016; // i guess 212 // ??? I don't know
100 } 213 return 0.016;
101 214 }
102 virtual void OutputSDL::process_samples(const audio_chunk &p_chunk) 215
216 void OutputSDL::process_samples(const audio_chunk &p_chunk)
103 { 217 {
104 // Reinitialize stream with possibly new values for channels and frequency 218 // Reinitialize stream with possibly new values for channels and frequency
105 ReinitStream(p_chunk.get_channels(), p_chunk.get_srate()); 219 ReinitStream(p_chunk.get_channels(), p_chunk.get_srate());
106 220
107 /* NOTE this is only actually tested with stereo */ 221 /* NOTE this is only actually tested with stereo */
108 #ifdef OUTSDL_USE_NATIVE_F32 222 #ifdef OUTSDL_USE_NATIVE_F32
223 t_size sz = p_chunk.get_data_size() * sizeof(float);
109 /* audio_sample is 32-bit floating point; SDL can use this directly */ 224 /* audio_sample is 32-bit floating point; SDL can use this directly */
110 sdl3_PutAudioStreamData(stream_, p_chunk.get_data(), p_chunk.get_data_size()); 225 if (!sdl3_PutAudioStreamData(stream_, p_chunk.get_data(), sz))
226 logf(logf_level::error, "SDL: %s", sdl3_GetError());
227 needed_ -= sz;
111 #else 228 #else
112 /* Expand the buffer if necessary */ 229 /* Expand the buffer if necessary */
113 t_size sz = p_chunk.get_data_size(); 230 t_size sz;
114 231
232 sz = p_chunk.get_data_size();
115 if (sz > buffer_.size()) 233 if (sz > buffer_.size())
116 buffer_.resize(sz); 234 buffer_.resize(sz);
117 235
118 /* Convert to int32 */ 236 float *buf = buffer_.data();
119 audio_math::convert_to_int32(p_chunk.get_data(), sz, buffer_.data(), buffer_.size()); 237
120 238 /* Convert to f32 */
121 /* Add int32 audio to stream */ 239 audio_math::convert(p_chunk.get_data(), buf, sz);
122 sdl3_PutAudioStreamData(stream_, buffer_.data(), buffer_.size()); 240
241 needed_ -= sz * sizeof(float);
242
243 /* Add f32 audio to stream */
244 while (sz > 0) {
245 /* no possible loss of data here; we cap size_t to int */
246 int to = static_cast<int>((std::min)(sz, INT_MIN / sizeof(float)));
247
248 sdl3_PutAudioStreamData(stream_, buffer_.data(), to * sizeof(float));
249
250 sz -= to;
251 buf += to;
252 }
123 #endif 253 #endif
124 } 254 }
125 255
126 virtual void OutputSDL::update(bool &p_ready) 256 void OutputSDL::update(bool &p_ready) { p_ready = update_v2() > 0; }
127 { 257
128 /* seems legit */ 258 size_t OutputSDL::update_v2()
129 p_ready = (sdl3_GetAudioStreamQueued(stream_) == 0); 259 {
130 } 260 return (std::max)(needed_.load(), 0) / (int)sizeof(float);
131 261 }
132 virtual void OutputSDL::pause(bool p_state) 262
133 { 263 void OutputSDL::pause(bool p_state)
264 {
265 logf(logf_level::info, "pause? %d", (int)p_state);
266 bool v;
134 if (p_state) { 267 if (p_state) {
135 sdl3_PauseAudioStreamDevice(stream_); 268 v = sdl3_PauseAudioStreamDevice(stream_);
136 } else { 269 } else {
137 sdl3_ResumeAudioStreamDevice(stream_); 270 v = sdl3_ResumeAudioStreamDevice(stream_);
138 } 271 }
272 if (!v) logf(logf_level::error, "pause: %s", sdl3_GetError());
139 } 273 }
140 274
141 // . these are easy 275 // . these are easy
142 virtual void OutputSDL::flush() 276 void OutputSDL::flush()
143 { 277 {
144 sdl3_ClearAudioStream(stream_); 278 if (!sdl3_ClearAudioStream(stream_))
145 } 279 logf(logf_level::error, "flush: %s", sdl3_GetError());
146 280 }
147 virtual void OutputSDL::force_play() 281
148 { 282 void OutputSDL::force_play()
149 sdl3_FlushAudioStream(stream_); 283 {
150 } 284 if (!sdl3_FlushAudioStream(stream_))
151 285 logf(logf_level::error, "force_play: %s", sdl3_GetError());
152 virtual void OutputSDL::volume_set(double p_val) 286 }
153 { 287
154 sdl3_SetAudioStreamGain(stream_, p_val); 288 void OutputSDL::volume_set(double p_val)
289 {
290 /* fb2k provides this as dB for some reason, so convert it back
291 * to linear which is what normal programs use. */
292 double p_linear = std::pow(10.0, p_val / 10.0);
293
294 if (!sdl3_SetAudioStreamGain(stream_, /* shut up msvc */static_cast<float>(p_linear)))
295 logf(logf_level::error, "volume_set: %s", sdl3_GetError());
296 }
297
298 /* conversion to/from GUID and SDL_AudioDeviceID */
299 GUID OutputSDL::DevIDtoGUID(SDL_AudioDeviceID id)
300 {
301 GUID g = {0};
302 g.Data1 = id;
303 return g;
304 }
305
306 SDL_AudioDeviceID OutputSDL::GUIDtoDevID(const GUID &g)
307 {
308 return g.Data1;
309 }
310
311 void OutputSDL::g_enum_devices(output_device_enum_callback& p_callback)
312 {
313 int i, count;
314
315 if (!LoadSDL())
316 return; /* uh oh */
317
318 SDL_AudioDeviceID *devs = sdl3_GetAudioPlaybackDevices(&count);
319
320 for (i = 0; i < count; i++) {
321 const char *name = sdl3_GetAudioDeviceName(devs[i]);
322 if (!name) continue;
323
324 p_callback.on_device(DevIDtoGUID(devs[i]), name, std::strlen(name));
325 }
326
327 // Add default device too for brevity
328 p_callback.on_device(DevIDtoGUID(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK), "default", 7);
329 }
330
331 bool OutputSDL::LoadSDL(void)
332 {
333 if (IsSDLLoaded())
334 return true;
335
336 // Unload any failed attempts
337 UnloadSDL();
338
339 sdl_ = LoadLibrary("SDL3.dll");
340 if (!sdl_) {
341 logf(logf_level::error, "Failed to load SDL3.dll!");
342 return false;
343 }
344
345 #define FUNC(type, x, params, callparams) \
346 sdl3_##x = (decltype(&SDL_##x))GetProcAddress(sdl_, "SDL_" #x);
347 #include "foo_out_sdl_funcs.h"
348
349 return IsSDLLoaded();
350 }
351
352 bool OutputSDL::IsSDLLoaded()
353 {
354 return (sdl_
355 #define FUNC(type, x, params, callparams) && (!!sdl3_##x)
356 #include "foo_out_sdl_funcs.h"
357 );
358 }
359
360 void OutputSDL::UnloadSDL()
361 {
362 // kill off everything
363 if (sdl_) {
364 FreeLibrary(sdl_);
365 sdl_ = nullptr;
366 }
367
368 #define FUNC(type, x, params, callparams) sdl3_##x = nullptr;
369 #include "foo_out_sdl_funcs.h"
370 }
371
372 bool OutputSDL::is_progressing()
373 {
374 return true; // ?
155 } 375 }
156 376
157 static output_factory_t<OutputSDL> g_output_sdl_factory; 377 static output_factory_t<OutputSDL> g_output_sdl_factory;
158 378
159 //////////////////////////////////////////////////////////////////////////////// 379 ////////////////////////////////////////////////////////////////////////////////