From c7c99905f4ba5f3d7b824ec2bc6f46d755d46d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gallet?= Date: Sat, 5 Jun 2021 15:35:57 +0200 Subject: [PATCH 1/3] Add SDL2 audio backend --- externals/CMakeLists.txt | 4 +- src/audio_core/CMakeLists.txt | 5 + src/audio_core/sdl2_sink.cpp | 167 ++++++++++++++++++++++++++++++++ src/audio_core/sdl2_sink.h | 29 ++++++ src/audio_core/sink_details.cpp | 10 ++ 5 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/audio_core/sdl2_sink.cpp create mode 100644 src/audio_core/sdl2_sink.h diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index aae0baa0b..ec3c0432b 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -48,10 +48,10 @@ target_include_directories(unicorn-headers INTERFACE ./unicorn/include) # SDL2 if (NOT SDL2_FOUND AND ENABLE_SDL2) if (NOT WIN32) - # Yuzu itself needs: Events Joystick Haptic Sensor Timers + # Yuzu itself needs: Events Joystick Haptic Sensor Timers Audio # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) set(SDL_UNUSED_SUBSYSTEMS - Atomic Audio Render Power Threads + Atomic Render Power Threads File CPUinfo Filesystem Locale) foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) string(TOUPPER ${_SUB} _OPT) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index a0ae07752..d25a1a645 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(audio_core STATIC voice_context.h $<$:cubeb_sink.cpp cubeb_sink.h> + $<$:sdl2_sink.cpp sdl2_sink.h> ) create_target_directory_groups(audio_core) @@ -71,3 +72,7 @@ if(ENABLE_CUBEB) target_link_libraries(audio_core PRIVATE cubeb) target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1) endif() +if(ENABLE_SDL2) + target_link_libraries(audio_core PRIVATE SDL2) + target_compile_definitions(audio_core PRIVATE HAVE_SDL2) +endif() diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp new file mode 100644 index 000000000..75c6202ef --- /dev/null +++ b/src/audio_core/sdl2_sink.cpp @@ -0,0 +1,167 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "audio_core/sdl2_sink.h" +#include "audio_core/stream.h" +#include "audio_core/time_stretch.h" +#include "common/assert.h" +#include "common/logging/log.h" +//#include "common/settings.h" + +// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif +#include +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace AudioCore { + +class SDLSinkStream final : public SinkStream { +public: + SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device) + : num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} { + + SDL_AudioSpec spec; + spec.freq = sample_rate; + spec.channels = static_cast(num_channels); + spec.format = AUDIO_S16SYS; + spec.samples = 4096; + spec.callback = nullptr; + + SDL_AudioSpec obtained; + if (output_device.empty()) + dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0); + else + dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0); + + if (dev == 0) { + LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError()); + return; + } + + SDL_PauseAudioDevice(dev, 0); + } + + ~SDLSinkStream() override { + if (dev == 0) { + return; + } + + SDL_PauseAudioDevice(dev, 1); + SDL_CloseAudioDevice(dev); + } + + void EnqueueSamples(u32 source_num_channels, const std::vector& samples) override { + if (source_num_channels > num_channels) { + // Downsample 6 channels to 2 + ASSERT_MSG(source_num_channels == 6, "Channel count must be 6"); + + std::vector buf; + buf.reserve(samples.size() * num_channels / source_num_channels); + for (std::size_t i = 0; i < samples.size(); i += source_num_channels) { + // Downmixing implementation taken from the ATSC standard + const s16 left{samples[i + 0]}; + const s16 right{samples[i + 1]}; + const s16 center{samples[i + 2]}; + const s16 surround_left{samples[i + 4]}; + const s16 surround_right{samples[i + 5]}; + // Not used in the ATSC reference implementation + [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; + + constexpr s32 clev{707}; // center mixing level coefficient + constexpr s32 slev{707}; // surround mixing level coefficient + + buf.push_back(static_cast(left + (clev * center / 1000) + + (slev * surround_left / 1000))); + buf.push_back(static_cast(right + (clev * center / 1000) + + (slev * surround_right / 1000))); + } + int ret = SDL_QueueAudio(dev, static_cast(buf.data()), + static_cast(buf.size() * sizeof(s16))); + if (ret < 0) + LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); + return; + } + + int ret = SDL_QueueAudio(dev, static_cast(samples.data()), + static_cast(samples.size() * sizeof(s16))); + if (ret < 0) + LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); + } + + std::size_t SamplesInQueue(u32 channel_count) const override { + if (dev == 0) + return 0; + + return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16)); + } + + void Flush() override { + should_flush = true; + } + + u32 GetNumChannels() const { + return num_channels; + } + +private: + SDL_AudioDeviceID dev = 0; + u32 num_channels{}; + std::atomic should_flush{}; + TimeStretcher time_stretch; +}; + +SDLSink::SDLSink(std::string_view target_device_name) { + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); + return; + } + } + + if (target_device_name != auto_device_name && !target_device_name.empty()) { + output_device = target_device_name; + } else { + output_device.clear(); + } +} + +SDLSink::~SDLSink() { + for (auto& sink_stream : sink_streams) { + sink_stream.reset(); + } +} + +SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) { + sink_streams.push_back( + std::make_unique(sample_rate, num_channels, output_device)); + return *sink_streams.back(); +} + +std::vector ListSDLSinkDevices() { + std::vector device_list; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); + return std::vector(); + } + } + + int device_count = SDL_GetNumAudioDevices(0); + for (int i = 0; i < device_count; ++i) { + device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); + } + + return device_list; +} + +} // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h new file mode 100644 index 000000000..8ec1526d8 --- /dev/null +++ b/src/audio_core/sdl2_sink.h @@ -0,0 +1,29 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "audio_core/sink.h" + +namespace AudioCore { + +class SDLSink final : public Sink { +public: + explicit SDLSink(std::string_view device_id); + ~SDLSink() override; + + SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, + const std::string& name) override; + +private: + std::string output_device; + std::vector sink_streams; +}; + +std::vector ListSDLSinkDevices(); + +} // namespace AudioCore diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index a848eb1c9..de10aecd2 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -11,6 +11,9 @@ #ifdef HAVE_CUBEB #include "audio_core/cubeb_sink.h" #endif +#ifdef HAVE_SDL2 +#include "audio_core/sdl2_sink.h" +#endif #include "common/logging/log.h" namespace AudioCore { @@ -35,6 +38,13 @@ constexpr SinkDetails sink_details[] = { return std::make_unique(device_id); }, &ListCubebSinkDevices}, +#endif +#ifdef HAVE_SDL2 + SinkDetails{"sdl2", + [](std::string_view device_id) -> std::unique_ptr { + return std::make_unique(device_id); + }, + &ListSDLSinkDevices}, #endif SinkDetails{"null", [](std::string_view device_id) -> std::unique_ptr { From df8a2e3ad8548a6ba71c299dd25ffd1a6c5a873a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gallet?= Date: Sun, 6 Jun 2021 11:22:22 +0200 Subject: [PATCH 2/3] Add sdl2 audio description in the yuzu-cmd config file --- src/yuzu_cmd/default_ini.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 4ce8e08e4..095078c79 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -236,7 +236,8 @@ swap_screen = [Audio] # Which audio output engine to use. -# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available) +# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available), +# sdl2: SDL2 audio engine (if available) output_engine = # Whether or not to enable the audio-stretching post-processing effect. From f611506dca7004cd86086f0e22acd5a55f0ca25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Gallet?= Date: Mon, 7 Jun 2021 12:51:59 +0200 Subject: [PATCH 3/3] Various suggestions by v1993 and lioncash --- src/audio_core/sdl2_sink.cpp | 16 ++++++---------- src/yuzu_cmd/default_ini.h | 4 +++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp index 75c6202ef..62d3716a6 100644 --- a/src/audio_core/sdl2_sink.cpp +++ b/src/audio_core/sdl2_sink.cpp @@ -37,10 +37,11 @@ public: spec.callback = nullptr; SDL_AudioSpec obtained; - if (output_device.empty()) + if (output_device.empty()) { dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0); - else + } else { dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0); + } if (dev == 0) { LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError()); @@ -55,7 +56,6 @@ public: return; } - SDL_PauseAudioDevice(dev, 1); SDL_CloseAudioDevice(dev); } @@ -134,11 +134,7 @@ SDLSink::SDLSink(std::string_view target_device_name) { } } -SDLSink::~SDLSink() { - for (auto& sink_stream : sink_streams) { - sink_stream.reset(); - } -} +SDLSink::~SDLSink() = default; SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) { sink_streams.push_back( @@ -152,11 +148,11 @@ std::vector ListSDLSinkDevices() { if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); - return std::vector(); + return {}; } } - int device_count = SDL_GetNumAudioDevices(0); + const int device_count = SDL_GetNumAudioDevices(0); for (int i = 0; i < device_count; ++i) { device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); } diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 095078c79..6b673b935 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -236,8 +236,10 @@ swap_screen = [Audio] # Which audio output engine to use. -# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available), +# auto (default): Auto-select +# cubeb: Cubeb audio engine (if available) # sdl2: SDL2 audio engine (if available) +# null: No audio output output_engine = # Whether or not to enable the audio-stretching post-processing effect.