[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/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")
|
||||
|
|
|
@ -23,6 +23,7 @@ project("xenia-app")
|
|||
"xenia-app-discord",
|
||||
"xenia-apu",
|
||||
"xenia-apu-nop",
|
||||
"xenia-apu-sdl",
|
||||
"xenia-base",
|
||||
"xenia-core",
|
||||
"xenia-cpu",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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