[APU/Linux] Implement cross platform audio using SDL2 library.
This commit is contained in:
parent
64f3925c7d
commit
160f218210
|
@ -229,6 +229,7 @@ solution("xenia")
|
||||||
include("src/xenia/app/discord")
|
include("src/xenia/app/discord")
|
||||||
include("src/xenia/apu")
|
include("src/xenia/apu")
|
||||||
include("src/xenia/apu/nop")
|
include("src/xenia/apu/nop")
|
||||||
|
include("src/xenia/apu/sdl")
|
||||||
include("src/xenia/base")
|
include("src/xenia/base")
|
||||||
include("src/xenia/cpu")
|
include("src/xenia/cpu")
|
||||||
include("src/xenia/cpu/backend/x64")
|
include("src/xenia/cpu/backend/x64")
|
||||||
|
|
|
@ -23,6 +23,7 @@ project("xenia-app")
|
||||||
"xenia-app-discord",
|
"xenia-app-discord",
|
||||||
"xenia-apu",
|
"xenia-apu",
|
||||||
"xenia-apu-nop",
|
"xenia-apu-nop",
|
||||||
|
"xenia-apu-sdl",
|
||||||
"xenia-base",
|
"xenia-base",
|
||||||
"xenia-core",
|
"xenia-core",
|
||||||
"xenia-cpu",
|
"xenia-cpu",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
// Available audio systems:
|
// Available audio systems:
|
||||||
#include "xenia/apu/nop/nop_audio_system.h"
|
#include "xenia/apu/nop/nop_audio_system.h"
|
||||||
|
#include "xenia/apu/sdl/sdl_audio_system.h"
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
#include "xenia/apu/xaudio2/xaudio2_audio_system.h"
|
#include "xenia/apu/xaudio2/xaudio2_audio_system.h"
|
||||||
#endif // XE_PLATFORM_WIN32
|
#endif // XE_PLATFORM_WIN32
|
||||||
|
@ -45,7 +46,7 @@
|
||||||
|
|
||||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
#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",
|
DEFINE_string(gpu, "any",
|
||||||
"Graphics system. Use: [any, d3d12, vulkan, vk, null]", "GPU");
|
"Graphics system. Use: [any, d3d12, vulkan, vk, null]", "GPU");
|
||||||
DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
|
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
|
#if XE_PLATFORM_WIN32
|
||||||
factory.Add<apu::xaudio2::XAudio2AudioSystem>("xaudio2");
|
factory.Add<apu::xaudio2::XAudio2AudioSystem>("xaudio2");
|
||||||
#endif // XE_PLATFORM_WIN32
|
#endif // XE_PLATFORM_WIN32
|
||||||
|
factory.Add<apu::sdl::SDLAudioSystem>("sdl");
|
||||||
factory.Add<apu::nop::NopAudioSystem>("nop");
|
factory.Add<apu::nop::NopAudioSystem>("nop");
|
||||||
return factory.Create(cvars::apu, processor);
|
return factory.Create(cvars::apu, processor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}",
|
||||||
|
})
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
Loading…
Reference in New Issue