[APU] Add ALSA apu on Linux

This commit is contained in:
Herman S.
2026-02-17 23:46:16 +09:00
parent 48c13be2d5
commit fade0cd889
9 changed files with 929 additions and 6 deletions
+4
View File
@@ -371,6 +371,10 @@ workspace("xenia")
include("src/xenia/hid/sdl")
end
if os.istarget("linux") then
include("src/xenia/apu/alsa")
end
if os.istarget("windows") then
include("src/xenia/apu/xaudio2")
include("src/xenia/gpu/d3d12")
+1
View File
@@ -101,6 +101,7 @@ project("xenia-app")
filter("platforms:Linux")
links({
"xenia-apu-alsa",
"X11",
"xcb",
"X11-xcb",
+23 -5
View File
@@ -36,6 +36,9 @@
// Available audio systems:
#include "xenia/apu/nop/nop_audio_system.h"
#if XE_PLATFORM_LINUX
#include "xenia/apu/alsa/alsa_audio_system.h"
#endif // XE_PLATFORM_LINUX
#if !XE_PLATFORM_ANDROID
#include "xenia/apu/sdl/sdl_audio_system.h"
#endif // !XE_PLATFORM_ANDROID
@@ -60,11 +63,23 @@
#include "xenia/hid/xinput/xinput_hid.h"
#endif // XE_PLATFORM_WIN32
DEFINE_string(apu, "any", "Audio system. Use: [any, nop, sdl, xaudio2]", "APU");
DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]",
"GPU");
DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
"HID");
#if XE_PLATFORM_WIN32
#define APU_OPTIONS "[any, nop, sdl, xaudio2]"
#define GPU_OPTIONS "[any, d3d12, vulkan, null]"
#define HID_OPTIONS "[any, nop, sdl, winkey, xinput]"
#elif XE_PLATFORM_LINUX
#define APU_OPTIONS "[any, alsa, nop, sdl]"
#define GPU_OPTIONS "[any, vulkan, null]"
#define HID_OPTIONS "[any, nop, sdl]"
#else
#define APU_OPTIONS "[any, nop, sdl]"
#define GPU_OPTIONS "[any, vulkan, null]"
#define HID_OPTIONS "[any, nop, sdl]"
#endif
DEFINE_string(apu, "any", "Audio system. Use: " APU_OPTIONS, "APU");
DEFINE_string(gpu, "any", "Graphics system. Use: " GPU_OPTIONS, "GPU");
DEFINE_string(hid, "any", "Input system. Use: " HID_OPTIONS, "HID");
DEFINE_path(
storage_root, "",
@@ -296,6 +311,9 @@ std::unique_ptr<apu::AudioSystem> EmulatorApp::CreateAudioSystem(
#if XE_PLATFORM_WIN32
factory.Add<apu::xaudio2::XAudio2AudioSystem>("xaudio2");
#endif // XE_PLATFORM_WIN32
#if XE_PLATFORM_LINUX
factory.Add<apu::alsa::ALSAAudioSystem>("alsa");
#endif // XE_PLATFORM_LINUX
#if !XE_PLATFORM_ANDROID
factory.Add<apu::sdl::SDLAudioSystem>("sdl");
#endif // !XE_PLATFORM_ANDROID
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_ALSA_ALSA_AUDIO_DRIVER_H_
#define XENIA_APU_ALSA_ALSA_AUDIO_DRIVER_H_
#include <alsa/asoundlib.h>
#include <atomic>
#include <memory>
#include <thread>
#include "xenia/apu/audio_driver.h"
#include "xenia/base/threading.h"
namespace xe {
namespace apu {
namespace alsa {
class ALSAAudioDriver : public AudioDriver {
public:
ALSAAudioDriver(xe::threading::Semaphore* semaphore,
uint32_t frequency = kFrameFrequencyDefault,
uint32_t channels = kFrameChannelsDefault,
bool need_format_conversion = true);
~ALSAAudioDriver() override;
bool Initialize() override;
void SubmitFrame(float* frame) override;
void Pause() override;
void Resume() override;
void SetVolume(float volume) override { volume_.store(volume); }
void Shutdown() override;
private:
void WorkerThread();
bool SetupAlsaDevice();
bool RecoverFromUnderrun(int err);
void ConvertChannels(const float* input, float* output,
size_t channel_samples);
size_t ResampleFrame(const float* input, float* output,
size_t input_frame_count, size_t output_capacity_frames,
float frequency_ratio, uint32_t channels);
void ApplyVolume(float* buffer, size_t sample_count, float volume);
xe::threading::Semaphore* semaphore_ = nullptr;
// ALSA handles
snd_pcm_t* pcm_handle_ = nullptr;
snd_pcm_hw_params_t* hw_params_ = nullptr;
snd_pcm_sw_params_t* sw_params_ = nullptr;
// Device configuration
uint32_t frame_frequency_;
uint32_t frame_channels_;
uint32_t channel_samples_;
uint32_t frame_size_;
bool need_format_conversion_;
// Output configuration (may differ from input)
uint32_t output_channels_ = 0;
snd_pcm_uframes_t period_size_ = 0;
snd_pcm_uframes_t buffer_size_ = 0;
// Threading
std::unique_ptr<std::thread> worker_thread_;
std::atomic<bool> running_{false};
std::atomic<bool> paused_{false};
std::atomic<float> volume_{1.0f};
// Ring buffer for frames (larger size to reduce underruns)
static constexpr size_t kRingBufferSize = 32;
float* ring_buffer_[kRingBufferSize] = {};
std::atomic<size_t> read_index_{0};
std::atomic<size_t> write_index_{0};
// ALSA period and buffer configuration
static constexpr snd_pcm_uframes_t kPeriodSize51 = 512;
static constexpr snd_pcm_uframes_t kPeriodSizeStereo = 1024;
static constexpr size_t kBufferPeriods = 4;
// Conversion buffer
std::unique_ptr<float[]> conversion_buffer_;
// Time scaling / resampling state
std::unique_ptr<float[]> resample_buffer_;
double resample_frac_position_ = 0.0;
};
} // namespace alsa
} // namespace apu
} // namespace xe
#endif // XENIA_APU_ALSA_ALSA_AUDIO_DRIVER_H_
+94
View File
@@ -0,0 +1,94 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/alsa/alsa_audio_system.h"
#include <alsa/asoundlib.h>
#include "xenia/apu/alsa/alsa_audio_driver.h"
#include "xenia/apu/apu_flags.h"
#include "xenia/base/logging.h"
namespace xe {
namespace apu {
namespace alsa {
ALSAAudioSystem::ALSAAudioSystem(cpu::Processor* processor)
: AudioSystem(processor) {}
ALSAAudioSystem::~ALSAAudioSystem() {}
bool ALSAAudioSystem::IsAvailable() {
// Check if ALSA is available by trying to open a dummy PCM device
snd_pcm_t* pcm = nullptr;
int err =
snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err >= 0 && pcm) {
snd_pcm_close(pcm);
return true;
}
return false;
}
void ALSAAudioSystem::Initialize() {}
std::unique_ptr<AudioSystem> ALSAAudioSystem::Create(
cpu::Processor* processor) {
if (!IsAvailable()) {
XELOGE("ALSA is not available on this system");
return nullptr;
}
return std::make_unique<ALSAAudioSystem>(processor);
}
X_RESULT ALSAAudioSystem::CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) {
assert_not_null(out_driver);
XELOGI("ALSAAudioSystem::CreateDriver for client index {}", index);
// Create a new driver for each client
// ALSA's dmix plugin should handle mixing multiple streams
auto driver = new ALSAAudioDriver(semaphore);
if (!driver->Initialize()) {
XELOGE("Failed to initialize ALSA driver for client index {}", index);
delete driver;
*out_driver = nullptr;
return X_ERROR_NOT_FOUND;
}
*out_driver = driver;
XELOGI("Successfully created ALSA driver for client index {}", index);
return X_ERROR_SUCCESS;
}
AudioDriver* ALSAAudioSystem::CreateDriver(xe::threading::Semaphore* semaphore,
uint32_t frequency,
uint32_t channels,
bool need_format_conversion) {
auto driver = new ALSAAudioDriver(semaphore, frequency, channels,
need_format_conversion);
if (!driver->Initialize()) {
delete driver;
return nullptr;
}
return driver;
}
void ALSAAudioSystem::DestroyDriver(AudioDriver* driver) {
assert_not_null(driver);
ALSAAudioDriver* alsa_driver = static_cast<ALSAAudioDriver*>(driver);
alsa_driver->Shutdown();
delete alsa_driver;
}
} // namespace alsa
} // namespace apu
} // namespace xe
+43
View File
@@ -0,0 +1,43 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_ALSA_ALSA_AUDIO_SYSTEM_H_
#define XENIA_APU_ALSA_ALSA_AUDIO_SYSTEM_H_
#include "xenia/apu/audio_system.h"
namespace xe {
namespace apu {
namespace alsa {
class ALSAAudioSystem : public AudioSystem {
public:
explicit ALSAAudioSystem(cpu::Processor* processor);
~ALSAAudioSystem() override;
static bool IsAvailable();
static std::unique_ptr<AudioSystem> Create(cpu::Processor* processor);
X_RESULT CreateDriver(size_t index, xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) override;
AudioDriver* CreateDriver(xe::threading::Semaphore* semaphore,
uint32_t frequency, uint32_t channels,
bool need_format_conversion) override;
void DestroyDriver(AudioDriver* driver) override;
protected:
void Initialize() override;
};
} // namespace alsa
} // namespace apu
} // namespace xe
#endif // XENIA_APU_ALSA_ALSA_AUDIO_SYSTEM_H_
+18
View File
@@ -0,0 +1,18 @@
group("src")
project("xenia-apu-alsa")
uuid("8c2e1340-f847-4f9a-8b2e-5d8c1b7a8f9e")
kind("StaticLib")
language("C++")
links({
"xenia-apu",
"xenia-base",
})
defines({
})
local_platform_files()
filter("platforms:Linux")
links({
"asound",
"pthread",
})
+16 -1
View File
@@ -194,14 +194,21 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg,
AudioDriver* driver;
auto result = CreateDriver(index, client_semaphore, &driver);
if (XFAILED(result)) {
XELOGE("AudioSystem::RegisterClient: CreateDriver failed for index={}",
index);
return result;
}
assert_not_null(driver);
XELOGI(
"AudioSystem::RegisterClient: driver created for index={}, driver={:p}",
index, (void*)driver);
uint32_t ptr = memory()->SystemHeapAlloc(0x4);
xe::store_and_swap<uint32_t>(memory()->TranslateVirtual(ptr), callback_arg);
clients_[index] = {driver, callback, callback_arg, ptr, true};
XELOGI("AudioSystem::RegisterClient: client {} registered successfully",
index);
if (out_index) {
*out_index = index;
@@ -215,7 +222,15 @@ void AudioSystem::SubmitFrame(size_t index, float* samples) {
auto global_lock = global_critical_region_.Acquire();
assert_true(index < kMaximumClientCount);
assert_true(clients_[index].driver != NULL);
if (index >= kMaximumClientCount || !clients_[index].in_use ||
!clients_[index].driver) {
XELOGW(
"SubmitFrame called for invalid/unregistered client index {} "
"(in_use={}, driver={:p})",
index, index < kMaximumClientCount ? clients_[index].in_use : false,
index < kMaximumClientCount ? (void*)clients_[index].driver : nullptr);
return;
}
(clients_[index].driver)->SubmitFrame(samples);
}