diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ee43a13d..c4443a976 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,9 +202,7 @@ set(AUDIO_LIB src/core/libraries/audio/audioin.cpp src/core/libraries/audio/audioout_backend.h src/core/libraries/audio/audioout_error.h src/core/libraries/audio/cubeb_audio.cpp - src/core/libraries/audio/cubeb_audio.h src/core/libraries/audio/sdl_audio.cpp - src/core/libraries/audio/sdl_audio.h src/core/libraries/ngs2/ngs2.cpp src/core/libraries/ngs2/ngs2.h ) diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 2e77a30a6..33e1d3302 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -10,9 +10,8 @@ #include "common/config.h" #include "common/logging/log.h" #include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_backend.h" #include "core/libraries/audio/audioout_error.h" -#include "core/libraries/audio/cubeb_audio.h" -#include "core/libraries/audio/sdl_audio.h" #include "core/libraries/libs.h" namespace Libraries::AudioOut { @@ -177,13 +176,11 @@ int PS4_SYSV_ABI sceAudioOutClose(s32 handle) { std::scoped_lock lock(ports_mutex); auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - audio->Close(port.impl); port.impl = nullptr; - port.is_open = false; return ORBIS_OK; } @@ -254,7 +251,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta std::scoped_lock lock(ports_mutex); const auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } @@ -398,13 +395,13 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, } std::scoped_lock lock{ports_mutex}; - const auto port = std::ranges::find(ports_out, false, &PortOut::is_open); + const auto port = + std::ranges::find_if(ports_out, [&](const PortOut& p) { return p.impl == nullptr; }); if (port == ports_out.end()) { LOG_ERROR(Lib_AudioOut, "Audio ports are full"); return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; } - port->is_open = true; port->type = port_type; port->samples_num = length; port->freq = sample_rate; @@ -413,8 +410,8 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, port->channels_num = GetFormatNumChannels(format); port->sample_size = GetFormatSampleSize(format); port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB); - port->impl = audio->Open(*port); + return std::distance(ports_out.begin(), port) + 1; } @@ -433,12 +430,12 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) { } auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } const size_t data_size = port.samples_num * port.sample_size * port.channels_num; - audio->Output(port.impl, ptr, data_size); + port.impl->Output(ptr, data_size); return ORBIS_OK; } @@ -547,7 +544,7 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { std::scoped_lock lock(ports_mutex); auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } @@ -578,7 +575,7 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { } } - audio->SetVolume(port.impl, port.volume); + port.impl->SetVolume(port.volume); return ORBIS_OK; } diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index 7a60567ff..3471315ba 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -3,12 +3,15 @@ #pragma once -#include "common/bit_field.h" +#include +#include "common/bit_field.h" #include "core/libraries/system/userservice.h" namespace Libraries::AudioOut { +class PortBackend; + // Main up to 8 ports, BGM 1 port, voice up to 4 ports, // personal up to 4 ports, padspk up to 5 ports, aux 1 port constexpr int SCE_AUDIO_OUT_NUM_PORTS = 22; @@ -57,7 +60,8 @@ struct OrbisAudioOutPortState { }; struct PortOut { - void* impl; + std::unique_ptr impl{}; + u32 samples_num; u32 freq; OrbisAudioOutParamFormat format; @@ -66,7 +70,6 @@ struct PortOut { bool is_float; std::array volume; u8 sample_size; - bool is_open; }; int PS4_SYSV_ABI sceAudioOutDeviceIdOpen(); diff --git a/src/core/libraries/audio/audioout_backend.h b/src/core/libraries/audio/audioout_backend.h index bf612401e..ecc4cf7c6 100644 --- a/src/core/libraries/audio/audioout_backend.h +++ b/src/core/libraries/audio/audioout_backend.h @@ -3,19 +3,42 @@ #pragma once +typedef struct cubeb cubeb; + namespace Libraries::AudioOut { struct PortOut; +class PortBackend { +public: + virtual ~PortBackend() = default; + + virtual void Output(void* ptr, size_t size) = 0; + virtual void SetVolume(const std::array& ch_volumes) = 0; +}; + class AudioOutBackend { public: AudioOutBackend() = default; virtual ~AudioOutBackend() = default; - virtual void* Open(PortOut& port) = 0; - virtual void Close(void* impl) = 0; - virtual void Output(void* impl, void* ptr, size_t size) = 0; - virtual void SetVolume(void* impl, const std::array& ch_volumes) = 0; + virtual std::unique_ptr Open(PortOut& port) = 0; +}; + +class CubebAudioOut final : public AudioOutBackend { +public: + CubebAudioOut(); + ~CubebAudioOut() override; + + std::unique_ptr Open(PortOut& port) override; + +private: + cubeb* ctx = nullptr; +}; + +class SDLAudioOut final : public AudioOutBackend { +public: + std::unique_ptr Open(PortOut& port) override; }; } // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/cubeb_audio.cpp b/src/core/libraries/audio/cubeb_audio.cpp index 949e0e40a..448b80624 100644 --- a/src/core/libraries/audio/cubeb_audio.cpp +++ b/src/core/libraries/audio/cubeb_audio.cpp @@ -7,48 +7,49 @@ #include "common/assert.h" #include "common/ringbuffer.h" #include "core/libraries/audio/audioout.h" -#include "core/libraries/audio/cubeb_audio.h" +#include "core/libraries/audio/audioout_backend.h" namespace Libraries::AudioOut { constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold -static cubeb_channel_layout GetCubebChannelLayout(int num_channels) { - switch (num_channels) { - case 1: - return CUBEB_LAYOUT_MONO; - case 2: - return CUBEB_LAYOUT_STEREO; - case 8: - return CUBEB_LAYOUT_3F4_LFE; - default: - UNREACHABLE(); - } -} - -struct CubebStream { - cubeb_stream* stream{}; - size_t frame_size; - ring_buffer_base buffer; - - CubebStream(cubeb* ctx, const PortOut& port) +class CubebPortBackend : public PortBackend { +public: + CubebPortBackend(cubeb* ctx, const PortOut& port) : frame_size(port.channels_num * port.sample_size), buffer(static_cast(port.samples_num * frame_size) * 4) { if (!ctx) { return; } + const auto get_channel_layout = [&port] -> cubeb_channel_layout { + switch (port.channels_num) { + case 1: + return CUBEB_LAYOUT_MONO; + case 2: + return CUBEB_LAYOUT_STEREO; + case 8: + return CUBEB_LAYOUT_3F4_LFE; + default: + UNREACHABLE(); + } + }; cubeb_stream_params stream_params = { .format = port.is_float ? CUBEB_SAMPLE_FLOAT32LE : CUBEB_SAMPLE_S16LE, .rate = port.freq, .channels = static_cast(port.channels_num), - .layout = GetCubebChannelLayout(port.channels_num), + .layout = get_channel_layout(), + .prefs = CUBEB_STREAM_PREF_NONE, }; u32 latency_frames = 512; if (const auto ret = cubeb_get_min_latency(ctx, &stream_params, &latency_frames); ret != CUBEB_OK) { - LOG_WARNING(Lib_AudioOut, "Could not get minimum cubeb audio latency: {}", ret); + LOG_WARNING(Lib_AudioOut, + "Could not get minimum cubeb audio latency, falling back to default: {}", + ret); } - if (const auto ret = cubeb_stream_init(ctx, &stream, "shadPS4", nullptr, nullptr, nullptr, + char stream_name[64]; + snprintf(stream_name, sizeof(stream_name), "shadPS4 stream %p", this); + if (const auto ret = cubeb_stream_init(ctx, &stream, stream_name, nullptr, nullptr, nullptr, &stream_params, latency_frames, &DataCallback, &StateCallback, this); ret != CUBEB_OK) { @@ -61,7 +62,7 @@ struct CubebStream { } } - ~CubebStream() { + ~CubebPortBackend() override { if (!stream) { return; } @@ -72,7 +73,7 @@ struct CubebStream { stream = nullptr; } - void Output(void* ptr, size_t size) { + void Output(void* ptr, size_t size) override { auto* data = static_cast(ptr); while (size > 0) { const auto queued = buffer.enqueue(data, static_cast(size)); @@ -86,7 +87,7 @@ struct CubebStream { } } - void SetVolume(const std::array& ch_volumes) { + void SetVolume(const std::array& ch_volumes) override { if (!stream) { return; } @@ -99,9 +100,10 @@ struct CubebStream { } } +private: static long DataCallback(cubeb_stream* stream, void* user_data, const void* in, void* out, long num_frames) { - auto* stream_data = static_cast(user_data); + auto* stream_data = static_cast(user_data); const auto out_data = static_cast(out); const auto requested_size = static_cast(num_frames * stream_data->frame_size); const auto dequeued_size = stream_data->buffer.dequeue(out_data, requested_size); @@ -128,10 +130,14 @@ struct CubebStream { break; } } + + size_t frame_size; + ring_buffer_base buffer; + cubeb_stream* stream{}; }; CubebAudioOut::CubebAudioOut() { - cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback); + cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr); if (const auto ret = cubeb_init(&ctx, "shadPS4", nullptr); ret != CUBEB_OK) { LOG_CRITICAL(Lib_AudioOut, "Failed to create cubeb context: {}", ret); } @@ -145,38 +151,8 @@ CubebAudioOut::~CubebAudioOut() { ctx = nullptr; } -void* CubebAudioOut::Open(PortOut& port) { - return new CubebStream(ctx, port); -} - -void CubebAudioOut::Close(void* impl) { - if (!impl) { - return; - } - delete static_cast(impl); -} - -void CubebAudioOut::Output(void* impl, void* ptr, size_t size) { - if (!impl) { - return; - } - static_cast(impl)->Output(ptr, size); -} - -void CubebAudioOut::SetVolume(void* impl, const std::array& ch_volumes) { - if (!impl) { - return; - } - static_cast(impl)->SetVolume(ch_volumes); -} - -void CubebAudioOut::LogCallback(const char* format, ...) { - std::array buffer{}; - std::va_list args; - va_start(args, format); - vsnprintf(buffer.data(), buffer.size(), format, args); - va_end(args); - LOG_DEBUG(Lib_Audio3d, "[cubeb] {}", buffer.data()); +std::unique_ptr CubebAudioOut::Open(PortOut& port) { + return std::make_unique(ctx, port); } } // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/cubeb_audio.h b/src/core/libraries/audio/cubeb_audio.h deleted file mode 100644 index eff5be5ba..000000000 --- a/src/core/libraries/audio/cubeb_audio.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" -#include "core/libraries/audio/audioout_backend.h" - -typedef struct cubeb cubeb; - -namespace Libraries::AudioOut { - -class CubebAudioOut final : public AudioOutBackend { -public: - CubebAudioOut(); - ~CubebAudioOut(); - - void* Open(PortOut& port) override; - void Close(void* impl) override; - void Output(void* impl, void* ptr, size_t size) override; - void SetVolume(void* impl, const std::array& ch_volumes) override; - -private: - static void LogCallback(const char* format, ...); - - cubeb* ctx = nullptr; -}; - -} // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp index 89c8fd7f8..7d7a7cee5 100644 --- a/src/core/libraries/audio/sdl_audio.cpp +++ b/src/core/libraries/audio/sdl_audio.cpp @@ -1,45 +1,60 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include #include -#include -#include "common/assert.h" +#include "common/logging/log.h" #include "core/libraries/audio/audioout.h" -#include "core/libraries/audio/sdl_audio.h" +#include "core/libraries/audio/audioout_backend.h" namespace Libraries::AudioOut { constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold -void* SDLAudioOut::Open(PortOut& port) { - SDL_AudioSpec fmt; - SDL_zero(fmt); - fmt.format = port.is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16; - fmt.channels = port.channels_num; - fmt.freq = static_cast(port.freq); - - auto* stream = - SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); - SDL_ResumeAudioStreamDevice(stream); - return stream; -} - -void SDLAudioOut::Close(void* impl) { - SDL_DestroyAudioStream(static_cast(impl)); -} - -void SDLAudioOut::Output(void* impl, void* ptr, size_t size) { - auto* stream = static_cast(impl); - SDL_PutAudioStreamData(stream, ptr, size); - while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { - SDL_Delay(0); +class SDLPortBackend : public PortBackend { +public: + explicit SDLPortBackend(const PortOut& port) { + const SDL_AudioSpec fmt = { + .format = port.is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16, + .channels = port.channels_num, + .freq = static_cast(port.freq), + }; + stream = + SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); + if (stream == nullptr) { + LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError()); + } + SDL_ResumeAudioStreamDevice(stream); } -} -void SDLAudioOut::SetVolume(void* impl, const std::array& ch_volumes) { - // Not yet implemented + ~SDLPortBackend() override { + if (stream) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + } + } + + void Output(void* ptr, size_t size) override { + SDL_PutAudioStreamData(stream, ptr, static_cast(size)); + while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { + // Yield to allow the stream to drain. + std::this_thread::yield(); + } + } + + void SetVolume(const std::array& ch_volumes) override { + // TODO: Not yet implemented + } + +private: + SDL_AudioStream* stream; +}; + +std::unique_ptr SDLAudioOut::Open(PortOut& port) { + return std::make_unique(port); } } // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.h b/src/core/libraries/audio/sdl_audio.h deleted file mode 100644 index 7a9edfffb..000000000 --- a/src/core/libraries/audio/sdl_audio.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" -#include "core/libraries/audio/audioout_backend.h" - -namespace Libraries::AudioOut { - -class SDLAudioOut final : public AudioOutBackend { -public: - void* Open(PortOut& port) override; - void Close(void* impl) override; - void Output(void* impl, void* ptr, size_t size) override; - void SetVolume(void* impl, const std::array& ch_volumes) override; -}; - -} // namespace Libraries::AudioOut