From f0b4afaec6f9de22ab134abffbe82a1dde4e3724 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 12 Apr 2025 17:40:02 -0700 Subject: [PATCH] audio3d: Implement central Audio3d output. --- src/core/libraries/audio/audioout.cpp | 3 +- src/core/libraries/audio/audioout.h | 10 ++- src/core/libraries/audio3d/audio3d.cpp | 113 ++++++++++++++++++------- src/core/libraries/audio3d/audio3d.h | 6 +- 4 files changed, 98 insertions(+), 34 deletions(-) diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index dea8115e9..92488443f 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -191,6 +191,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta case OrbisAudioOutPort::Main: case OrbisAudioOutPort::Bgm: case OrbisAudioOutPort::Voice: + case OrbisAudioOutPort::Audio3d: state->output = 1; state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels; break; @@ -316,7 +317,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; } if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) && - (port_type != OrbisAudioOutPort::Aux)) { + (port_type != OrbisAudioOutPort::Audio3d && port_type != OrbisAudioOutPort::Aux)) { LOG_ERROR(Lib_AudioOut, "Invalid port type"); return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; } diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index 5eafb43a1..7fcc25095 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -20,7 +20,15 @@ class PortBackend; constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22; constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value -enum class OrbisAudioOutPort { Main = 0, Bgm = 1, Voice = 2, Personal = 3, Padspk = 4, Aux = 127 }; +enum class OrbisAudioOutPort { + Main = 0, + Bgm = 1, + Voice = 2, + Personal = 3, + Padspk = 4, + Audio3d = 126, + Aux = 127, +}; enum class OrbisAudioOutParamFormat : u32 { S16Mono = 0, diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index 328113992..4a3910808 100644 --- a/src/core/libraries/audio3d/audio3d.cpp +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/assert.h" @@ -13,6 +14,17 @@ namespace Libraries::Audio3d { +static constexpr u32 AUDIO3D_SAMPLE_RATE = 48000; + +static constexpr u32 AUDIO3D_INPUT_NUM_CHANNELS = 8; + +static constexpr AudioOut::OrbisAudioOutParamFormat AUDIO3D_OUTPUT_FORMAT = + AudioOut::OrbisAudioOutParamFormat::S16Stereo; +static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2; +static constexpr u32 AUDIO3D_OUTPUT_BUFFER_FRAMES = 0x100; +static constexpr u32 AUDIO3D_OUTPUT_BUFFER_BYTES = + AUDIO3D_OUTPUT_BUFFER_FRAMES * AUDIO3D_OUTPUT_NUM_CHANNELS * sizeof(s16); + static std::unique_ptr state; int PS4_SYSV_ABI sceAudio3dAudioOutClose() { @@ -45,14 +57,17 @@ sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceU int PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) { LOG_INFO(Lib_Audio3d, "called, handle = {}, ptr = {}", handle, ptr); - if (!state->ports.contains(handle)) { - LOG_ERROR(Lib_Audio3d, "!state->ports.contains(handle)"); + if (!ptr) { + LOG_ERROR(Lib_Audio3d, "!ptr"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + if (handle < 0 || (handle & 0xFFFF) > 25) { + LOG_ERROR(Lib_Audio3d, "handle < 0 || (handle & 0xFFFF) > 25"); return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - state->ports[handle].queue.emplace_back(ptr); - - return ORBIS_OK; + return AudioOut::sceAudioOutOutput(handle, ptr); } int PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* param, @@ -64,8 +79,7 @@ int PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* p return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - // return sceAudioOutOutputs(param, num); - return ORBIS_OK; + return AudioOut::sceAudioOutOutputs(param, num); } int PS4_SYSV_ABI sceAudio3dBedWrite(const OrbisAudio3dPortId port_id, const u32 num_channels, @@ -108,7 +122,11 @@ int PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32 } } - state->ports[port_id].queue.emplace_back(buffer); + state->ports[port_id].queue.emplace_back(OrbisAudio3dPcm{ + .format = format, + .sample_buffer = buffer, + .num_samples = num_samples, + }); return ORBIS_OK; } @@ -160,7 +178,20 @@ int PS4_SYSV_ABI sceAudio3dInitialize(const s64 reserved) { } state = std::make_unique(); - AudioOut::sceAudioOutInit(); + + const auto init_ret = AudioOut::sceAudioOutInit(); + if (init_ret < 0) { + return init_ret; + } + + AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; + ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); + state->audio_out_handle = + AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, + AUDIO3D_OUTPUT_BUFFER_FRAMES, AUDIO3D_SAMPLE_RATE, ext_info); + if (state->audio_out_handle < 0) { + return state->audio_out_handle; + } return ORBIS_OK; } @@ -201,7 +232,7 @@ int PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id, switch (attribute.attribute_id) { case 0x00000001: { // PCM const auto pcm_attribute = static_cast(attribute.value); - state->ports[port_id].queue.emplace_back(pcm_attribute->sample_buffer); + state->ports[port_id].queue.emplace_back(*pcm_attribute); break; } default: @@ -230,17 +261,16 @@ int PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) { return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; } - auto& queue = state->ports[port_id].queue; - - if (queue.empty()) { - LOG_ERROR(Lib_Audio3d, "queue.empty()"); - return ORBIS_OK; + auto& port = state->ports[port_id]; + port.has_buffer = !port.queue.empty(); + if (port.has_buffer) { + port.current_buffer = port.queue.front(); + port.queue.pop_front(); + } else { + // Nothing to advance to. + LOG_DEBUG(Lib_Audio3d, "Port advance with no buffer queued"); } - // WHAT THE FUCK DO YOU DO - - // queue.pop(); - return ORBIS_OK; } @@ -298,15 +328,15 @@ int PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(const OrbisAudio3dPortId port_id, u return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - const auto [parameters, queue] = state->ports[port_id]; - const size_t size = queue.size(); + const auto port = state->ports[port_id]; + const size_t size = port.queue.size(); if (queue_level) { *queue_level = size; } if (queue_available) { - *queue_available = parameters.queue_depth - size; + *queue_available = port.parameters.queue_depth - size; } return ORBIS_OK; @@ -361,25 +391,46 @@ int PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id, return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - if (state->ports[port_id].parameters.buffer_mode != ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) { + const auto& port = state->ports[port_id]; + if (port.parameters.buffer_mode != ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) { LOG_ERROR(Lib_Audio3d, "port doesn't have push capability"); return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; } - auto& queue = state->ports[port_id].queue; - - if (queue.empty()) { - LOG_ERROR(Lib_Audio3d, "queue.empty()"); + if (!port.has_buffer) { + // Nothing to push. + LOG_DEBUG(Lib_Audio3d, "Port push with no buffer ready"); return ORBIS_OK; } - for (const auto ptr : queue) { - // AudioOut::sceAudioOutOutput(1, ptr); + const u32 src_size = + port.current_buffer.num_samples * sizeof(float) * AUDIO3D_INPUT_NUM_CHANNELS; + const SDL_AudioSpec src_spec = { + .format = port.current_buffer.format == ORBIS_AUDIO3D_FORMAT_S16 ? SDL_AUDIO_S16LE + : SDL_AUDIO_F32LE, + .channels = AUDIO3D_INPUT_NUM_CHANNELS, + .freq = AUDIO3D_SAMPLE_RATE, + }; + constexpr SDL_AudioSpec dst_spec = { + .format = SDL_AUDIO_S16LE, + .channels = AUDIO3D_OUTPUT_NUM_CHANNELS, + .freq = AUDIO3D_SAMPLE_RATE, + }; + + u8* dst_data; + int dst_len; + if (!SDL_ConvertAudioSamples(&src_spec, + static_cast(port.current_buffer.sample_buffer), + static_cast(src_size), &dst_spec, &dst_data, &dst_len)) { + LOG_ERROR(Lib_Audio3d, "SDL_ConvertAudioSamples failed: {}", SDL_GetError()); + return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; } - queue.clear(); + // TODO: Implement asynchronous blocking mode. + const auto ret = AudioOut::sceAudioOutOutput(state->audio_out_handle, dst_data); + SDL_free(dst_data); - return ORBIS_OK; + return ret; } int PS4_SYSV_ABI sceAudio3dPortQueryDebug() { diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h index 114539160..d018dca3b 100644 --- a/src/core/libraries/audio3d/audio3d.h +++ b/src/core/libraries/audio3d/audio3d.h @@ -64,11 +64,15 @@ struct OrbisAudio3dAttribute { struct Port { OrbisAudio3dOpenParameters parameters{}; - std::deque queue; // Only stores PCM buffers for now + std::deque queue; // Only stores PCM buffers for now + + bool has_buffer{false}; + OrbisAudio3dPcm current_buffer{}; }; struct Audio3dState { std::unordered_map ports; + s32 audio_out_handle; }; int PS4_SYSV_ABI sceAudio3dAudioOutClose();