[APU/Linux] Implement cross platform audio using SDL2 library.

This commit is contained in:
Joel Linn 2019-10-28 00:18:43 +01:00 committed by Rick Gibbed
parent 64f3925c7d
commit 160f218210
8 changed files with 329 additions and 1 deletions

View File

@ -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")

View File

@ -23,6 +23,7 @@ project("xenia-app")
"xenia-app-discord",
"xenia-apu",
"xenia-apu-nop",
"xenia-apu-sdl",
"xenia-base",
"xenia-core",
"xenia-cpu",

View File

@ -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);
}

View File

@ -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}",
})

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_