Mercurial > foo_out_sdl
diff 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 |
line wrap: on
line diff
--- a/foo_out_sdl.cc Sat Jan 03 23:52:56 2026 -0500 +++ b/foo_out_sdl.cc Mon Jan 05 02:15:46 2026 -0500 @@ -1,7 +1,15 @@ -/* SDL output for foobar2000. - * Somewhat scuffed since SDL does not provide latency */ +#include <algorithm> +#include <cstdlib> +#include <cstdio> +#include <cstdarg> +#include <cmath> +#include <atomic> -#include <SDL3/SDL.h> +#include "SDL3/SDL.h" + +#include "foobar2000/helpers/foobar2000+atl.h" +#include "foobar2000/SDK/core_api.h" +#include "foobar2000/SDK/output.h" #if audio_sample_size == 32 # define OUTSDL_USE_NATIVE_F32 1 @@ -10,9 +18,56 @@ # define OUTSDL_FORMAT SDL_AUDIO_S32 #endif -class OutputSDL : public output { +/* ----------------------------------------------------------------------------------------- */ +/* logf: printf-like interface for printing into the console + * a.k.a. "paper hates std::stringstream" + * doing it this way results in a smaller binary as well */ + +enum class logf_level { + info, + error, +}; + +static int vlogf(logf_level level, const char* format, std::va_list ap) +{ + char buf[1024]; + int r; + + r = std::vsnprintf(buf, sizeof(buf), format, ap); + if (r < 0) + return -1; + + switch (level) { + case logf_level::info: + console::info(buf); + break; + case logf_level::error: + console::error(buf); + break; + } + + return r; +} + +static int logf(logf_level level, const char* format, ...) +{ + std::va_list ap; + int r; + + va_start(ap, format); + + r = vlogf(level, format, ap); + + va_end(ap); + + return r; +} + +/* ----------------------------------------------------------------------------------------- */ + +class OutputSDL : public output_v4 { public: - OutputSDL(); + OutputSDL(const GUID& p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth); ~OutputSDL(); virtual double get_latency() override; @@ -22,42 +77,77 @@ virtual void flush() override; virtual void force_play() override; virtual void volume_set(double p_val) override; + virtual bool is_progressing() override; + virtual size_t update_v2() override; + + static GUID g_get_guid() { return { 0x90033ef5, 0x6703, 0x44a0, { 0x98, 0x47, 0x53, 0x99, 0x1, 0x53, 0x3b, 0xd6 } }; } + static void g_enum_devices(output_device_enum_callback &p_callback); + static bool g_advanced_settings_query() { return false; } + static bool g_needs_bitdepth_config() { return false; } + static bool g_needs_dither_config() { return false; } + static bool g_needs_device_list_prefixes() { return true; } + static bool g_supports_multiple_streams() { return false; } + static bool g_is_high_latency() { return true; } + static uint32_t g_extra_flags() { return 0; } + static void g_advanced_settings_popup(HWND p_parent, POINT p_menupoint) {} + static const char* g_get_name() { return "SDL"; } private: + // ---------- Static members -------------- + // These are for loading SDL, as well as + // converting to/from GUIDs. + static HMODULE sdl_; + + static GUID DevIDtoGUID(SDL_AudioDeviceID id); + static SDL_AudioDeviceID GUIDtoDevID(const GUID &id); + + static bool LoadSDL(); + static bool IsSDLLoaded(); + static void UnloadSDL(); + // ---------------------------------------- + + static void SDLCALL AudioStreamCallback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount); + + void StreamCallback(int amount); + void ReinitStream(unsigned int channels, unsigned int freq); + // the audio stream itself SDL_AudioStream *stream_; #ifndef OUTSDL_USE_NATIVE_F32 - std::vector<t_int32> buffer_; + // buffer to convert f64 -> f32 + std::vector<float> buffer_; #endif + // the current spec of the audio stream SDL_AudioSpec spec_; - HANDLE sdl_; -#define FUNC(type, x, params, callparams) decltype(&SDL_##x) sdl3_##x; + std::atomic<int> needed_; + +#define FUNC(type, x, params, callparams) static decltype(&SDL_##x) sdl3_##x; #include "foo_out_sdl_funcs.h" }; -OutputSDL::OutputSDL() - : sdl_(nullptr) - , stream_(nullptr) +HMODULE OutputSDL::sdl_ = nullptr; +#define FUNC(type, x, params, callparams) decltype(&SDL_##x) OutputSDL::sdl3_##x = nullptr; +#include "foo_out_sdl_funcs.h" + +OutputSDL::OutputSDL(const GUID &p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth) + : stream_(nullptr) #ifndef OUTSDL_USE_NATIVE_F32 /* sane default size */ , buffer_(8096) #endif { - sdl_ = LoadLibraryA("foo_out_sdl_wrapper.dll"); - if (!sdl3_) - return; + // uhhhh + needed_.store(0); -#define FUNC(type, x, params, callparams) \ - sdl3_##x = (decltype(&SDL_##x))GetProcAddress(sdl_, "SDL_" #x); \ - if (!sdl3_##x) \ - return; -#include "foo_out_sdl_funcs.h" + if (!LoadSDL()) + return; // uh oh // increment subsystem counter // hope this succeeds!!! - sdl3_InitSubSystem(SDL_INIT_AUDIO); + if (!sdl3_InitSubSystem(SDL_INIT_AUDIO)) + logf(logf_level::error, sdl3_GetError()); /* make a guess */ spec_.format = OUTSDL_FORMAT; @@ -65,93 +155,223 @@ spec_.freq = 44100; /* guess CD quality */ // TODO supply output devices - stream_ = sdl3_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec_, nullptr, nullptr); + stream_ = sdl3_OpenAudioDeviceStream(GUIDtoDevID(p_device), &spec_, OutputSDL::AudioStreamCallback, this); + if (!stream_) + logf(logf_level::error, "%s", sdl3_GetError()); } OutputSDL::~OutputSDL() { - if (sdl_ && stream_ && sdl3_DestroyAudioStream) - sdl3_DestroyAudioStream(stream_); - if (sdl_ && sdl3_QuitSubSystem) - sdl3_QuitSubSystem(SDL_INIT_AUDIO); - if (sdl_) - FreeLibrary(sdl_); + if (!IsSDLLoaded()) + return; /* nothing to do */ + + sdl3_DestroyAudioStream(stream_); + sdl3_QuitSubSystem(SDL_INIT_AUDIO); + UnloadSDL(); +} + +void OutputSDL::StreamCallback(int total_amount) +{ + /* Don't need to do anything when additional_amount == 0 */ + if (!total_amount) return; + + needed_ += total_amount; +} + +void SDLCALL OutputSDL::AudioStreamCallback(void *userdata, SDL_AudioStream *stream, int a, int t) +{ + // Simply forwards to the main stream callback + OutputSDL *This = reinterpret_cast<OutputSDL *>(userdata); + + // assert(This->stream_ == stream); + + // Seems to work fine ? + This->StreamCallback(t); } void OutputSDL::ReinitStream(unsigned int channels, unsigned int freq) { - // Fast path; no change - // I'm assuming that the channel input config is the same for fb2k - // and for SDL. This may not be the case. Whatever.. + /* Fast path; no change + * This is the common case. Most music is 44.1khz and stereo. */ if (spec_.channels == channels && spec_.freq == freq) return; + spec_.format = OUTSDL_FORMAT; spec_.channels = channels; spec_.freq = freq; + logf(logf_level::info, "SDL: setting freq and channels %d, %d", channels, freq); + // tell SDL about our change - sdl3_SetAudioStreamFormat(stream_, &spec_, nullptr); + if (!sdl3_SetAudioStreamFormat(stream_, &spec_, nullptr)) + logf(logf_level::error, sdl3_GetError()); } -virtual double OutputSDL::get_latency() +double OutputSDL::get_latency() { - // TODO assume that one buffer's worth is queued - return 0.016; // i guess + // ??? I don't know + return 0.016; } -virtual void OutputSDL::process_samples(const audio_chunk &p_chunk) +void OutputSDL::process_samples(const audio_chunk &p_chunk) { // Reinitialize stream with possibly new values for channels and frequency ReinitStream(p_chunk.get_channels(), p_chunk.get_srate()); /* NOTE this is only actually tested with stereo */ #ifdef OUTSDL_USE_NATIVE_F32 + t_size sz = p_chunk.get_data_size() * sizeof(float); /* audio_sample is 32-bit floating point; SDL can use this directly */ - sdl3_PutAudioStreamData(stream_, p_chunk.get_data(), p_chunk.get_data_size()); + if (!sdl3_PutAudioStreamData(stream_, p_chunk.get_data(), sz)) + logf(logf_level::error, "SDL: %s", sdl3_GetError()); + needed_ -= sz; #else /* Expand the buffer if necessary */ - t_size sz = p_chunk.get_data_size(); + t_size sz; + sz = p_chunk.get_data_size(); if (sz > buffer_.size()) buffer_.resize(sz); - /* Convert to int32 */ - audio_math::convert_to_int32(p_chunk.get_data(), sz, buffer_.data(), buffer_.size()); + float *buf = buffer_.data(); + + /* Convert to f32 */ + audio_math::convert(p_chunk.get_data(), buf, sz); + + needed_ -= sz * sizeof(float); - /* Add int32 audio to stream */ - sdl3_PutAudioStreamData(stream_, buffer_.data(), buffer_.size()); + /* Add f32 audio to stream */ + while (sz > 0) { + /* no possible loss of data here; we cap size_t to int */ + int to = static_cast<int>((std::min)(sz, INT_MIN / sizeof(float))); + + sdl3_PutAudioStreamData(stream_, buffer_.data(), to * sizeof(float)); + + sz -= to; + buf += to; + } #endif } -virtual void OutputSDL::update(bool &p_ready) +void OutputSDL::update(bool &p_ready) { p_ready = update_v2() > 0; } + +size_t OutputSDL::update_v2() { - /* seems legit */ - p_ready = (sdl3_GetAudioStreamQueued(stream_) == 0); + return (std::max)(needed_.load(), 0) / (int)sizeof(float); } -virtual void OutputSDL::pause(bool p_state) +void OutputSDL::pause(bool p_state) { + logf(logf_level::info, "pause? %d", (int)p_state); + bool v; if (p_state) { - sdl3_PauseAudioStreamDevice(stream_); + v = sdl3_PauseAudioStreamDevice(stream_); } else { - sdl3_ResumeAudioStreamDevice(stream_); + v = sdl3_ResumeAudioStreamDevice(stream_); } + if (!v) logf(logf_level::error, "pause: %s", sdl3_GetError()); } // . these are easy -virtual void OutputSDL::flush() +void OutputSDL::flush() +{ + if (!sdl3_ClearAudioStream(stream_)) + logf(logf_level::error, "flush: %s", sdl3_GetError()); +} + +void OutputSDL::force_play() +{ + if (!sdl3_FlushAudioStream(stream_)) + logf(logf_level::error, "force_play: %s", sdl3_GetError()); +} + +void OutputSDL::volume_set(double p_val) { - sdl3_ClearAudioStream(stream_); + /* fb2k provides this as dB for some reason, so convert it back + * to linear which is what normal programs use. */ + double p_linear = std::pow(10.0, p_val / 10.0); + + if (!sdl3_SetAudioStreamGain(stream_, /* shut up msvc */static_cast<float>(p_linear))) + logf(logf_level::error, "volume_set: %s", sdl3_GetError()); +} + +/* conversion to/from GUID and SDL_AudioDeviceID */ +GUID OutputSDL::DevIDtoGUID(SDL_AudioDeviceID id) +{ + GUID g = {0}; + g.Data1 = id; + return g; +} + +SDL_AudioDeviceID OutputSDL::GUIDtoDevID(const GUID &g) +{ + return g.Data1; } -virtual void OutputSDL::force_play() +void OutputSDL::g_enum_devices(output_device_enum_callback& p_callback) { - sdl3_FlushAudioStream(stream_); + int i, count; + + if (!LoadSDL()) + return; /* uh oh */ + + SDL_AudioDeviceID *devs = sdl3_GetAudioPlaybackDevices(&count); + + for (i = 0; i < count; i++) { + const char *name = sdl3_GetAudioDeviceName(devs[i]); + if (!name) continue; + + p_callback.on_device(DevIDtoGUID(devs[i]), name, std::strlen(name)); + } + + // Add default device too for brevity + p_callback.on_device(DevIDtoGUID(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK), "default", 7); } -virtual void OutputSDL::volume_set(double p_val) +bool OutputSDL::LoadSDL(void) { - sdl3_SetAudioStreamGain(stream_, p_val); + if (IsSDLLoaded()) + return true; + + // Unload any failed attempts + UnloadSDL(); + + sdl_ = LoadLibrary("SDL3.dll"); + if (!sdl_) { + logf(logf_level::error, "Failed to load SDL3.dll!"); + return false; + } + +#define FUNC(type, x, params, callparams) \ + sdl3_##x = (decltype(&SDL_##x))GetProcAddress(sdl_, "SDL_" #x); +#include "foo_out_sdl_funcs.h" + + return IsSDLLoaded(); +} + +bool OutputSDL::IsSDLLoaded() +{ + return (sdl_ +#define FUNC(type, x, params, callparams) && (!!sdl3_##x) +#include "foo_out_sdl_funcs.h" + ); +} + +void OutputSDL::UnloadSDL() +{ + // kill off everything + if (sdl_) { + FreeLibrary(sdl_); + sdl_ = nullptr; + } + +#define FUNC(type, x, params, callparams) sdl3_##x = nullptr; +#include "foo_out_sdl_funcs.h" +} + +bool OutputSDL::is_progressing() +{ + return true; // ? } static output_factory_t<OutputSDL> g_output_sdl_factory;
