rpcsx-os: aout playback support

This commit is contained in:
Nikita Savyolov 2024-10-04 00:24:34 +03:00
parent 1a3d783c78
commit 3c1620c030
No known key found for this signature in database
GPG Key ID: 32C1EF023AFC184B
8 changed files with 546 additions and 22 deletions

6
.github/BUILDING.md vendored
View File

@ -4,20 +4,20 @@
### The dependencies for Debian-like distributions. ### The dependencies for Debian-like distributions.
``` ```
sudo apt install build-essential cmake libunwind-dev libglfw3-dev libvulkan-dev vulkan-validationlayers-dev spirv-tools glslang-tools libspirv-cross-c-shared-dev libsox-dev git sudo apt install build-essential cmake libunwind-dev libglfw3-dev libvulkan-dev vulkan-validationlayers-dev spirv-tools glslang-tools libspirv-cross-c-shared-dev libsox-dev git libasound2-dev
``` ```
# git is only needed for ubuntu 22.04 # git is only needed for ubuntu 22.04
### The dependencies for Fedora distributions: ### The dependencies for Fedora distributions:
``` ```
sudo dnf install cmake libunwind-devel glfw-devel vulkan-devel vulkan-validation-layers-devel spirv-tools glslang-devel gcc-c++ gcc spirv-tools-devel xbyak-devel sox-devel sudo dnf install cmake libunwind-devel glfw-devel vulkan-devel vulkan-validation-layers-devel spirv-tools glslang-devel gcc-c++ gcc spirv-tools-devel xbyak-devel sox-devel alsa-lib-devel
``` ```
### The dependencies for Arch distributions: ### The dependencies for Arch distributions:
``` ```
sudo pacman -S libunwind glfw-x11 vulkan-devel sox glslang git cmake sudo pacman -S libunwind glfw-x11 vulkan-devel sox glslang git cmake alsa-lib
``` ```
> Side note you will need to pull ``spirv-cross`` from the AUR for now so do the following > Side note you will need to pull ``spirv-cross`` from the AUR for now so do the following
``` ```

View File

@ -26,7 +26,7 @@ jobs:
sudo apt update sudo apt update
sudo apt install -y cmake build-essential libunwind-dev \ sudo apt install -y cmake build-essential libunwind-dev \
libglfw3-dev libvulkan-dev vulkan-validationlayers \ libglfw3-dev libvulkan-dev vulkan-validationlayers \
libsox-dev libsox-dev libasound2-dev
echo "deb http://azure.archive.ubuntu.com/ubuntu noble main universe" | sudo tee /etc/apt/sources.list echo "deb http://azure.archive.ubuntu.com/ubuntu noble main universe" | sudo tee /etc/apt/sources.list
sudo apt update sudo apt update
sudo apt install g++-14 ninja-build sudo apt install g++-14 ninja-build

View File

@ -6,6 +6,9 @@ target_include_directories(standalone-config INTERFACE orbis-kernel-config)
add_library(orbis::kernel::config ALIAS standalone-config) add_library(orbis::kernel::config ALIAS standalone-config)
add_executable(rpcsx-os add_executable(rpcsx-os
audio/AudioDevice.cpp
audio/AlsaDevice.cpp
iodev/ajm.cpp iodev/ajm.cpp
iodev/blockpool.cpp iodev/blockpool.cpp
iodev/bt.cpp iodev/bt.cpp
@ -66,7 +69,7 @@ add_executable(rpcsx-os
) )
target_include_directories(rpcsx-os PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(rpcsx-os PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(rpcsx-os PUBLIC orbis::kernel amdgpu::bridge rx libcrypto libunwind::unwind-x86_64 xbyak::xbyak sox::sox) target_link_libraries(rpcsx-os PUBLIC orbis::kernel amdgpu::bridge rx libcrypto libunwind::unwind-x86_64 xbyak::xbyak sox::sox asound)
target_base_address(rpcsx-os 0x0000010000000000) target_base_address(rpcsx-os 0x0000010000000000)
target_compile_options(rpcsx-os PRIVATE "-mfsgsbase") target_compile_options(rpcsx-os PRIVATE "-mfsgsbase")

View File

@ -0,0 +1,242 @@
#include "AlsaDevice.hpp"
#include "orbis/utils/Logs.hpp"
#include "rx/hexdump.hpp"
AlsaDevice::AlsaDevice() {}
void AlsaDevice::start() {
setAlsaFormat();
int err;
if ((err = snd_pcm_open(&mPCMHandle, "default", SND_PCM_STREAM_PLAYBACK,
0)) < 0) {
ORBIS_LOG_FATAL("Cannot open audio device", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_malloc(&mHWParams)) < 0) {
ORBIS_LOG_FATAL("Cannot allocate hardware parameter structure",
snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_any(mPCMHandle, mHWParams)) < 0) {
ORBIS_LOG_FATAL("Cannot initialize hardware parameter structure",
snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_set_rate_resample(mPCMHandle, mHWParams,
0)) < 0) {
ORBIS_LOG_FATAL("Cannot disable rate resampling", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_set_access(mPCMHandle, mHWParams,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
ORBIS_LOG_FATAL("Cannot set access type", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_set_format(mPCMHandle, mHWParams,
mAlsaFormat)) < 0) {
ORBIS_LOG_FATAL("Cannot set sample format", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_set_rate(mPCMHandle, mHWParams, mFrequency,
0)) < 0) {
ORBIS_LOG_FATAL("Cannot set sample rate", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_set_channels(mPCMHandle, mHWParams, mChannels)) <
0) {
ORBIS_LOG_FATAL("cannot set channel count", snd_strerror(err), mChannels);
std::abort();
}
uint periods = mSampleCount;
if ((err = snd_pcm_hw_params_set_periods_max(mPCMHandle, mHWParams, &periods, NULL)) < 0) {
ORBIS_LOG_FATAL("Cannot set periods count", snd_strerror(err));
std::abort();
}
int frameBytes = snd_pcm_format_physical_width(mAlsaFormat) * mChannels / 8;
snd_pcm_uframes_t size = mSampleSize / frameBytes;
// TODO: it shouldn't work like this
if ((err = snd_pcm_hw_params_set_buffer_size(mPCMHandle, mHWParams, size)) < 0) {
ORBIS_LOG_FATAL("Cannot set buffer size", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_hw_params_set_period_size(mPCMHandle, mHWParams, size / 2, 0)) < 0) {
ORBIS_LOG_FATAL("Cannot set period size", snd_strerror(err));
std::abort();
}
snd_pcm_uframes_t periodSize;
if ((err = snd_pcm_hw_params_get_period_size(mHWParams, &periodSize, NULL)) < 0) {
ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err));
std::abort();
}
snd_pcm_uframes_t bufferSize;
if ((err = snd_pcm_hw_params_get_buffer_size(mHWParams, &bufferSize)) < 0) {
ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err));
std::abort();
}
ORBIS_LOG_TODO("period and buffer", periodSize, bufferSize);
if ((err = snd_pcm_hw_params(mPCMHandle, mHWParams)) < 0) {
ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_sw_params_malloc(&mSWParams)) < 0) {
ORBIS_LOG_FATAL("Cannot allocate software parameter structure",
snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_sw_params_current(mPCMHandle, mSWParams)) < 0) {
ORBIS_LOG_FATAL("cannot sw params current", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_sw_params_set_start_threshold(mPCMHandle, mSWParams, periodSize)) < 0) {
ORBIS_LOG_FATAL("cannot set start threshold", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_sw_params_set_stop_threshold(mPCMHandle, mSWParams, bufferSize)) < 0) {
ORBIS_LOG_FATAL("cannot set stop threshold", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_sw_params(mPCMHandle, mSWParams)) < 0) {
ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err));
std::abort();
}
if ((err = snd_pcm_prepare(mPCMHandle)) < 0) {
ORBIS_LOG_FATAL("cannot prepare audio interface for use",
snd_strerror(err));
std::abort();
}
mWorking = true;
}
int AlsaDevice::fixXRun()
{
switch (snd_pcm_state(mPCMHandle)) {
case SND_PCM_STATE_XRUN:
return snd_pcm_prepare(mPCMHandle);
case SND_PCM_STATE_DRAINING:
if (snd_pcm_stream(mPCMHandle) == SND_PCM_STREAM_CAPTURE)
return snd_pcm_prepare(mPCMHandle);
break;
default:
break;
}
return -EIO;
}
int AlsaDevice::resumeFromSupsend()
{
int res;
while ((res = snd_pcm_resume(mPCMHandle)) == -EAGAIN)
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!res)
return 0;
return snd_pcm_prepare(mPCMHandle);
}
long AlsaDevice::write(void *buf, long len) {
if (!mWorking) return 0;
ssize_t r;
int frameBytes = snd_pcm_format_physical_width(mAlsaFormat) * mChannels / 8;
snd_pcm_uframes_t frames = len / frameBytes;
r = snd_pcm_writei(mPCMHandle, buf, frames);
if (r == -EPIPE) {
if (!(r = fixXRun()))
return write(buf, len);
} else if (r == -ESTRPIPE) {
if (!(r = resumeFromSupsend()))
return write(buf, len);
}
r *= frameBytes;
return r;
}
void AlsaDevice::stop() {
snd_pcm_hw_params_free(mHWParams);
snd_pcm_sw_params_free(mSWParams);
snd_pcm_drain(mPCMHandle);
snd_pcm_drop(mPCMHandle);
mWorking = false;
}
void AlsaDevice::reset() {
if (!mWorking) return;
int err;
err = snd_pcm_drop(mPCMHandle);
if (err >= 0)
err = snd_pcm_prepare(mPCMHandle);
if (err < 0)
err = err;
}
audio_buf_info AlsaDevice::getOSpace() {
int err;
snd_pcm_uframes_t periodSize;
if ((err = snd_pcm_hw_params_get_period_size(mHWParams, &periodSize, NULL)) < 0) {
ORBIS_LOG_FATAL("cannot get period size", snd_strerror(err));
std::abort();
}
snd_pcm_uframes_t bufferSize;
if ((err = snd_pcm_hw_params_get_buffer_size(mHWParams, &bufferSize)) < 0) {
ORBIS_LOG_FATAL("cannot get buffer size", snd_strerror(err));
std::abort();
}
int frameBytes = snd_pcm_format_physical_width(mAlsaFormat) * mChannels / 8;
snd_pcm_sframes_t avail, delay;
audio_buf_info info;
avail = snd_pcm_avail_update(mPCMHandle);
if (avail < 0 || (snd_pcm_uframes_t)avail > bufferSize)
avail = bufferSize;
info.fragsize = periodSize * frameBytes;
info.fragstotal = mSampleCount;
info.bytes = avail * frameBytes;
info.fragments = avail / periodSize;
return info;
}
void AlsaDevice::setAlsaFormat() {
if (mWorking)
return;
_snd_pcm_format fmt;
switch (mFormat) {
case FMT_S32_LE:
fmt = SND_PCM_FORMAT_S32_LE;
break;
case FMT_S16_LE:
fmt = SND_PCM_FORMAT_S16_LE;
break;
case FMT_AC3:
default:
ORBIS_LOG_FATAL("Format is not supported", mFormat);
std::abort();
break;
}
mAlsaFormat = fmt;
}
AlsaDevice::~AlsaDevice() {
stop();
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "AudioDevice.hpp"
#include <alsa/asoundlib.h>
#include <cstdlib>
#include <thread>
class AlsaDevice : public AudioDevice {
private:
snd_pcm_format_t mAlsaFormat;
snd_pcm_t *mPCMHandle;
snd_pcm_hw_params_t *mHWParams;
snd_pcm_sw_params_t *mSWParams;
public:
AlsaDevice();
~AlsaDevice() override;
void init() override {};
void start() override;
long write(void *, long) override;
void stop() override;
void reset() override;
void setAlsaFormat();
int fixXRun();
int resumeFromSupsend();
audio_buf_info getOSpace() override;
};

View File

@ -0,0 +1,55 @@
#include "AudioDevice.hpp"
#include "orbis/utils/Logs.hpp"
#include "rx/hexdump.hpp"
AudioDevice::AudioDevice() {}
void AudioDevice::init() {}
void AudioDevice::start() {}
long AudioDevice::write(void *buf, long len) {
return -1;
}
void AudioDevice::stop() {
}
void AudioDevice::reset() {}
void AudioDevice::setFormat(orbis::uint format) {
if (mWorking)
return;
mFormat = format;
}
void AudioDevice::setFrequency(orbis::uint frequency) {
if (mWorking)
return;
mFrequency = frequency;
}
void AudioDevice::setChannels(orbis::ushort channels) {
if (mWorking)
return;
if (channels > 8) {
ORBIS_LOG_FATAL("Channels count is not supported", channels);
std::abort();
}
mChannels = channels;
}
void AudioDevice::setSampleSize(orbis::uint sampleSize, orbis::uint sampleCount) {
if (mWorking)
return;
mSampleSize = sampleSize;
mSampleCount = sampleCount;
}
audio_buf_info AudioDevice::getOSpace() {
audio_buf_info info;
return info;
}
AudioDevice::~AudioDevice() {}

View File

@ -0,0 +1,44 @@
#pragma once
#define FMT_S16_LE 0x10
#define FMT_AC3 0x400
#define FMT_S32_LE 0x1000
#include <cstdlib>
#include <orbis/sys/sysproto.hpp>
struct audio_buf_info {
int fragments;
int fragstotal;
int fragsize;
int bytes;
};
class AudioDevice {
protected:
bool mWorking = false;
orbis::uint mFormat{};
orbis::uint mFrequency{};
orbis::ushort mChannels{};
orbis::ushort mSampleSize{};
orbis::ushort mSampleCount{};
private:
public:
AudioDevice();
virtual ~AudioDevice();
virtual void init();
virtual void start();
virtual long write(void *buf, long len);
virtual void stop();
virtual void reset();
void setFormat(orbis::uint format);
void setFrequency(orbis::uint frequency);
void setChannels(orbis::ushort channels);
void setSampleSize(orbis::uint sampleSize = 0, orbis::uint sampleCount = 0);
virtual audio_buf_info getOSpace();
};

View File

@ -1,3 +1,4 @@
#include "audio/AlsaDevice.hpp"
#include "io-device.hpp" #include "io-device.hpp"
#include "iodev/mbus_av.hpp" #include "iodev/mbus_av.hpp"
#include "orbis/KernelAllocator.hpp" #include "orbis/KernelAllocator.hpp"
@ -8,20 +9,165 @@
#include "orbis/uio.hpp" #include "orbis/uio.hpp"
#include "orbis/utils/Logs.hpp" #include "orbis/utils/Logs.hpp"
#include <bits/types/struct_iovec.h> #include <bits/types/struct_iovec.h>
// #include <rx/hexdump.hpp>
#define SNDCTL_DSP_RESET 0x20005000
#define SNDCTL_DSP_SETFRAGMENT 0xc004500a
#define SNDCTL_DSP_SETFMT 0xc0045005
#define SNDCTL_DSP_SPEED 0xc0045002
#define SNDCTL_DSP_CHANNELS 0xc0045006
#define ORBIS_AUDIO_UPDATE_TICK_PARAMS 0xc004505c
#define SNDCTL_DSP_SYNCGROUP 0xc048501c
#define ORBIS_AUDIO_CONFIG_SPDIF 0xc0085063
#define SNDCTL_DSP_GETBLKSIZE 0x40045004
#define SOUND_PCM_READ_BITS 0x40045005
#define SNDCTL_DSP_GETOSPACE 0x4010500c
#define SNDCTL_DSP_SYNCSTART 0x8004501d
#define ORBIS_AUDIO_IOCTL_SETCONTROL 0x80085062
struct AoutFile : orbis::File {}; struct AoutFile : orbis::File {};
struct AoutDevice : public IoDevice {
AudioDevice *audioDevice;
orbis::ErrorCode open(orbis::Ref<orbis::File> *file, const char *path,
std::uint32_t flags, std::uint32_t mode,
orbis::Thread *thread) override;
};
static orbis::ErrorCode aout_ioctl(orbis::File *file, std::uint64_t request, static orbis::ErrorCode aout_ioctl(orbis::File *file, std::uint64_t request,
void *argp, orbis::Thread *thread) { void *argp, orbis::Thread *thread) {
ORBIS_LOG_FATAL("Unhandled aout ioctl", request); auto device = static_cast<AoutDevice *>(file->device.get());
thread->where(); switch (request) {
case SNDCTL_DSP_RESET: {
ORBIS_LOG_TODO("SNDCTL_DSP_RESET");
if (auto audioDevice = device->audioDevice) {
audioDevice->reset();
}
return {};
}
case SNDCTL_DSP_SETFRAGMENT: {
struct Args {
std::uint32_t fragment;
};
auto args = reinterpret_cast<Args *>(argp);
ORBIS_LOG_NOTICE("SNDCTL_DSP_SETFRAGMENT", args->fragment & 0xF, (args->fragment >> 16) & 0xF);
if (auto audioDevice = device->audioDevice) {
audioDevice->setSampleSize(1 << (args->fragment & 0xF), (args->fragment >> 16) & 0xF);
}
return {};
}
case SNDCTL_DSP_SETFMT: {
struct Args {
std::uint32_t fmt;
};
auto args = reinterpret_cast<Args *>(argp);
ORBIS_LOG_NOTICE("SNDCTL_DSP_SETFMT", args->fmt);
if (auto audioDevice = device->audioDevice) {
audioDevice->setFormat(args->fmt);
}
return {};
}
case SNDCTL_DSP_SPEED: {
struct Args {
std::uint32_t speed;
};
auto args = reinterpret_cast<Args *>(argp);
if (auto audioDevice = device->audioDevice) {
audioDevice->setFrequency(args->speed);
}
return {};
}
case SNDCTL_DSP_CHANNELS: {
struct Args {
std::uint32_t channels;
};
auto args = reinterpret_cast<Args *>(argp);
if (auto audioDevice = device->audioDevice) {
audioDevice->setChannels(args->channels);
}
return {};
}
case ORBIS_AUDIO_UPDATE_TICK_PARAMS: {
struct Args {
std::uint32_t tick;
};
auto args = reinterpret_cast<Args *>(argp);
ORBIS_LOG_NOTICE("ORBIS_AUDIO_UPDATE_TICK_PARAMS", args->tick);
return {};
}
case SNDCTL_DSP_SYNCGROUP: {
ORBIS_LOG_NOTICE("SNDCTL_DSP_SYNCGROUP");
return {};
}
case ORBIS_AUDIO_CONFIG_SPDIF: {
struct Args {
std::uint64_t unk0;
};
auto args = reinterpret_cast<Args *>(argp);
args->unk0 = 0x100000000; // Disable SPDIF output
return {};
}
case SNDCTL_DSP_GETBLKSIZE: {
struct Args {
std::uint32_t blksize;
};
auto args = reinterpret_cast<Args *>(argp);
ORBIS_LOG_NOTICE("SNDCTL_DSP_GETBLKSIZE", args->blksize);
return {};
}
case SOUND_PCM_READ_BITS: {
struct Args {
std::uint32_t bits;
};
auto args = reinterpret_cast<Args *>(argp);
ORBIS_LOG_NOTICE("SOUND_PCM_READ_BITS", args->bits);
return {};
}
case SNDCTL_DSP_GETOSPACE: {
auto args = reinterpret_cast<audio_buf_info *>(argp);
if (auto audioDevice = device->audioDevice) {
auto info = audioDevice->getOSpace();
args->fragments = info.fragments;
args->fragstotal = info.fragstotal;
args->fragsize = info.fragsize;
args->bytes = info.bytes;
}
ORBIS_LOG_TODO("SNDCTL_DSP_GETOSPACE", args->fragments, args->fragstotal, args->fragsize, args->bytes);
return {};
}
case SNDCTL_DSP_SYNCSTART: {
ORBIS_LOG_NOTICE("SNDCTL_DSP_SYNCSTART");
if (auto audioDevice = device->audioDevice) {
audioDevice->start();
}
return {};
}
case ORBIS_AUDIO_IOCTL_SETCONTROL: {
struct Args {
std::uint64_t unk0;
};
auto args = reinterpret_cast<Args *>(argp);
ORBIS_LOG_NOTICE("ORBIS_AUDIO_IOCTL_SETCONTROL", args->unk0);
return {};
}
default:
ORBIS_LOG_FATAL("Unhandled aout ioctl", request);
thread->where();
break;
}
return {}; return {};
} }
static orbis::ErrorCode aout_write(orbis::File *file, orbis::Uio *uio, static orbis::ErrorCode aout_write(orbis::File *file, orbis::Uio *uio,
orbis::Thread *) { orbis::Thread *thread) {
for (auto entry : std::span(uio->iov, uio->iovcnt)) { auto device = static_cast<AoutDevice *>(file->device.get());
uio->offset += entry.len; if (auto audioDevice = device->audioDevice) {
for (auto vec : std::span(uio->iov, uio->iovcnt)) {
audioDevice->write(vec.base, vec.len);
// rx::hexdump({(std::byte*)vec.base, vec.len});
uio->offset += vec.len;
}
} }
return {}; return {};
} }
@ -31,19 +177,22 @@ static const orbis::FileOps fileOps = {
.write = aout_write, .write = aout_write,
}; };
struct AoutDevice : IoDevice { orbis::ErrorCode AoutDevice::open(orbis::Ref<orbis::File> *file,
orbis::ErrorCode open(orbis::Ref<orbis::File> *file, const char *path, const char *path, std::uint32_t flags,
std::uint32_t flags, std::uint32_t mode, std::uint32_t mode, orbis::Thread *thread) {
orbis::Thread *thread) override { ORBIS_LOG_FATAL("aout device open", path, flags, mode);
ORBIS_LOG_FATAL("aout device open", path, flags, mode); auto newFile = orbis::knew<AoutFile>();
auto newFile = orbis::knew<AoutFile>(); newFile->ops = &fileOps;
newFile->ops = &fileOps; newFile->device = this;
newFile->device = this; thread->where();
thread->where();
*file = newFile; *file = newFile;
return {}; // TODO: create it only for hdmi device
if (true) {
// TODO: use factory to more backends support
this->audioDevice = new AlsaDevice();
} }
}; return {};
}
IoDevice *createAoutCharacterDevice() { return orbis::knew<AoutDevice>(); } IoDevice *createAoutCharacterDevice() { return orbis::knew<AoutDevice>(); }