audioout: Refactor backend ports into class.

This commit is contained in:
squidbus 2024-12-26 13:53:44 -08:00
parent dc0725aa91
commit a7f8aae2be
8 changed files with 122 additions and 158 deletions

View File

@ -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
)

View File

@ -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;
}

View File

@ -3,12 +3,15 @@
#pragma once
#include "common/bit_field.h"
#include <memory>
#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<PortBackend> impl{};
u32 samples_num;
u32 freq;
OrbisAudioOutParamFormat format;
@ -66,7 +70,6 @@ struct PortOut {
bool is_float;
std::array<int, 8> volume;
u8 sample_size;
bool is_open;
};
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen();

View File

@ -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<int, 8>& 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<int, 8>& ch_volumes) = 0;
virtual std::unique_ptr<PortBackend> Open(PortOut& port) = 0;
};
class CubebAudioOut final : public AudioOutBackend {
public:
CubebAudioOut();
~CubebAudioOut() override;
std::unique_ptr<PortBackend> Open(PortOut& port) override;
private:
cubeb* ctx = nullptr;
};
class SDLAudioOut final : public AudioOutBackend {
public:
std::unique_ptr<PortBackend> Open(PortOut& port) override;
};
} // namespace Libraries::AudioOut

View File

@ -7,14 +7,22 @@
#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) {
class CubebPortBackend : public PortBackend {
public:
CubebPortBackend(cubeb* ctx, const PortOut& port)
: frame_size(port.channels_num * port.sample_size),
buffer(static_cast<int>(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:
@ -24,31 +32,24 @@ static cubeb_channel_layout GetCubebChannelLayout(int num_channels) {
default:
UNREACHABLE();
}
}
struct CubebStream {
cubeb_stream* stream{};
size_t frame_size;
ring_buffer_base<u8> buffer;
CubebStream(cubeb* ctx, const PortOut& port)
: frame_size(port.channels_num * port.sample_size),
buffer(static_cast<int>(port.samples_num * frame_size) * 4) {
if (!ctx) {
return;
}
};
cubeb_stream_params stream_params = {
.format = port.is_float ? CUBEB_SAMPLE_FLOAT32LE : CUBEB_SAMPLE_S16LE,
.rate = port.freq,
.channels = static_cast<u32>(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<u8*>(ptr);
while (size > 0) {
const auto queued = buffer.enqueue(data, static_cast<int>(size));
@ -86,7 +87,7 @@ struct CubebStream {
}
}
void SetVolume(const std::array<int, 8>& ch_volumes) {
void SetVolume(const std::array<int, 8>& 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<CubebStream*>(user_data);
auto* stream_data = static_cast<CubebPortBackend*>(user_data);
const auto out_data = static_cast<u8*>(out);
const auto requested_size = static_cast<int>(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<u8> 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<CubebStream*>(impl);
}
void CubebAudioOut::Output(void* impl, void* ptr, size_t size) {
if (!impl) {
return;
}
static_cast<CubebStream*>(impl)->Output(ptr, size);
}
void CubebAudioOut::SetVolume(void* impl, const std::array<int, 8>& ch_volumes) {
if (!impl) {
return;
}
static_cast<CubebStream*>(impl)->SetVolume(ch_volumes);
}
void CubebAudioOut::LogCallback(const char* format, ...) {
std::array<char, 512> 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<PortBackend> CubebAudioOut::Open(PortOut& port) {
return std::make_unique<CubebPortBackend>(ctx, port);
}
} // namespace Libraries::AudioOut

View File

@ -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<int, 8>& ch_volumes) override;
private:
static void LogCallback(const char* format, ...);
cubeb* ctx = nullptr;
};
} // namespace Libraries::AudioOut

View File

@ -1,45 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include <SDL3/SDL_audio.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_timer.h>
#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<int>(port.freq);
auto* stream =
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<int>(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);
return stream;
}
void SDLAudioOut::Close(void* impl) {
SDL_DestroyAudioStream(static_cast<SDL_AudioStream*>(impl));
~SDLPortBackend() override {
if (stream) {
SDL_DestroyAudioStream(stream);
stream = nullptr;
}
}
void SDLAudioOut::Output(void* impl, void* ptr, size_t size) {
auto* stream = static_cast<SDL_AudioStream*>(impl);
SDL_PutAudioStreamData(stream, ptr, size);
void Output(void* ptr, size_t size) override {
SDL_PutAudioStreamData(stream, ptr, static_cast<int>(size));
while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
SDL_Delay(0);
// Yield to allow the stream to drain.
std::this_thread::yield();
}
}
void SDLAudioOut::SetVolume(void* impl, const std::array<int, 8>& ch_volumes) {
// Not yet implemented
void SetVolume(const std::array<int, 8>& ch_volumes) override {
// TODO: Not yet implemented
}
private:
SDL_AudioStream* stream;
};
std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
return std::make_unique<SDLPortBackend>(port);
}
} // namespace Libraries::AudioOut

View File

@ -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<int, 8>& ch_volumes) override;
};
} // namespace Libraries::AudioOut