AudioStream: Re-add SDL backend
This commit is contained in:
parent
e70f0e1bc1
commit
9703542775
|
@ -365,6 +365,11 @@ std::unique_ptr<AudioStream> Host::CreateAudioStream(AudioBackend backend, u32 s
|
||||||
return AudioStream::CreateCubebAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
return AudioStream::CreateCubebAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_SDL2
|
||||||
|
case AudioBackend::SDL:
|
||||||
|
return AudioStream::CreateSDLAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
case AudioBackend::XAudio2:
|
case AudioBackend::XAudio2:
|
||||||
return AudioStream::CreateXAudio2Stream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
return AudioStream::CreateXAudio2Stream(sample_rate, channels, buffer_ms, latency_ms, stretch);
|
||||||
|
|
|
@ -1559,6 +1559,9 @@ static constexpr const std::array s_audio_backend_names = {
|
||||||
#ifdef ENABLE_CUBEB
|
#ifdef ENABLE_CUBEB
|
||||||
"Cubeb",
|
"Cubeb",
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_SDL2
|
||||||
|
"SDL",
|
||||||
|
#endif
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
"XAudio2",
|
"XAudio2",
|
||||||
#endif
|
#endif
|
||||||
|
@ -1571,6 +1574,9 @@ static constexpr const std::array s_audio_backend_display_names = {
|
||||||
#ifdef ENABLE_CUBEB
|
#ifdef ENABLE_CUBEB
|
||||||
TRANSLATE_NOOP("AudioBackend", "Cubeb"),
|
TRANSLATE_NOOP("AudioBackend", "Cubeb"),
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_SDL2
|
||||||
|
TRANSLATE_NOOP("AudioBackend", "SDL"),
|
||||||
|
#endif
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
TRANSLATE_NOOP("AudioBackend", "XAudio2"),
|
TRANSLATE_NOOP("AudioBackend", "XAudio2"),
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -484,6 +484,8 @@ struct Settings
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::XAudio2;
|
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::XAudio2;
|
||||||
#elif defined(__ANDROID__)
|
#elif defined(__ANDROID__)
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::AAudio;
|
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::AAudio;
|
||||||
|
#elif defined(ENABLE_SDL2)
|
||||||
|
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::SDL;
|
||||||
#else
|
#else
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Null;
|
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Null;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -190,6 +190,9 @@ enum class AudioBackend : u8
|
||||||
#ifdef ENABLE_CUBEB
|
#ifdef ENABLE_CUBEB
|
||||||
Cubeb,
|
Cubeb,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_SDL2
|
||||||
|
SDL,
|
||||||
|
#endif
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
XAudio2,
|
XAudio2,
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -189,7 +189,8 @@ if(ENABLE_VULKAN)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_SDL2)
|
if(ENABLE_SDL2)
|
||||||
target_sources(util PRIVATE
|
target_sources(util PRIVATE
|
||||||
|
sdl_audio_stream.cpp
|
||||||
sdl_input_source.cpp
|
sdl_input_source.cpp
|
||||||
sdl_input_source.h
|
sdl_input_source.h
|
||||||
)
|
)
|
||||||
|
|
|
@ -89,10 +89,10 @@ u32 AudioStream::GetBufferedFramesRelaxed() const
|
||||||
return (wpos + m_buffer_size - rpos) % m_buffer_size;
|
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();
|
const u32 available_frames = GetBufferedFramesRelaxed();
|
||||||
u32 frames_to_read = nFrames;
|
u32 frames_to_read = num_frames;
|
||||||
u32 silence_frames = 0;
|
u32 silence_frames = 0;
|
||||||
|
|
||||||
if (m_filling)
|
if (m_filling)
|
||||||
|
@ -102,7 +102,7 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames)
|
||||||
|
|
||||||
if (available_frames < toFill)
|
if (available_frames < toFill)
|
||||||
{
|
{
|
||||||
silence_frames = nFrames;
|
silence_frames = num_frames;
|
||||||
frames_to_read = 0;
|
frames_to_read = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -133,7 +133,7 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames)
|
||||||
// towards the end of the buffer
|
// towards the end of the buffer
|
||||||
if (end > 0)
|
if (end > 0)
|
||||||
{
|
{
|
||||||
std::memcpy(bData, &m_buffer[rpos], sizeof(s32) * end);
|
std::memcpy(samples, &m_buffer[rpos], sizeof(s32) * end);
|
||||||
rpos += end;
|
rpos += end;
|
||||||
rpos = (rpos == m_buffer_size) ? 0 : rpos;
|
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;
|
const u32 start = frames_to_read - end;
|
||||||
if (start > 0)
|
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;
|
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
|
// 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.
|
// aliasing, but better than popping by inserting silence.
|
||||||
const u32 increment =
|
const u32 increment =
|
||||||
static_cast<u32>(65536.0f * (static_cast<float>(frames_to_read / m_channels) / static_cast<float>(nFrames)));
|
static_cast<u32>(65536.0f * (static_cast<float>(frames_to_read / m_channels) / static_cast<float>(num_frames)));
|
||||||
|
|
||||||
s16* resample_ptr = static_cast<s16*>(alloca(sizeof(s16) * frames_to_read));
|
s16* resample_ptr = static_cast<s16*>(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;
|
const u32 copy_stride = sizeof(SampleType) * m_channels;
|
||||||
u32 resample_subpos = 0;
|
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);
|
std::memcpy(out_ptr, resample_ptr, copy_stride);
|
||||||
out_ptr += m_channels;
|
out_ptr += m_channels;
|
||||||
|
@ -174,16 +174,28 @@ void AudioStream::ReadFrames(s16* bData, u32 nFrames)
|
||||||
resample_subpos %= 65536u;
|
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
|
else
|
||||||
{
|
{
|
||||||
// no data, fall back to silence
|
// 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<s32>(m_volume) * 32768;
|
||||||
|
const u32 num_samples = num_frames * m_channels;
|
||||||
|
|
||||||
|
while (num_samples > 0)
|
||||||
|
*samples = static_cast<s16>((static_cast<s16>(*samples) * volume_mult) >> 15);
|
||||||
|
}
|
||||||
|
|
||||||
void AudioStream::InternalWriteFrames(s32* bData, u32 nSamples)
|
void AudioStream::InternalWriteFrames(s32* bData, u32 nSamples)
|
||||||
{
|
{
|
||||||
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
||||||
|
|
|
@ -85,6 +85,10 @@ public:
|
||||||
static std::vector<std::string> GetCubebDriverNames();
|
static std::vector<std::string> GetCubebDriverNames();
|
||||||
static std::vector<std::pair<std::string, std::string>> GetCubebOutputDevices(const char* driver);
|
static std::vector<std::pair<std::string, std::string>> GetCubebOutputDevices(const char* driver);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_SDL2
|
||||||
|
static std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
|
||||||
|
AudioStretchMode stretch);
|
||||||
|
#endif
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
|
static std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
|
||||||
AudioStretchMode stretch);
|
AudioStretchMode stretch);
|
||||||
|
@ -94,7 +98,8 @@ protected:
|
||||||
AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
|
AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
|
||||||
void BaseInitialize();
|
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_sample_rate = 0;
|
||||||
u32 m_channels = 0;
|
u32 m_channels = 0;
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// 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 <SDL.h>
|
||||||
|
|
||||||
|
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> AudioStream::CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
|
||||||
|
u32 latency_ms, AudioStretchMode stretch)
|
||||||
|
{
|
||||||
|
if (!InitializeSDLAudio())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::unique_ptr<SDLAudioStream> stream = std::make_unique<SDLAudioStream>(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<Uint8>(m_channels);
|
||||||
|
spec.format = AUDIO_S16;
|
||||||
|
spec.samples = static_cast<Uint16>(GetBufferSizeForMS(m_sample_rate, (latency_ms == 0) ? m_buffer_ms : latency_ms));
|
||||||
|
spec.callback = AudioCallback;
|
||||||
|
spec.userdata = static_cast<void*>(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<SDLAudioStream*>(userdata);
|
||||||
|
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_channels;
|
||||||
|
|
||||||
|
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames);
|
||||||
|
this_ptr->ApplyVolume(reinterpret_cast<SampleType*>(stream), num_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLAudioStream::SetOutputVolume(u32 volume)
|
||||||
|
{
|
||||||
|
m_volume = volume;
|
||||||
|
}
|
|
@ -203,6 +203,7 @@
|
||||||
<ClCompile Include="postprocessing_shader.cpp" />
|
<ClCompile Include="postprocessing_shader.cpp" />
|
||||||
<ClCompile Include="postprocessing_shader_fx.cpp" />
|
<ClCompile Include="postprocessing_shader_fx.cpp" />
|
||||||
<ClCompile Include="postprocessing_shader_glsl.cpp" />
|
<ClCompile Include="postprocessing_shader_glsl.cpp" />
|
||||||
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
<ClCompile Include="sdl_input_source.cpp" />
|
<ClCompile Include="sdl_input_source.cpp" />
|
||||||
<ClCompile Include="shadergen.cpp" />
|
<ClCompile Include="shadergen.cpp" />
|
||||||
<ClCompile Include="shiftjis.cpp" />
|
<ClCompile Include="shiftjis.cpp" />
|
||||||
|
|
|
@ -156,6 +156,7 @@
|
||||||
<ClCompile Include="opengl_context_egl_x11.cpp" />
|
<ClCompile Include="opengl_context_egl_x11.cpp" />
|
||||||
<ClCompile Include="opengl_context_wgl.cpp" />
|
<ClCompile Include="opengl_context_wgl.cpp" />
|
||||||
<ClCompile Include="image.cpp" />
|
<ClCompile Include="image.cpp" />
|
||||||
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="metal_shaders.metal" />
|
<None Include="metal_shaders.metal" />
|
||||||
|
|
Loading…
Reference in New Issue