From 3c1620c030adddd3e0fc4f58fd1a40645555d516 Mon Sep 17 00:00:00 2001 From: Nikita Savyolov Date: Fri, 4 Oct 2024 00:24:34 +0300 Subject: [PATCH] rpcsx-os: aout playback support --- .github/BUILDING.md | 6 +- .github/workflows/rpcsx.yml | 2 +- rpcsx-os/CMakeLists.txt | 5 +- rpcsx-os/audio/AlsaDevice.cpp | 242 +++++++++++++++++++++++++++++++++ rpcsx-os/audio/AlsaDevice.hpp | 31 +++++ rpcsx-os/audio/AudioDevice.cpp | 55 ++++++++ rpcsx-os/audio/AudioDevice.hpp | 44 ++++++ rpcsx-os/iodev/aout.cpp | 183 ++++++++++++++++++++++--- 8 files changed, 546 insertions(+), 22 deletions(-) create mode 100644 rpcsx-os/audio/AlsaDevice.cpp create mode 100644 rpcsx-os/audio/AlsaDevice.hpp create mode 100644 rpcsx-os/audio/AudioDevice.cpp create mode 100644 rpcsx-os/audio/AudioDevice.hpp diff --git a/.github/BUILDING.md b/.github/BUILDING.md index 231a21c25..c120a19de 100644 --- a/.github/BUILDING.md +++ b/.github/BUILDING.md @@ -4,20 +4,20 @@ ### 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 ### 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: ``` -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 ``` diff --git a/.github/workflows/rpcsx.yml b/.github/workflows/rpcsx.yml index 4ebec57fc..9843c63a8 100644 --- a/.github/workflows/rpcsx.yml +++ b/.github/workflows/rpcsx.yml @@ -26,7 +26,7 @@ jobs: sudo apt update sudo apt install -y cmake build-essential libunwind-dev \ 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 sudo apt update sudo apt install g++-14 ninja-build diff --git a/rpcsx-os/CMakeLists.txt b/rpcsx-os/CMakeLists.txt index 745ba1716..c4aa07c5e 100644 --- a/rpcsx-os/CMakeLists.txt +++ b/rpcsx-os/CMakeLists.txt @@ -6,6 +6,9 @@ target_include_directories(standalone-config INTERFACE orbis-kernel-config) add_library(orbis::kernel::config ALIAS standalone-config) add_executable(rpcsx-os + audio/AudioDevice.cpp + audio/AlsaDevice.cpp + iodev/ajm.cpp iodev/blockpool.cpp iodev/bt.cpp @@ -66,7 +69,7 @@ add_executable(rpcsx-os ) 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_compile_options(rpcsx-os PRIVATE "-mfsgsbase") diff --git a/rpcsx-os/audio/AlsaDevice.cpp b/rpcsx-os/audio/AlsaDevice.cpp new file mode 100644 index 000000000..024a668a1 --- /dev/null +++ b/rpcsx-os/audio/AlsaDevice.cpp @@ -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(); +} \ No newline at end of file diff --git a/rpcsx-os/audio/AlsaDevice.hpp b/rpcsx-os/audio/AlsaDevice.hpp new file mode 100644 index 000000000..12328fa92 --- /dev/null +++ b/rpcsx-os/audio/AlsaDevice.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "AudioDevice.hpp" +#include +#include +#include + +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; +}; diff --git a/rpcsx-os/audio/AudioDevice.cpp b/rpcsx-os/audio/AudioDevice.cpp new file mode 100644 index 000000000..24a5778a7 --- /dev/null +++ b/rpcsx-os/audio/AudioDevice.cpp @@ -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() {} \ No newline at end of file diff --git a/rpcsx-os/audio/AudioDevice.hpp b/rpcsx-os/audio/AudioDevice.hpp new file mode 100644 index 000000000..95dfe2629 --- /dev/null +++ b/rpcsx-os/audio/AudioDevice.hpp @@ -0,0 +1,44 @@ +#pragma once + +#define FMT_S16_LE 0x10 +#define FMT_AC3 0x400 +#define FMT_S32_LE 0x1000 + +#include +#include + +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(); +}; diff --git a/rpcsx-os/iodev/aout.cpp b/rpcsx-os/iodev/aout.cpp index 3ac39cc94..0c019841a 100644 --- a/rpcsx-os/iodev/aout.cpp +++ b/rpcsx-os/iodev/aout.cpp @@ -1,3 +1,4 @@ +#include "audio/AlsaDevice.hpp" #include "io-device.hpp" #include "iodev/mbus_av.hpp" #include "orbis/KernelAllocator.hpp" @@ -8,20 +9,165 @@ #include "orbis/uio.hpp" #include "orbis/utils/Logs.hpp" #include +// #include + +#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 AoutDevice : public IoDevice { + AudioDevice *audioDevice; + + orbis::ErrorCode open(orbis::Ref *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, void *argp, orbis::Thread *thread) { - ORBIS_LOG_FATAL("Unhandled aout ioctl", request); - thread->where(); + auto device = static_cast(file->device.get()); + 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(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(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(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(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(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(argp); + args->unk0 = 0x100000000; // Disable SPDIF output + return {}; + } + case SNDCTL_DSP_GETBLKSIZE: { + struct Args { + std::uint32_t blksize; + }; + auto args = reinterpret_cast(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(argp); + ORBIS_LOG_NOTICE("SOUND_PCM_READ_BITS", args->bits); + return {}; + } + case SNDCTL_DSP_GETOSPACE: { + auto args = reinterpret_cast(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(argp); + ORBIS_LOG_NOTICE("ORBIS_AUDIO_IOCTL_SETCONTROL", args->unk0); + return {}; + } + default: + ORBIS_LOG_FATAL("Unhandled aout ioctl", request); + thread->where(); + break; + } return {}; } static orbis::ErrorCode aout_write(orbis::File *file, orbis::Uio *uio, - orbis::Thread *) { - for (auto entry : std::span(uio->iov, uio->iovcnt)) { - uio->offset += entry.len; + orbis::Thread *thread) { + auto device = static_cast(file->device.get()); + 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 {}; } @@ -31,19 +177,22 @@ static const orbis::FileOps fileOps = { .write = aout_write, }; -struct AoutDevice : IoDevice { - orbis::ErrorCode open(orbis::Ref *file, const char *path, - std::uint32_t flags, std::uint32_t mode, - orbis::Thread *thread) override { - ORBIS_LOG_FATAL("aout device open", path, flags, mode); - auto newFile = orbis::knew(); - newFile->ops = &fileOps; - newFile->device = this; - thread->where(); +orbis::ErrorCode AoutDevice::open(orbis::Ref *file, + const char *path, std::uint32_t flags, + std::uint32_t mode, orbis::Thread *thread) { + ORBIS_LOG_FATAL("aout device open", path, flags, mode); + auto newFile = orbis::knew(); + newFile->ops = &fileOps; + newFile->device = this; + thread->where(); - *file = newFile; - return {}; + *file = newFile; + // 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(); }