summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Linn <jl@conductive.de>2019-10-28 00:18:43 +0100
committerRick Gibbed <rick@gibbed.us>2020-02-10 14:01:47 -0600
commit160f218210a8ed68ca56cddaf080f8b6437878ce (patch)
treeed73b2bbaa82516b118baf10eac6fdb6212ec074
parent64f3925c7d244d545150eb44acb6dbc5cc66f974 (diff)
[APU/Linux] Implement cross platform audio using SDL2 library.
-rw-r--r--premake5.lua1
-rw-r--r--src/xenia/app/premake5.lua1
-rw-r--r--src/xenia/app/xenia_main.cc4
-rw-r--r--src/xenia/apu/sdl/premake5.lua29
-rw-r--r--src/xenia/apu/sdl/sdl_audio_driver.cc147
-rw-r--r--src/xenia/apu/sdl/sdl_audio_driver.h54
-rw-r--r--src/xenia/apu/sdl/sdl_audio_system.cc54
-rw-r--r--src/xenia/apu/sdl/sdl_audio_system.h40
8 files changed, 329 insertions, 1 deletions
diff --git a/premake5.lua b/premake5.lua
index e5158612d..91b8c2342 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -229,6 +229,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 e5fd4ba8f..a385245ac 100644
--- a/src/xenia/app/premake5.lua
+++ b/src/xenia/app/premake5.lua
@@ -23,6 +23,7 @@ project("xenia-app")
"xenia-app-discord",
"xenia-apu",
"xenia-apu-nop",
+ "xenia-apu-sdl",
"xenia-base",
"xenia-core",
"xenia-cpu",
diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc
index bcd76d553..623744651 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
@@ -45,7 +46,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, sdl, winkey, xinput]",
@@ -149,6 +150,7 @@ std::unique_ptr<apu::AudioSystem> CreateAudioSystem(cpu::Processor* processor) {
#if XE_PLATFORM_WIN32
factory.Add<apu::xaudio2::XAudio2AudioSystem>("xaudio2");
#endif // XE_PLATFORM_WIN32
+ factory.Add<apu::sdl::SDLAudioSystem>("sdl");
factory.Add<apu::nop::NopAudioSystem>("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..883d8b0c6
--- /dev/null
+++ b/src/xenia/apu/sdl/premake5.lua
@@ -0,0 +1,29 @@
+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-devel-VC/include/",
+ })
+ libdirs({
+ project_root.."/third_party/SDL2-devel-VC/lib/x64/",
+ })
+ -- Copy the dll to the output folder
+ postbuildcommands({
+ "{COPY} %{prj.basedir}/"..project_root.."/third_party/SDL2-devel-VC/lib/x64/SDL2.dll %{cfg.targetdir}",
+ })
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..12a7da021
--- /dev/null
+++ b/src/xenia/apu/sdl/sdl_audio_driver.cc
@@ -0,0 +1,147 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2020 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 <array>
+
+#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<SDLAudioDriver*>(userdata);
+
+ std::unique_lock<std::mutex> 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<float*>(frame_ptr);
+ float* output_frame;
+ {
+ std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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..404ceaa6a
--- /dev/null
+++ b/src/xenia/apu/sdl/sdl_audio_driver.h
@@ -0,0 +1,54 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2020 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 <SDL2/SDL.h>
+
+#include <mutex>
+#include <queue>
+#include <stack>
+
+#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<float*> frames_queued_ = {};
+ std::stack<float*> 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..12a4c4616
--- /dev/null
+++ b/src/xenia/apu/sdl/sdl_audio_system.cc
@@ -0,0 +1,54 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2020 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<AudioSystem> SDLAudioSystem::Create(cpu::Processor* processor) {
+ return std::make_unique<SDLAudioSystem>(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<SDLAudioDriver*>(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..9f9d3108f
--- /dev/null
+++ b/src/xenia/apu/sdl/sdl_audio_system.h
@@ -0,0 +1,40 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2020 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<AudioSystem> 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_