diff --git a/premake5.lua b/premake5.lua index 212ffc9e1..c3413c776 100644 --- a/premake5.lua +++ b/premake5.lua @@ -238,6 +238,7 @@ solution("xenia") include("src/xenia/app/discord") include("src/xenia/apu") include("src/xenia/apu/nop") + include("src/xenia/apu/sdl") include("src/xenia/base") include("src/xenia/cpu") include("src/xenia/cpu/backend/x64") diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 3b9b79486..f7ff54fb0 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -24,6 +24,7 @@ project("xenia-app") "xenia-app-discord", "xenia-apu", "xenia-apu-nop", + "xenia-apu-sdl", "xenia-base", "xenia-core", "xenia-cpu", @@ -73,10 +74,12 @@ project("xenia-app") "X11-xcb", "GL", "vulkan", + "SDL2", }) filter("platforms:Windows") links({ + "delayimp", -- Enable dll delayed loading for msvc "xenia-apu-xaudio2", "xenia-gpu-d3d12", "xenia-hid-winkey", @@ -84,6 +87,11 @@ project("xenia-app") "xenia-ui-d3d12", }) + filter("platforms:Windows") + linkoptions({ + "/DELAYLOAD:SDL2.dll", -- SDL is not mandatory since on windows, XAudio2 is available for sound + }) + filter("platforms:Windows") -- Only create the .user file if it doesn't already exist. local user_file = project_root.."/build/xenia-app.vcxproj.user" diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 1b6e1017b..d0f9984fb 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -23,6 +23,7 @@ // Available audio systems: #include "xenia/apu/nop/nop_audio_system.h" +#include "xenia/apu/sdl/sdl_audio_system.h" #if XE_PLATFORM_WIN32 #include "xenia/apu/xaudio2/xaudio2_audio_system.h" #endif // XE_PLATFORM_WIN32 @@ -44,7 +45,7 @@ #include "third_party/xbyak/xbyak/xbyak_util.h" -DEFINE_string(apu, "any", "Audio system. Use: [any, nop, xaudio2]", "APU"); +DEFINE_string(apu, "any", "Audio system. Use: [any, nop, sdl, xaudio2]", "APU"); DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, vk, null]", "GPU"); DEFINE_string(hid, "any", "Input system. Use: [any, nop, winkey, xinput]", @@ -148,6 +149,7 @@ std::unique_ptr CreateAudioSystem(cpu::Processor* processor) { #if XE_PLATFORM_WIN32 factory.Add("xaudio2"); #endif // XE_PLATFORM_WIN32 + factory.Add("sdl"); factory.Add("nop"); return factory.Create(cvars::apu, processor); } diff --git a/src/xenia/apu/sdl/premake5.lua b/src/xenia/apu/sdl/premake5.lua new file mode 100644 index 000000000..11e6669c7 --- /dev/null +++ b/src/xenia/apu/sdl/premake5.lua @@ -0,0 +1,30 @@ +project_root = "../../../.." +include(project_root.."/tools/build") + +group("src") +project("xenia-apu-sdl") + uuid("153b4e8b-813a-40e6-9366-4b51abc73c45") + kind("StaticLib") + language("C++") + links({ + "xenia-base", + "xenia-apu", + "SDL2", + }) + defines({ + }) + local_platform_files() + + filter("platforms:Windows") + -- On linux we build against the system version (libsdl2-dev) + includedirs({ + project_root.."/third_party/SDL2/include/", + }) + libdirs({ + project_root.."/third_party/SDL2/lib/x64/", + }) + -- Copy the dll to the output folder + postbuildcommands({ + "{COPY} %{prj.basedir}/"..project_root.."/third_party/SDL2/lib/x64/SDL2.dll %{cfg.targetdir}", + }) + \ No newline at end of file diff --git a/src/xenia/apu/sdl/sdl_audio_driver.cc b/src/xenia/apu/sdl/sdl_audio_driver.cc new file mode 100644 index 000000000..50be90c79 --- /dev/null +++ b/src/xenia/apu/sdl/sdl_audio_driver.cc @@ -0,0 +1,147 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2019 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/apu/sdl/sdl_audio_driver.h" + +#include + +#include "xenia/apu/apu_flags.h" +#include "xenia/base/logging.h" +#if XE_PLATFORM_WIN32 +#include "xenia/base/platform_win.h" +#endif // XE_PLATFORM_WIN32 + +namespace xe { +namespace apu { +namespace sdl { + +SDLAudioDriver::SDLAudioDriver(Memory* memory, + xe::threading::Semaphore* semaphore) + : AudioDriver(memory), semaphore_(semaphore) {} + +SDLAudioDriver::~SDLAudioDriver() { + assert_true(frames_queued_.empty()); + assert_true(frames_unused_.empty()); +}; + +bool SDLAudioDriver::Initialize() { + // With msvc delayed loading, exceptions are used to determine dll presence. +#if XE_PLATFORM_WIN32 + __try { +#endif // XE_PLATFORM_WIN32 + SDL_version ver = {}; + SDL_GetVersion(&ver); + if ((ver.major < 2) || + (ver.major == 2 && ver.minor == 0 && ver.patch < 8)) { + XELOGW( + "SDL library version %d.%d.%d is outdated. " + "You may experience choppy audio.", + ver.major, ver.minor, ver.patch); + } +#if XE_PLATFORM_WIN32 + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } +#endif // XE_PLATFORM_WIN32 + + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + return false; + } + sdl_initialized_ = true; + + SDL_AudioCallback audio_callback = [](void* userdata, Uint8* stream, + int len) -> void { + assert_true(len == frame_size_); + const auto driver = static_cast(userdata); + + std::unique_lock guard(driver->frames_mutex_); + if (driver->frames_queued_.empty()) { + memset(stream, 0, len); + } else { + auto buffer = driver->frames_queued_.front(); + driver->frames_queued_.pop(); + if (cvars::mute) { + memset(stream, 0, len); + } else { + memcpy(stream, buffer, len); + } + driver->frames_unused_.push(buffer); + + auto ret = driver->semaphore_->Release(1, nullptr); + assert_true(ret); + } + }; + + SDL_AudioSpec wanted_spec = {}; + wanted_spec.freq = frame_frequency_; + wanted_spec.format = AUDIO_F32; + wanted_spec.channels = frame_channels_; + wanted_spec.samples = channel_samples_; + wanted_spec.callback = audio_callback; + wanted_spec.userdata = this; + sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &wanted_spec, nullptr, 0); + if (sdl_device_id_ <= 0) { + XELOGE("SDL_OpenAudioDevice() failed."); + return false; + } + SDL_PauseAudioDevice(sdl_device_id_, 0); + + return true; +} + +void SDLAudioDriver::SubmitFrame(uint32_t frame_ptr) { + const auto input_frame = memory_->TranslateVirtual(frame_ptr); + float* output_frame; + { + std::unique_lock guard(frames_mutex_); + if (frames_unused_.empty()) { + output_frame = new float[frame_samples_]; + } else { + output_frame = frames_unused_.top(); + frames_unused_.pop(); + } + } + + // interleave the data + for (size_t index = 0, o = 0; index < channel_samples_; ++index) { + for (size_t channel = 0, table = 0; channel < frame_channels_; + ++channel, table += channel_samples_) { + output_frame[o++] = xe::byte_swap(input_frame[table + index]); + } + } + + { + std::unique_lock guard(frames_mutex_); + frames_queued_.push(output_frame); + } +} + +void SDLAudioDriver::Shutdown() { + if (sdl_device_id_ > 0) { + SDL_CloseAudioDevice(sdl_device_id_); + sdl_device_id_ = -1; + } + if (sdl_initialized_) { + SDL_QuitSubSystem(SDL_INIT_AUDIO); + sdl_initialized_ = false; + } + std::unique_lock guard(frames_mutex_); + while (!frames_unused_.empty()) { + delete[] frames_unused_.top(); + frames_unused_.pop(); + }; + while (!frames_queued_.empty()) { + delete[] frames_queued_.front(); + frames_queued_.pop(); + }; +} + +} // namespace sdl +} // namespace apu +} // namespace xe diff --git a/src/xenia/apu/sdl/sdl_audio_driver.h b/src/xenia/apu/sdl/sdl_audio_driver.h new file mode 100644 index 000000000..559db68ad --- /dev/null +++ b/src/xenia/apu/sdl/sdl_audio_driver.h @@ -0,0 +1,54 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2019 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_APU_SDL_SDL_AUDIO_DRIVER_H_ +#define XENIA_APU_SDL_SDL_AUDIO_DRIVER_H_ +#include + +#include +#include +#include + +#include "xenia/apu/audio_driver.h" +#include "xenia/base/threading.h" + +namespace xe { +namespace apu { +namespace sdl { + +class SDLAudioDriver : public AudioDriver { + public: + SDLAudioDriver(Memory* memory, xe::threading::Semaphore* semaphore); + ~SDLAudioDriver() override; + + bool Initialize(); + void SubmitFrame(uint32_t frame_ptr) override; + void Shutdown(); + + protected: + xe::threading::Semaphore* semaphore_ = nullptr; + + SDL_AudioDeviceID sdl_device_id_ = -1; + bool sdl_initialized_ = false; + + static const uint32_t frame_frequency_ = 48000; + static const uint32_t frame_channels_ = 6; + static const uint32_t channel_samples_ = 256; + static const uint32_t frame_samples_ = frame_channels_ * channel_samples_; + static const uint32_t frame_size_ = sizeof(float) * frame_samples_; + std::queue frames_queued_ = {}; + std::stack frames_unused_ = {}; + std::mutex frames_mutex_ = {}; +}; + +} // namespace sdl +} // namespace apu +} // namespace xe + +#endif // XENIA_APU_SDL_SDL_AUDIO_DRIVER_H_ diff --git a/src/xenia/apu/sdl/sdl_audio_system.cc b/src/xenia/apu/sdl/sdl_audio_system.cc new file mode 100644 index 000000000..1b98ffaf4 --- /dev/null +++ b/src/xenia/apu/sdl/sdl_audio_system.cc @@ -0,0 +1,54 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2019 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/apu/sdl/sdl_audio_system.h" + +#include "xenia/apu/apu_flags.h" +#include "xenia/apu/sdl/sdl_audio_driver.h" + +namespace xe { +namespace apu { +namespace sdl { + +std::unique_ptr SDLAudioSystem::Create(cpu::Processor* processor) { + return std::make_unique(processor); +} + +SDLAudioSystem::SDLAudioSystem(cpu::Processor* processor) + : AudioSystem(processor) {} + +SDLAudioSystem::~SDLAudioSystem() {} + +void SDLAudioSystem::Initialize() { AudioSystem::Initialize(); } + +X_STATUS SDLAudioSystem::CreateDriver(size_t index, + xe::threading::Semaphore* semaphore, + AudioDriver** out_driver) { + assert_not_null(out_driver); + auto driver = new SDLAudioDriver(memory_, semaphore); + if (!driver->Initialize()) { + driver->Shutdown(); + return X_STATUS_UNSUCCESSFUL; + } + + *out_driver = driver; + return X_STATUS_SUCCESS; +} + +void SDLAudioSystem::DestroyDriver(AudioDriver* driver) { + assert_not_null(driver); + auto sdldriver = dynamic_cast(driver); + assert_not_null(sdldriver); + sdldriver->Shutdown(); + delete sdldriver; +} + +} // namespace sdl +} // namespace apu +} // namespace xe diff --git a/src/xenia/apu/sdl/sdl_audio_system.h b/src/xenia/apu/sdl/sdl_audio_system.h new file mode 100644 index 000000000..5dee2e021 --- /dev/null +++ b/src/xenia/apu/sdl/sdl_audio_system.h @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2019 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_APU_SDL_SDL_AUDIO_SYSTEM_H_ +#define XENIA_APU_SDL_SDL_AUDIO_SYSTEM_H_ + +#include "xenia/apu/audio_system.h" + +namespace xe { +namespace apu { +namespace sdl { + +class SDLAudioSystem : public AudioSystem { + public: + explicit SDLAudioSystem(cpu::Processor* processor); + ~SDLAudioSystem() override; + + static bool IsAvailable() { return true; } + + static std::unique_ptr Create(cpu::Processor* processor); + + X_RESULT CreateDriver(size_t index, xe::threading::Semaphore* semaphore, + AudioDriver** out_driver) override; + void DestroyDriver(AudioDriver* driver) override; + + protected: + void Initialize() override; +}; + +} // namespace sdl +} // namespace apu +} // namespace xe + +#endif // XENIA_APU_SDL_SDL_AUDIO_SYSTEM_H_