// SPDX-FileCopyrightText: Copyright 2026 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/alignment.h" #include "common/logging.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_; audio_dev = sceAudioOutOpen(ORBIS_USER_SERVICE_USER_ID_SYSTEM, ORBIS_AUDIO_OUT_PORT_TYPE_MAIN, 0, Common::AlignUp(TargetSampleCount, 0x100), TargetSampleRate, device_channels > 1 ? ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_STEREO : ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_MONO); if (audio_dev > 0) { audio_thread = std::jthread([=, this](std::stop_token stop_token) { std::array buffer; while (!stop_token.stop_requested()) { if (this->type == StreamType::In) { this->ProcessAudioIn(buffer, TargetSampleCount); (void)buffer; // TODO: microphone support } else { int err = 0; std::fill(buffer.begin(), buffer.end(), 0); this->ProcessAudioOutAndRender(buffer, TargetSampleCount); sceAudioOutOutput(audio_dev, nullptr); if ((err = sceAudioOutOutput(audio_dev, buffer.data())) < 0) LOG_ERROR(Service_Audio, "{}", err); } // Wait for pause, we don't really have a native pause // so this is the best i can do while (paused && !stop_token.stop_requested()) ; } sceAudioOutClose(audio_dev); }); } 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); if (audio_thread.joinable()) { audio_thread.request_stop(); audio_thread.join(); } } void Finalize() override { if (audio_dev > 0) { } } void Start(bool resume = false) override { if (audio_dev > 0 && paused) { paused = false; } } void Stop() override { if (audio_dev > 0 && !paused) { SignalPause(); } } 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; } } // namespace AudioCore::Sink