diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b6d08e1b..d405bbd42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,5 +6,22 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++") +set(SDL_SHARED OFF CACHE BOOL "" FORCE) +set(SDL_STATIC ON CACHE BOOL "" FORCE) +set(SDL_TEST OFF CACHE BOOL "" FORCE) +set(SDL_VIDEO OFF CACHE BOOL "" FORCE) +set(SDL_RENDER OFF CACHE BOOL "" FORCE) +set(SDL_X11 OFF CACHE BOOL "" FORCE) +set(SDL_WAYLAND OFF CACHE BOOL "" FORCE) +set(SDL_OPENGL OFF CACHE BOOL "" FORCE) +set(SDL_OPENGLES OFF CACHE BOOL "" FORCE) +set(SDL_VULKAN OFF CACHE BOOL "" FORCE) +set(SDL_JOYSTICK OFF CACHE BOOL "" FORCE) +set(SDL_HAPTIC OFF CACHE BOOL "" FORCE) +set(SDL_HIDAPI OFF CACHE BOOL "" FORCE) +set(SDL_SENSOR OFF CACHE BOOL "" FORCE) +set(SDL_STATIC_PIC ON CACHE BOOL "" FORCE) +add_subdirectory(3rdparty/SDL2) + add_subdirectory(core/libs) add_subdirectory(core/relinker) diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 2f5b83d73..1b53ed9f3 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -8,5 +8,8 @@ add_library(libSceAudioOut SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2System.cpp ) -target_include_directories(libSceAudioOut PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceAudioOut PRIVATE libc) +target_include_directories(libSceAudioOut PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include +) +target_link_libraries(libSceAudioOut PRIVATE libc libkernel SDL2-static) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 64f2d2eb6..be0c3fc04 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -1,57 +1,469 @@ +#include +#include +#include #include +#include +#include +#include +#include +#include + +#include "SDL.h" #include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include "prx/libkernel/Time/include/Time.hpp" + +static constexpr int PORT_TYPE_MAIN = 0; +static constexpr int PORT_TYPE_BGM = 1; +static constexpr int PORT_TYPE_VOICE = 2; +static constexpr int PORT_TYPE_PERSONAL = 3; +static constexpr int PORT_TYPE_PADSPK = 4; +static constexpr int PORT_TYPE_VIBRATION = 10; +static constexpr int PORT_TYPE_AUDIO3D = 126; +static constexpr int PORT_TYPE_AUX = 127; + +static constexpr int PORTS_MAX = 32; +static constexpr int DEFAULT_VOLUME = 32768; +static constexpr std::uint32_t FORMAT_MASK = 0xFFu; +static constexpr std::uint64_t TARGET_LATENCY_US = 40000; +static constexpr std::uint64_t DRAIN_TIMEOUT_US = 200000; +static constexpr std::uint64_t DRAIN_SLEEP_US = 1000; + +enum class Format { + Unknown, + S16Mono, + S16Stereo, + S16_8Ch, + F32Mono, + F32Stereo, + F32_8Ch, + S16_8ChStd, + F32_8ChStd, +}; + +static bool formatIsFloat(Format f) { + return f == Format::F32Mono || f == Format::F32Stereo || + f == Format::F32_8Ch || f == Format::F32_8ChStd; +} + +static bool formatIsStd(Format f) { + return f == Format::S16_8ChStd || f == Format::F32_8ChStd; +} + +static int channelsForFormat(Format f) { + switch (f) { + case Format::S16Mono: + case Format::F32Mono: + return 1; + case Format::S16Stereo: + case Format::F32Stereo: + return 2; + case Format::S16_8Ch: + case Format::F32_8Ch: + case Format::S16_8ChStd: + case Format::F32_8ChStd: + return 8; + default: + throw std::runtime_error("channelsForFormat: unknown format"); + } +} + +static SDL_AudioFormat sdlFormat(Format f) { + return formatIsFloat(f) ? AUDIO_F32SYS : AUDIO_S16SYS; +} + +static std::uint32_t bytesPerSample(Format f) { + return formatIsFloat(f) ? sizeof(float) : sizeof(std::int16_t); +} + +struct Port { + bool used = false; + int type = 0; + std::uint32_t samplesNum = 0; + std::uint32_t freq = 0; + Format format = Format::Unknown; + int channels = 0; + int volume[8] = {}; + std::uint64_t lastOutputTime = 0; + SDL_AudioDeviceID device = 0; + SDL_AudioSpec spec = {}; +}; + +static std::mutex g_mutex; +static Port g_ports[PORTS_MAX]; +static bool g_sdlInitialized = false; + +static bool ensureSdlAudio() { + if (g_sdlInitialized) { + return true; + } + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + return false; + } + g_sdlInitialized = true; + return true; +} + +static bool openDevice(Port& port) { + if (!ensureSdlAudio()) { + return false; + } + SDL_AudioSpec desired{}; + desired.freq = static_cast(port.freq); + desired.format = sdlFormat(port.format); + desired.channels = static_cast(port.channels); + desired.samples = static_cast(port.samplesNum); + desired.callback = nullptr; + SDL_AudioSpec obtained{}; + port.device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); + if (port.device == 0) { + return false; + } + port.spec = obtained; + SDL_PauseAudioDevice(port.device, 0); + return true; +} + +static void closeDevice(Port& port) { + if (port.device != 0 && SDL_WasInit(SDL_INIT_AUDIO) != 0) { + SDL_ClearQueuedAudio(port.device); + SDL_CloseAudioDevice(port.device); + } + port.device = 0; + port.spec = {}; +} + +static constexpr std::uint32_t STD_8CH_MAP[8] = {0, 1, 2, 3, 6, 7, 4, 5}; + +static const void* prepareBuffer(const Port& port, const void* data, std::vector& buf) { + const auto frames = port.samplesNum; + const auto ch = static_cast(port.channels); + const auto bps = bytesPerSample(port.format); + const auto size = frames * ch * bps; + + bool volumeChanged = false; + for (std::uint32_t i = 0; i < ch; i++) { + if (port.volume[i] != DEFAULT_VOLUME) { + volumeChanged = true; + break; + } + } + + if (!volumeChanged && !formatIsStd(port.format)) { + return data; + } + + buf.resize(size); + const bool isStd = formatIsStd(port.format) && ch == 8; + + if (formatIsFloat(port.format)) { + auto* dst = reinterpret_cast(buf.data()); + const auto* src = static_cast(data); + for (std::uint32_t fr = 0; fr < frames; fr++) { + for (std::uint32_t c = 0; c < ch; c++) { + const auto srcCh = isStd ? STD_8CH_MAP[c] : c; + dst[fr * ch + c] = src[fr * ch + srcCh] * + (static_cast(port.volume[c]) / static_cast(DEFAULT_VOLUME)); + } + } + } else { + auto* dst = reinterpret_cast(buf.data()); + const auto* src = static_cast(data); + for (std::uint32_t fr = 0; fr < frames; fr++) { + for (std::uint32_t c = 0; c < ch; c++) { + const auto srcCh = isStd ? STD_8CH_MAP[c] : c; + std::int64_t s = static_cast(src[fr * ch + srcCh]) * + port.volume[c] / DEFAULT_VOLUME; + s = std::clamp(s, + static_cast(std::numeric_limits::min()), + static_cast(std::numeric_limits::max())); + dst[fr * ch + c] = static_cast(s); + } + } + } + return buf.data(); +} + +static void queueAudio(Port& port, const void* data) { + if (port.device == 0 || data == nullptr) { + return; + } + + std::vector prepareBuf; + const void* prepared = prepareBuffer(port, data, prepareBuf); + const std::uint32_t preparedSize = port.samplesNum * + static_cast(port.channels) * bytesPerSample(port.format); + + SDL_AudioCVT cvt{}; + const int cvtResult = SDL_BuildAudioCVT( + &cvt, + sdlFormat(port.format), static_cast(port.channels), static_cast(port.freq), + port.spec.format, port.spec.channels, port.spec.freq); + + if (cvtResult < 0) { + throw std::runtime_error(std::string("SDL_BuildAudioCVT: ") + SDL_GetError()); + } + + const void* queueData = prepared; + std::uint32_t queueSize = preparedSize; + std::vector convertBuf; + + if (cvtResult > 0) { + convertBuf.resize(static_cast(preparedSize) * cvt.len_mult); + std::memcpy(convertBuf.data(), prepared, preparedSize); + cvt.buf = convertBuf.data(); + cvt.len = static_cast(preparedSize); + if (SDL_ConvertAudio(&cvt) < 0) { + throw std::runtime_error(std::string("SDL_ConvertAudio: ") + SDL_GetError()); + } + queueData = cvt.buf; + queueSize = static_cast(cvt.len_cvt); + } + + const std::uint64_t bufferUs = port.freq != 0 + ? (1000000ULL * port.samplesNum) / port.freq + : 0; + const std::uint32_t buffers = bufferUs != 0 + ? static_cast((TARGET_LATENCY_US + bufferUs - 1) / bufferUs) + : 2u; + const std::uint32_t minQueued = queueSize * std::clamp(buffers, 2u, 16u); + const std::uint64_t waitStart = sceKernelGetProcessTime(); + + while (SDL_GetQueuedAudioSize(port.device) > minQueued) { + if (sceKernelGetProcessTime() - waitStart > DRAIN_TIMEOUT_US) { + SDL_ClearQueuedAudio(port.device); + break; + } + struct timespec req{}; + req.tv_sec = 0; + req.tv_nsec = static_cast(DRAIN_SLEEP_US * 1000ULL); + nanosleep(&req, nullptr); + } + + if (SDL_QueueAudio(port.device, queueData, queueSize) < 0) { + throw std::runtime_error(std::string("SDL_QueueAudio: ") + SDL_GetError()); + } +} + +static bool portTypeValid(int type) { + return (type >= PORT_TYPE_MAIN && type <= PORT_TYPE_PADSPK) || + type == PORT_TYPE_VIBRATION || + type == PORT_TYPE_AUDIO3D || + type == PORT_TYPE_AUX; +} + +static Port* getPort(int handle) { + const int idx = handle - 1; + if (idx < 0 || idx >= PORTS_MAX || !g_ports[idx].used) { + return nullptr; + } + return &g_ports[idx]; +} extern "C" { -int APS5_VABI sceAudioOutInit(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAudioOutInit() { return 0; } -int APS5_VABI sceAudioOutOpen(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param) { - (void)user_id; - (void)type; - (void)index; - (void)len; - (void)freq; - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len, + std::uint32_t freq, std::uint32_t param) { + (void)userId; + if (!portTypeValid(type)) { + return -2144993270; + } + if (index != 0) { + throw std::runtime_error("sceAudioOutOpen: index != 0 not supported"); + } + + Format format = Format::Unknown; + switch (param & FORMAT_MASK) { + case 0: format = Format::S16Mono; break; + case 1: format = Format::S16Stereo; break; + case 2: format = Format::S16_8Ch; break; + case 3: format = Format::F32Mono; break; + case 4: format = Format::F32Stereo; break; + case 5: format = Format::F32_8Ch; break; + case 6: format = Format::S16_8ChStd; break; + case 7: format = Format::F32_8ChStd; break; + default: + throw std::runtime_error("sceAudioOutOpen: unknown format param"); + } + + std::lock_guard lock(g_mutex); + for (int i = 0; i < PORTS_MAX; i++) { + if (!g_ports[i].used) { + Port& port = g_ports[i]; + port.used = true; + port.type = type; + port.samplesNum = len; + port.freq = freq; + port.format = format; + port.channels = channelsForFormat(format); + port.lastOutputTime = 0; + for (int c = 0; c < port.channels; c++) { + port.volume[c] = DEFAULT_VOLUME; + } + if (type != PORT_TYPE_VIBRATION) { + openDevice(port); + } + return i + 1; + } + } + return -2144993275; } int APS5_VABI sceAudioOutClose(int handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + Port* port = getPort(handle); + if (port == nullptr) { + return -2144993277; + } + closeDevice(*port); + *port = Port{}; return 0; } int APS5_VABI sceAudioOutOutput(int handle, const void* ptr) { - (void)handle; - (void)ptr; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard lock(g_mutex); + Port* port = getPort(handle); + if (port == nullptr) { + return -2144993277; + } + + const std::uint64_t blockUs = (1000000ULL * port->samplesNum) / port->freq; + const std::uint64_t now = sceKernelGetProcessTime(); + const std::uint64_t next = port->lastOutputTime + blockUs; + if (next > now && port->device == 0) { + const std::uint64_t waitUs = next - now; + struct timespec req{}; + req.tv_sec = static_cast(waitUs / 1000000ULL); + req.tv_nsec = static_cast((waitUs % 1000000ULL) * 1000ULL); + nanosleep(&req, nullptr); + } + + queueAudio(*port, ptr); + port->lastOutputTime = sceKernelGetProcessTime(); + return static_cast(port->samplesNum); } -int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, uint32_t num) { - (void)param; - (void)num; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) { + if (param == nullptr || num == 0) { + return -2144993276; + } + + std::lock_guard lock(g_mutex); + + for (std::uint32_t i = 0; i < num; i++) { + if (getPort(param[i].handle) == nullptr) { + return -2144993277; + } + } + + Port& first = *getPort(param[0].handle); + const std::uint64_t blockUs = (1000000ULL * first.samplesNum) / first.freq; + const std::uint64_t now = sceKernelGetProcessTime(); + + std::uint64_t maxWait = 0; + for (std::uint32_t i = 0; i < num; i++) { + Port& p = *getPort(param[i].handle); + const std::uint64_t next = p.lastOutputTime + blockUs; + const std::uint64_t wait = next > now ? next - now : 0; + if (wait > maxWait) { + maxWait = wait; + } + } + + bool anyDevice = false; + for (std::uint32_t i = 0; i < num; i++) { + if (getPort(param[i].handle)->device != 0) { + anyDevice = true; + break; + } + } + + if (maxWait != 0 && !anyDevice) { + struct timespec req{}; + req.tv_sec = static_cast(maxWait / 1000000ULL); + req.tv_nsec = static_cast((maxWait % 1000000ULL) * 1000ULL); + nanosleep(&req, nullptr); + } + + for (std::uint32_t i = 0; i < num; i++) { + queueAudio(*getPort(param[i].handle), param[i].ptr); + } + + const std::uint64_t done = sceKernelGetProcessTime(); + for (std::uint32_t i = 0; i < num; i++) { + getPort(param[i].handle)->lastOutputTime = done; + } + + return static_cast(first.samplesNum); } -int APS5_VABI sceAudioOutSetVolume(int handle, uint32_t flag, int* vol) { - (void)handle; - (void)flag; - (void)vol; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAudioOutSetVolume(int handle, std::uint32_t flag, int* vol) { + if (vol == nullptr) { + return -2144993276; + } + std::lock_guard lock(g_mutex); + Port* port = getPort(handle); + if (port == nullptr) { + return -2144993277; + } + const bool isStd = formatIsStd(port->format); + for (int i = 0; i < port->channels; i++, flag >>= 1u) { + if ((flag & 1u) == 0) { + continue; + } + int srcIdx = i; + if (isStd) { + if (i == 4) srcIdx = 6; + else if (i == 5) srcIdx = 7; + else if (i == 6) srcIdx = 4; + else if (i == 7) srcIdx = 5; + } + port->volume[i] = vol[srcIdx]; + } return 0; } int APS5_VABI sceAudioOutGetPortState(int handle, AudioOutPortState* state) { - (void)handle; - (void)state; - NotImplemented_nid_no_patch(__func__); + if (state == nullptr) { + return -2144993276; + } + std::lock_guard lock(g_mutex); + Port* port = getPort(handle); + if (port == nullptr) { + return -2144993277; + } + state->rerouteCounter = 0; + state->volume = 127; + state->flag = 0; + state->activeState = 0; + state->reserved[0] = 0; + switch (port->type) { + case PORT_TYPE_MAIN: + case PORT_TYPE_BGM: + case PORT_TYPE_AUDIO3D: + state->output = 1; + state->channel = static_cast(port->channels > 2 ? 2 : port->channels); + break; + case PORT_TYPE_VOICE: + case PORT_TYPE_PERSONAL: + state->output = 0x40; + state->channel = 1; + break; + case PORT_TYPE_PADSPK: + case PORT_TYPE_VIBRATION: + state->output = 4; + state->channel = 1; + break; + case PORT_TYPE_AUX: + state->output = 0x80; + state->channel = 0; + break; + default: + throw std::runtime_error("sceAudioOutGetPortState: unknown port type"); + } return 0; }