mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-23 05:38:59 +02:00
native ps4 audio sink
This commit is contained in:
parent
8275903b65
commit
ad8d6de8bc
6 changed files with 224 additions and 3 deletions
|
|
@ -256,4 +256,10 @@ else()
|
||||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(PLATFORM_PS4)
|
||||||
|
target_sources(audio_core PRIVATE
|
||||||
|
sink/ps4_sink.cpp
|
||||||
|
sink/ps4_sink.h)
|
||||||
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(audio_core)
|
create_target_directory_groups(audio_core)
|
||||||
|
|
|
||||||
156
src/audio_core/sink/ps4_sink.cpp
Normal file
156
src/audio_core/sink/ps4_sink.cpp
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <stop_token>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <orbis/AudioOut.h>
|
||||||
|
|
||||||
|
#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<s16> 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<PS4SinkStream>(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<std::string> ListPS4SinkDevices(bool capture) {
|
||||||
|
return {{"Default"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetPS4Latency() {
|
||||||
|
return TargetSampleCount * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioCore::Sink
|
||||||
43
src/audio_core/sink/ps4_sink.h
Normal file
43
src/audio_core/sink/ps4_sink.h
Normal file
|
|
@ -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 <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<SinkStreamPtr> sink_streams;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> ListPS4SinkDevices(bool capture);
|
||||||
|
u32 GetPS4Latency();
|
||||||
|
|
||||||
|
} // namespace AudioCore::Sink
|
||||||
|
|
@ -19,6 +19,9 @@
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
#include "audio_core/sink/sdl2_sink.h"
|
#include "audio_core/sink/sdl2_sink.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef __OPENORBIS__
|
||||||
|
#include "audio_core/sink/ps4_sink.h"
|
||||||
|
#endif
|
||||||
#include "audio_core/sink/null_sink.h"
|
#include "audio_core/sink/null_sink.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/settings_enums.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.
|
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||||
constexpr SinkDetails sink_details[] = {
|
constexpr SinkDetails sink_details[] = {
|
||||||
|
#ifdef __OPENORBIS__
|
||||||
|
SinkDetails{
|
||||||
|
Settings::AudioEngine::Ps4,
|
||||||
|
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||||
|
return std::make_unique<PS4Sink>(device_id);
|
||||||
|
},
|
||||||
|
&ListPS4SinkDevices,
|
||||||
|
&GetPS4Latency,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
#ifdef HAVE_OBOE
|
#ifdef HAVE_OBOE
|
||||||
SinkDetails{
|
SinkDetails{
|
||||||
Settings::AudioEngine::Oboe,
|
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
|
// 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
|
// Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
|
||||||
// causes audio issues, in that case go with SDL.
|
// 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);
|
iter = find_backend(Settings::AudioEngine::Cubeb);
|
||||||
if (iter->latency() > TargetSampleCount * 3) {
|
if (iter->latency() > TargetSampleCount * 3) {
|
||||||
iter = find_backend(Settings::AudioEngine::Sdl2);
|
iter = find_backend(Settings::AudioEngine::Sdl2);
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,13 @@ struct EnumMetadata {
|
||||||
// AudioEngine must be specified discretely due to having existing but slightly different
|
// AudioEngine must be specified discretely due to having existing but slightly different
|
||||||
// canonicalizations
|
// canonicalizations
|
||||||
// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
|
// 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<>
|
template<>
|
||||||
inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() {
|
inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() {
|
||||||
return {
|
return {
|
||||||
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
|
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
|
||||||
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
|
{"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
|
/// @brief This is just a sufficiently large number that is more than the number of other enums declared here
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||||
{"nvservices", &Nvidia::LoopProcess},
|
{"nvservices", &Nvidia::LoopProcess},
|
||||||
{"bsdsocket", &Sockets::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); });
|
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();
|
if (run_on_host) kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue