From ad8d6de8bca09585d8cbcdee37d9303574e95aa2 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 7 Mar 2026 21:07:14 +0000 Subject: [PATCH] native ps4 audio sink --- src/audio_core/CMakeLists.txt | 6 ++ src/audio_core/sink/ps4_sink.cpp | 156 +++++++++++++++++++++++++++ src/audio_core/sink/ps4_sink.h | 43 ++++++++ src/audio_core/sink/sink_details.cpp | 17 ++- src/common/settings_enums.h | 3 +- src/core/hle/service/services.cpp | 2 +- 6 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 src/audio_core/sink/ps4_sink.cpp create mode 100644 src/audio_core/sink/ps4_sink.h diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 22106fc74d..3a3f937107 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -256,4 +256,10 @@ else() target_compile_definitions(audio_core PRIVATE HAVE_SDL2) endif() +if(PLATFORM_PS4) + target_sources(audio_core PRIVATE + sink/ps4_sink.cpp + sink/ps4_sink.h) +endif() + create_target_directory_groups(audio_core) diff --git a/src/audio_core/sink/ps4_sink.cpp b/src/audio_core/sink/ps4_sink.cpp new file mode 100644 index 0000000000..42b151f49f --- /dev/null +++ b/src/audio_core/sink/ps4_sink.cpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include + +#include "audio_core/common/common.h" +#include "audio_core/sink/ps4_sink.h" +#include "audio_core/sink/sink_stream.h" +#include "common/logging/log.h" +#include "common/scope_exit.h" +#include "core/core.h" + +namespace AudioCore::Sink { + +/// @brief PS4 sink stream, responsible for sinking samples to hardware. +struct PS4SinkStream final : public SinkStream { + /// @brief Create a new sink stream. + /// @param device_channels_ - Number of channels supported by the hardware. + /// @param system_channels_ - Number of channels the audio systems expect. + /// @param output_device - Name of the output device to use for this stream. + /// @param input_device - Name of the input device to use for this stream. + /// @param type_ - Type of this stream. + /// @param system_ - Core system. + /// @param event - Event used only for audio renderer, signalled on buffer consume. + PS4SinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device, const std::string& input_device, StreamType type_, Core::System& system_) + : SinkStream{system_, type_} + { + system_channels = system_channels_; + device_channels = device_channels_; + + auto const length = 0x800; + auto const sample_rate = 48000; + auto const num_channels = this->GetDeviceChannels(); + output_buffer.resize(length * num_channels * sizeof(s16)); + + auto const param_type = num_channels == 1 ? ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_MONO : ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_STEREO; + audio_dev = sceAudioOutOpen(ORBIS_USER_SERVICE_USER_ID_SYSTEM, ORBIS_AUDIO_OUT_PORT_TYPE_MAIN, 0, length, sample_rate, param_type); + if (audio_dev > 0) { + audio_thread = std::jthread([=, this](std::stop_token stop_token) { + while (!stop_token.stop_requested()) { + if (this->type == StreamType::In) { + // this->ProcessAudioIn(input_buffer, length); + } else { + sceAudioOutOutput(audio_dev, nullptr); + this->ProcessAudioOutAndRender(output_buffer, length); + sceAudioOutOutput(audio_dev, output_buffer.data()); + } + } + }); + } else { + LOG_ERROR(Service_Audio, "Failed to create audio device! {:#x}", uint32_t(audio_dev)); + } + } + + ~PS4SinkStream() override { + LOG_DEBUG(Service_Audio, "Destroying PS4 stream {}", name); + sceAudioOutClose(audio_dev); + if (audio_thread.joinable()) { + audio_thread.request_stop(); + audio_thread.join(); + } + } + + void Finalize() override { + if (audio_dev > 0) { + Stop(); + sceAudioOutClose(audio_dev); + } + } + + void Start(bool resume = false) override { + if (audio_dev > 0 && paused) { + paused = false; + } + } + + void Stop() override { + if (audio_dev > 0 && !paused) { + + } + } + + std::vector output_buffer; + std::jthread audio_thread; + int32_t audio_dev{}; +}; + +PS4Sink::PS4Sink(std::string_view target_device_name) { + int32_t rc = sceAudioOutInit(); + if (rc == 0 || unsigned(rc) == ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT) { + if (target_device_name != auto_device_name && !target_device_name.empty()) { + output_device = target_device_name; + } else { + output_device.clear(); + } + device_channels = 2; + } else { + LOG_ERROR(Service_Audio, "Unable to open audio out! {:#x}", uint32_t(rc)); + } +} + +PS4Sink::~PS4Sink() = default; + +/// @brief Create a new sink stream. +/// @param system - Core system. +/// @param system_channels - Number of channels the audio system expects. May differ from the device's channel count. +/// @param name - Name of this stream. +/// @param type - Type of this stream, render/in/out. +/// @return A pointer to the created SinkStream +SinkStream* PS4Sink::AcquireSinkStream(Core::System& system, u32 system_channels_, const std::string&, StreamType type) { + system_channels = system_channels_; + SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique(device_channels, system_channels, output_device, input_device, type, system)); + return stream.get(); +} + +void PS4Sink::CloseStream(SinkStream* stream) { + for (size_t i = 0; i < sink_streams.size(); i++) { + if (sink_streams[i].get() == stream) { + sink_streams[i].reset(); + sink_streams.erase(sink_streams.begin() + i); + break; + } + } +} + +void PS4Sink::CloseStreams() { + sink_streams.clear(); +} + +f32 PS4Sink::GetDeviceVolume() const { + return sink_streams.size() > 0 ? sink_streams[0]->GetDeviceVolume() : 1.f; +} + +void PS4Sink::SetDeviceVolume(f32 volume) { + for (auto& stream : sink_streams) + stream->SetDeviceVolume(volume); +} + +void PS4Sink::SetSystemVolume(f32 volume) { + for (auto& stream : sink_streams) + stream->SetSystemVolume(volume); +} + +std::vector ListPS4SinkDevices(bool capture) { + return {{"Default"}}; +} + +u32 GetPS4Latency() { + return TargetSampleCount * 2; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/ps4_sink.h b/src/audio_core/sink/ps4_sink.h new file mode 100644 index 0000000000..cfc8c293bb --- /dev/null +++ b/src/audio_core/sink/ps4_sink.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/sink/sink.h" + +namespace Core { +class System; +} + +namespace AudioCore::Sink { +class SinkStream; + +/// @brief PS4 backend sink, holds multiple output streams and is responsible for sinking samples to +/// hardware. Used by Audio Render, Audio In and Audio Out. +struct PS4Sink final : public Sink { + explicit PS4Sink(std::string_view device_id); + ~PS4Sink() override; + SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, const std::string& name, StreamType type) override; + void CloseStream(SinkStream* stream) override; + void CloseStreams() override; + f32 GetDeviceVolume() const override; + void SetDeviceVolume(f32 volume) override; + void SetSystemVolume(f32 volume) override; + /// Name of the output device used by streams + std::string output_device; + /// Name of the input device used by streams + std::string input_device; + /// Vector of streams managed by this sink + std::vector sink_streams; +}; + +std::vector ListPS4SinkDevices(bool capture); +u32 GetPS4Latency(); + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 70bf75018b..d8a80b9319 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -19,6 +19,9 @@ #ifdef HAVE_SDL2 #include "audio_core/sink/sdl2_sink.h" #endif +#ifdef __OPENORBIS__ +#include "audio_core/sink/ps4_sink.h" +#endif #include "audio_core/sink/null_sink.h" #include "common/logging/log.h" #include "common/settings_enums.h" @@ -51,6 +54,16 @@ struct SinkDetails { // sink_details is ordered in terms of desirability, with the best choice at the top. constexpr SinkDetails sink_details[] = { +#ifdef __OPENORBIS__ + SinkDetails{ + Settings::AudioEngine::Ps4, + [](std::string_view device_id) -> std::unique_ptr { + return std::make_unique(device_id); + }, + &ListPS4SinkDevices, + &GetPS4Latency, + }, +#endif #ifdef HAVE_OBOE SinkDetails{ Settings::AudioEngine::Oboe, @@ -115,7 +128,9 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { // BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which // causes audio issues, in that case go with SDL. -#if defined(HAVE_CUBEB) && defined(HAVE_SDL2) +#if defined(__OPENORBIS__) + iter = find_backend(Settings::AudioEngine::Ps4); +#elif defined(HAVE_CUBEB) && defined(HAVE_SDL2) iter = find_backend(Settings::AudioEngine::Cubeb); if (iter->latency() > TargetSampleCount * 3) { iter = find_backend(Settings::AudioEngine::Sdl2); diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 638be4127f..d82f4dc90a 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -92,12 +92,13 @@ struct EnumMetadata { // AudioEngine must be specified discretely due to having existing but slightly different // canonicalizations // TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id -enum class AudioEngine : u32 { Auto, Cubeb, Sdl2, Null, Oboe, }; +enum class AudioEngine : u32 { Auto, Cubeb, Sdl2, Null, Oboe, Ps4 }; template<> inline std::vector> EnumMetadata::Canonicalizations() { return { {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2}, {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe}, + {"ps4", AudioEngine::Ps4}, }; } /// @brief This is just a sufficiently large number that is more than the number of other enums declared here diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index 4d04b0fdf0..bf0a89dfef 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -100,7 +100,7 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"nvservices", &Nvidia::LoopProcess}, {"bsdsocket", &Sockets::LoopProcess}, }) { - if (run_on_host) kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach(); + if (run_on_host) kernel.RunOnHostCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }).detach(); else kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }); } if (run_on_host) kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();