From 97035427757f3f7b65df7b2a30cb9b1bb46abc9d Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 20 Mar 2024 22:46:20 +1000 Subject: [PATCH] AudioStream: Re-add SDL backend --- src/core/host.cpp | 5 ++ src/core/settings.cpp | 6 ++ src/core/settings.h | 2 + src/core/types.h | 3 + src/util/CMakeLists.txt | 3 +- src/util/audio_stream.cpp | 34 ++++++--- src/util/audio_stream.h | 7 +- src/util/sdl_audio_stream.cpp | 136 ++++++++++++++++++++++++++++++++++ src/util/util.vcxproj | 1 + src/util/util.vcxproj.filters | 1 + 10 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 src/util/sdl_audio_stream.cpp diff --git a/src/core/host.cpp b/src/core/host.cpp index 87a555299..b21255cc7 100644 --- a/src/core/host.cpp +++ b/src/core/host.cpp @@ -365,6 +365,11 @@ std::unique_ptr Host::CreateAudioStream(AudioBackend backend, u32 s return AudioStream::CreateCubebAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch); #endif +#ifdef ENABLE_SDL2 + case AudioBackend::SDL: + return AudioStream::CreateSDLAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch); +#endif + #ifdef _WIN32 case AudioBackend::XAudio2: return AudioStream::CreateXAudio2Stream(sample_rate, channels, buffer_ms, latency_ms, stretch); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index da1079f8d..66ef2175b 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1559,6 +1559,9 @@ static constexpr const std::array s_audio_backend_names = { #ifdef ENABLE_CUBEB "Cubeb", #endif +#ifdef ENABLE_SDL2 + "SDL", +#endif #ifdef _WIN32 "XAudio2", #endif @@ -1571,6 +1574,9 @@ static constexpr const std::array s_audio_backend_display_names = { #ifdef ENABLE_CUBEB TRANSLATE_NOOP("AudioBackend", "Cubeb"), #endif +#ifdef ENABLE_SDL2 + TRANSLATE_NOOP("AudioBackend", "SDL"), +#endif #ifdef _WIN32 TRANSLATE_NOOP("AudioBackend", "XAudio2"), #endif diff --git a/src/core/settings.h b/src/core/settings.h index 4e8342b32..803ba2abe 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -484,6 +484,8 @@ struct Settings static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::XAudio2; #elif defined(__ANDROID__) static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::AAudio; +#elif defined(ENABLE_SDL2) + static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::SDL; #else static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Null; #endif diff --git a/src/core/types.h b/src/core/types.h index 57b45e744..a106bbfec 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -190,6 +190,9 @@ enum class AudioBackend : u8 #ifdef ENABLE_CUBEB Cubeb, #endif +#ifdef ENABLE_SDL2 + SDL, +#endif #ifdef _WIN32 XAudio2, #endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 5fc9263a1..88419d486 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -189,7 +189,8 @@ if(ENABLE_VULKAN) endif() if(ENABLE_SDL2) - target_sources(util PRIVATE + target_sources(util PRIVATE + sdl_audio_stream.cpp sdl_input_source.cpp sdl_input_source.h ) diff --git a/src/util/audio_stream.cpp b/src/util/audio_stream.cpp index fb57ba4a6..4a1806d8d 100644 --- a/src/util/audio_stream.cpp +++ b/src/util/audio_stream.cpp @@ -89,10 +89,10 @@ u32 AudioStream::GetBufferedFramesRelaxed() const return (wpos + m_buffer_size - rpos) % m_buffer_size; } -void AudioStream::ReadFrames(s16* bData, u32 nFrames) +void AudioStream::ReadFrames(s16* samples, u32 num_frames) { const u32 available_frames = GetBufferedFramesRelaxed(); - u32 frames_to_read = nFrames; + u32 frames_to_read = num_frames; u32 silence_frames = 0; if (m_filling) @@ -102,7 +102,7 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames) if (available_frames < toFill) { - silence_frames = nFrames; + silence_frames = num_frames; frames_to_read = 0; } else @@ -133,7 +133,7 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames) // towards the end of the buffer if (end > 0) { - std::memcpy(bData, &m_buffer[rpos], sizeof(s32) * end); + std::memcpy(samples, &m_buffer[rpos], sizeof(s32) * end); rpos += end; rpos = (rpos == m_buffer_size) ? 0 : rpos; } @@ -142,7 +142,7 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames) const u32 start = frames_to_read - end; if (start > 0) { - std::memcpy(&bData[end * 2], &m_buffer[0], sizeof(s32) * start); + std::memcpy(&samples[end * 2], &m_buffer[0], sizeof(s32) * start); rpos = start; } @@ -156,15 +156,15 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames) // super basic resampler - spread the input samples evenly across the output samples. will sound like ass and have // aliasing, but better than popping by inserting silence. const u32 increment = - static_cast(65536.0f * (static_cast(frames_to_read / m_channels) / static_cast(nFrames))); + static_cast(65536.0f * (static_cast(frames_to_read / m_channels) / static_cast(num_frames))); s16* resample_ptr = static_cast(alloca(sizeof(s16) * frames_to_read)); - std::memcpy(resample_ptr, bData, sizeof(s16) * frames_to_read); + std::memcpy(resample_ptr, samples, sizeof(s16) * frames_to_read); - s16* out_ptr = bData; + s16* out_ptr = samples; const u32 copy_stride = sizeof(SampleType) * m_channels; u32 resample_subpos = 0; - for (u32 i = 0; i < nFrames; i++) + for (u32 i = 0; i < num_frames; i++) { std::memcpy(out_ptr, resample_ptr, copy_stride); out_ptr += m_channels; @@ -174,16 +174,28 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames) resample_subpos %= 65536u; } - Log_VerbosePrintf("Audio buffer underflow, resampled %u frames to %u", frames_to_read, nFrames); + Log_VerbosePrintf("Audio buffer underflow, resampled %u frames to %u", frames_to_read, num_frames); } else { // no data, fall back to silence - std::memset(bData + frames_to_read, 0, sizeof(s32) * silence_frames); + std::memset(samples + frames_to_read, 0, sizeof(s32) * silence_frames); } } } +void AudioStream::ApplyVolume(s16* samples, u32 num_frames) +{ + if (m_volume == 100) + return; + + const s32 volume_mult = static_cast(m_volume) * 32768; + const u32 num_samples = num_frames * m_channels; + + while (num_samples > 0) + *samples = static_cast((static_cast(*samples) * volume_mult) >> 15); +} + void AudioStream::InternalWriteFrames(s32* bData, u32 nSamples) { const u32 free = m_buffer_size - GetBufferedFramesRelaxed(); diff --git a/src/util/audio_stream.h b/src/util/audio_stream.h index d38d80e46..76f2b7b7e 100644 --- a/src/util/audio_stream.h +++ b/src/util/audio_stream.h @@ -85,6 +85,10 @@ public: static std::vector GetCubebDriverNames(); static std::vector> GetCubebOutputDevices(const char* driver); #endif +#ifdef ENABLE_SDL2 + static std::unique_ptr CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms, + AudioStretchMode stretch); +#endif #ifdef _WIN32 static std::unique_ptr CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms, AudioStretchMode stretch); @@ -94,7 +98,8 @@ protected: AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch); void BaseInitialize(); - void ReadFrames(s16* bData, u32 nSamples); + void ReadFrames(s16* samples, u32 num_frames); + void ApplyVolume(s16* samples, u32 num_frames); u32 m_sample_rate = 0; u32 m_channels = 0; diff --git a/src/util/sdl_audio_stream.cpp b/src/util/sdl_audio_stream.cpp new file mode 100644 index 000000000..94206b312 --- /dev/null +++ b/src/util/sdl_audio_stream.cpp @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#include "audio_stream.h" + +#include "common/assert.h" +#include "common/log.h" + +#include + +Log_SetChannel(SDLAudioStream); + +namespace { +class SDLAudioStream final : public AudioStream +{ +public: + SDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch); + ~SDLAudioStream(); + + void SetPaused(bool paused) override; + void SetOutputVolume(u32 volume) override; + + bool OpenDevice(u32 latency_ms); + void CloseDevice(); + +protected: + ALWAYS_INLINE bool IsOpen() const { return (m_device_id != 0); } + + static void AudioCallback(void* userdata, uint8_t* stream, int len); + + u32 m_device_id = 0; +}; +} // namespace + +static bool InitializeSDLAudio() +{ + static bool initialized = false; + if (initialized) + return true; + + // May as well keep it alive until the process exits. + const int error = SDL_InitSubSystem(SDL_INIT_AUDIO); + if (error != 0) + { + Log_ErrorFmt("SDL_InitSubSystem(SDL_INIT_AUDIO) returned {}", error); + return false; + } + + std::atexit([]() { SDL_QuitSubSystem(SDL_INIT_AUDIO); }); + + initialized = true; + return true; +} + +SDLAudioStream::SDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch) + : AudioStream(sample_rate, channels, buffer_ms, stretch) +{ +} + +SDLAudioStream::~SDLAudioStream() +{ + if (IsOpen()) + SDLAudioStream::CloseDevice(); +} + +std::unique_ptr AudioStream::CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, + u32 latency_ms, AudioStretchMode stretch) +{ + if (!InitializeSDLAudio()) + return {}; + + std::unique_ptr stream = std::make_unique(sample_rate, channels, buffer_ms, stretch); + if (!stream->OpenDevice(latency_ms)) + stream.reset(); + + return stream; +} + +bool SDLAudioStream::OpenDevice(u32 latency_ms) +{ + DebugAssert(!IsOpen()); + + SDL_AudioSpec spec = {}; + spec.freq = m_sample_rate; + spec.channels = static_cast(m_channels); + spec.format = AUDIO_S16; + spec.samples = static_cast(GetBufferSizeForMS(m_sample_rate, (latency_ms == 0) ? m_buffer_ms : latency_ms)); + spec.callback = AudioCallback; + spec.userdata = static_cast(this); + + SDL_AudioSpec obtained_spec = {}; + m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + if (m_device_id == 0) + { + Log_ErrorFmt("SDL_OpenAudioDevice() failed: {}", SDL_GetError()); + return false; + } + + Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples); + + BaseInitialize(); + m_volume = 100; + m_paused = false; + SDL_PauseAudioDevice(m_device_id, 0); + + return true; +} + +void SDLAudioStream::SetPaused(bool paused) +{ + if (m_paused == paused) + return; + + SDL_PauseAudioDevice(m_device_id, paused ? 1 : 0); + m_paused = paused; +} + +void SDLAudioStream::CloseDevice() +{ + SDL_CloseAudioDevice(m_device_id); + m_device_id = 0; +} + +void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len) +{ + SDLAudioStream* const this_ptr = static_cast(userdata); + const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_channels; + + this_ptr->ReadFrames(reinterpret_cast(stream), num_frames); + this_ptr->ApplyVolume(reinterpret_cast(stream), num_frames); +} + +void SDLAudioStream::SetOutputVolume(u32 volume) +{ + m_volume = volume; +} diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index 8d2086b10..e3c184228 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -203,6 +203,7 @@ + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 8850b1df7..a34a2f656 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -156,6 +156,7 @@ +