audio: Move port logic out of SDL backend and define backend interface. (#1848)

This commit is contained in:
squidbus 2024-12-22 01:46:28 -08:00 committed by GitHub
parent 0931802151
commit fac21a5362
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 203 additions and 156 deletions

View file

@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <magic_enum/magic_enum.hpp>
#include "common/assert.h"
@ -13,7 +15,22 @@
namespace Libraries::AudioOut {
static std::unique_ptr<SDLAudioOut> audio;
struct PortOut {
void* impl;
u32 samples_num;
u32 freq;
OrbisAudioOutParamFormat format;
OrbisAudioOutPort type;
int channels_num;
bool is_float;
std::array<int, 8> volume;
u8 sample_size;
bool is_open;
};
std::shared_mutex ports_mutex;
std::array<PortOut, SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
static std::unique_ptr<AudioOutBackend> audio;
static std::string_view GetAudioOutPort(OrbisAudioOutPort port) {
switch (port) {
@ -70,6 +87,58 @@ static std::string_view GetAudioOutParamAttr(OrbisAudioOutParamAttr attr) {
}
}
static bool IsFormatFloat(const OrbisAudioOutParamFormat format) {
switch (format) {
case OrbisAudioOutParamFormat::S16Mono:
case OrbisAudioOutParamFormat::S16Stereo:
case OrbisAudioOutParamFormat::S16_8CH:
case OrbisAudioOutParamFormat::S16_8CH_Std:
return false;
case OrbisAudioOutParamFormat::FloatMono:
case OrbisAudioOutParamFormat::FloatStereo:
case OrbisAudioOutParamFormat::Float_8CH:
case OrbisAudioOutParamFormat::Float_8CH_Std:
return true;
default:
UNREACHABLE_MSG("Unknown format");
}
}
static int GetFormatNumChannels(const OrbisAudioOutParamFormat format) {
switch (format) {
case OrbisAudioOutParamFormat::S16Mono:
case OrbisAudioOutParamFormat::FloatMono:
return 1;
case OrbisAudioOutParamFormat::S16Stereo:
case OrbisAudioOutParamFormat::FloatStereo:
return 2;
case OrbisAudioOutParamFormat::S16_8CH:
case OrbisAudioOutParamFormat::Float_8CH:
case OrbisAudioOutParamFormat::S16_8CH_Std:
case OrbisAudioOutParamFormat::Float_8CH_Std:
return 8;
default:
UNREACHABLE_MSG("Unknown format");
}
}
static u8 GetFormatSampleSize(const OrbisAudioOutParamFormat format) {
switch (format) {
case OrbisAudioOutParamFormat::S16Mono:
case OrbisAudioOutParamFormat::S16Stereo:
case OrbisAudioOutParamFormat::S16_8CH:
case OrbisAudioOutParamFormat::S16_8CH_Std:
return 2;
case OrbisAudioOutParamFormat::FloatMono:
case OrbisAudioOutParamFormat::FloatStereo:
case OrbisAudioOutParamFormat::Float_8CH:
case OrbisAudioOutParamFormat::Float_8CH_Std:
return 4;
default:
UNREACHABLE_MSG("Unknown format");
}
}
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
@ -110,8 +179,21 @@ int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAudioOutClose() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
LOG_INFO(Lib_AudioOut, "handle = {}", handle);
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
std::scoped_lock lock(ports_mutex);
auto& port = ports_out.at(handle - 1);
if (!port.is_open) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
audio->Close(port.impl);
port.impl = nullptr;
port.is_open = false;
return ORBIS_OK;
}
@ -180,16 +262,21 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
const auto [type, channels_num] = audio->GetStatus(handle);
std::scoped_lock lock(ports_mutex);
const auto& port = ports_out.at(handle - 1);
if (!port.is_open) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
state->rerouteCounter = 0;
state->volume = 127;
switch (type) {
switch (port.type) {
case OrbisAudioOutPort::Main:
case OrbisAudioOutPort::Bgm:
case OrbisAudioOutPort::Voice:
state->output = 1;
state->channel = (channels_num > 2 ? 2 : channels_num);
state->channel = port.channels_num > 2 ? 2 : port.channels_num;
break;
case OrbisAudioOutPort::Personal:
case OrbisAudioOutPort::Padspk:
@ -276,7 +363,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
u32 sample_rate,
OrbisAudioOutParamExtendedInformation param_type) {
LOG_INFO(Lib_AudioOut,
"AudioOutOpen id = {} port_type = {} index = {} lenght= {} sample_rate = {} "
"id = {} port_type = {} index = {} length = {} sample_rate = {} "
"param_type = {} attr = {}",
user_id, GetAudioOutPort(port_type), index, length, sample_rate,
GetAudioOutParamFormat(param_type.data_format),
@ -310,7 +397,26 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
LOG_ERROR(Lib_AudioOut, "Invalid format attribute");
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
}
return audio->Open(port_type, length, sample_rate, format);
std::scoped_lock lock{ports_mutex};
const auto port = std::ranges::find(ports_out, false, &PortOut::is_open);
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;
port->format = format;
port->is_float = IsFormatFloat(format);
port->channels_num = GetFormatNumChannels(format);
port->sample_size = GetFormatSampleSize(format);
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
port->impl = audio->Open(port->is_float, port->channels_num, port->freq);
return std::distance(ports_out.begin(), port) + 1;
}
int PS4_SYSV_ABI sceAudioOutOpenEx() {
@ -326,7 +432,15 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) {
// Nothing to output
return ORBIS_OK;
}
return audio->Output(handle, ptr);
auto& port = ports_out.at(handle - 1);
if (!port.is_open) {
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);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
@ -431,7 +545,42 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
return audio->SetVolume(handle, flag, vol);
std::scoped_lock lock(ports_mutex);
auto& port = ports_out.at(handle - 1);
if (!port.is_open) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
for (int i = 0; i < port.channels_num; i++, flag >>= 1u) {
auto bit = flag & 0x1u;
if (bit == 1) {
int src_index = i;
if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std ||
port.format == OrbisAudioOutParamFormat::S16_8CH_Std) {
switch (i) {
case 4:
src_index = 6;
break;
case 5:
src_index = 7;
break;
case 6:
src_index = 4;
break;
case 7:
src_index = 5;
break;
default:
break;
}
}
port.volume[i] = vol[src_index];
}
}
audio->SetVolume(port.impl, port.volume);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAudioOutSetVolumeDown() {

View file

@ -64,7 +64,7 @@ int PS4_SYSV_ABI sceAudioOutA3dExit();
int PS4_SYSV_ABI sceAudioOutA3dInit();
int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
int PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
int PS4_SYSV_ABI sceAudioOutClose();
int PS4_SYSV_ABI sceAudioOutClose(s32 handle);
int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
int PS4_SYSV_ABI sceAudioOutExGetSystemInfo();

View file

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace Libraries::AudioOut {
class AudioOutBackend {
public:
AudioOutBackend() = default;
virtual ~AudioOutBackend() = default;
virtual void* Open(bool is_float, int num_channels, u32 sample_rate) = 0;
virtual void Close(void* impl) = 0;
virtual void Output(void* impl, const void* ptr, size_t size) = 0;
virtual void SetVolume(void* impl, std::array<int, 8> ch_volumes) = 0;
};
} // namespace Libraries::AudioOut

View file

@ -1,141 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include <SDL3/SDL_audio.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_timer.h>
#include "common/assert.h"
#include "core/libraries/audio/audioout_error.h"
#include "core/libraries/audio/sdl_audio.h"
namespace Libraries::AudioOut {
constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold
s32 SDLAudioOut::Open(OrbisAudioOutPort type, u32 samples_num, u32 freq,
OrbisAudioOutParamFormat format) {
std::scoped_lock lock{m_mutex};
const auto port = std::ranges::find(ports_out, false, &PortOut::is_open);
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 = type;
port->samples_num = samples_num;
port->freq = freq;
port->format = format;
SDL_AudioFormat sampleFormat;
switch (format) {
case OrbisAudioOutParamFormat::S16Mono:
sampleFormat = SDL_AUDIO_S16;
port->channels_num = 1;
port->sample_size = 2;
break;
case OrbisAudioOutParamFormat::FloatMono:
sampleFormat = SDL_AUDIO_F32;
port->channels_num = 1;
port->sample_size = 4;
break;
case OrbisAudioOutParamFormat::S16Stereo:
sampleFormat = SDL_AUDIO_S16;
port->channels_num = 2;
port->sample_size = 2;
break;
case OrbisAudioOutParamFormat::FloatStereo:
sampleFormat = SDL_AUDIO_F32;
port->channels_num = 2;
port->sample_size = 4;
break;
case OrbisAudioOutParamFormat::S16_8CH:
sampleFormat = SDL_AUDIO_S16;
port->channels_num = 8;
port->sample_size = 2;
break;
case OrbisAudioOutParamFormat::Float_8CH:
sampleFormat = SDL_AUDIO_F32;
port->channels_num = 8;
port->sample_size = 4;
break;
case OrbisAudioOutParamFormat::S16_8CH_Std:
sampleFormat = SDL_AUDIO_S16;
port->channels_num = 8;
port->sample_size = 2;
break;
case OrbisAudioOutParamFormat::Float_8CH_Std:
sampleFormat = SDL_AUDIO_F32;
port->channels_num = 8;
port->sample_size = 4;
break;
default:
UNREACHABLE_MSG("Unknown format");
}
port->volume.fill(Libraries::AudioOut::SCE_AUDIO_OUT_VOLUME_0DB);
void* SDLAudioOut::Open(bool is_float, int num_channels, u32 sample_rate) {
SDL_AudioSpec fmt;
SDL_zero(fmt);
fmt.format = sampleFormat;
fmt.channels = port->channels_num;
fmt.freq = freq;
port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL);
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(port->stream));
return std::distance(ports_out.begin(), port) + 1;
fmt.format = is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16;
fmt.channels = num_channels;
fmt.freq = sample_rate;
auto* stream =
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr);
SDL_ResumeAudioStreamDevice(stream);
return stream;
}
s32 SDLAudioOut::Output(s32 handle, const void* ptr) {
auto& port = ports_out.at(handle - 1);
if (!port.is_open) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
void SDLAudioOut::Close(void* impl) {
SDL_DestroyAudioStream(static_cast<SDL_AudioStream*>(impl));
}
const size_t data_size = port.samples_num * port.sample_size * port.channels_num;
bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size);
while (SDL_GetAudioStreamAvailable(port.stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
void SDLAudioOut::Output(void* impl, const void* ptr, size_t size) {
auto* stream = static_cast<SDL_AudioStream*>(impl);
SDL_PutAudioStreamData(stream, ptr, size);
while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
SDL_Delay(0);
}
return result ? ORBIS_OK : -1;
}
s32 SDLAudioOut::SetVolume(s32 handle, s32 bitflag, s32* volume) {
using Libraries::AudioOut::OrbisAudioOutParamFormat;
auto& port = ports_out.at(handle - 1);
if (!port.is_open) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) {
auto bit = bitflag & 0x1u;
if (bit == 1) {
int src_index = i;
if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std ||
port.format == OrbisAudioOutParamFormat::S16_8CH_Std) {
switch (i) {
case 4:
src_index = 6;
break;
case 5:
src_index = 7;
break;
case 6:
src_index = 4;
break;
case 7:
src_index = 5;
break;
default:
break;
}
}
port.volume[i] = volume[src_index];
}
}
return ORBIS_OK;
void SDLAudioOut::SetVolume(void* impl, std::array<int, 8> ch_volumes) {
// Not yet implemented
}
} // namespace Libraries::AudioOut

View file

@ -3,40 +3,16 @@
#pragma once
#include <shared_mutex>
#include <SDL3/SDL_audio.h>
#include "core/libraries/audio/audioout.h"
#include "core/libraries/audio/audioout_backend.h"
namespace Libraries::AudioOut {
class SDLAudioOut {
class SDLAudioOut final : public AudioOutBackend {
public:
explicit SDLAudioOut() = default;
~SDLAudioOut() = default;
s32 Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, OrbisAudioOutParamFormat format);
s32 Output(s32 handle, const void* ptr);
s32 SetVolume(s32 handle, s32 bitflag, s32* volume);
constexpr std::pair<OrbisAudioOutPort, int> GetStatus(s32 handle) const {
const auto& port = ports_out.at(handle - 1);
return std::make_pair(port.type, port.channels_num);
}
private:
struct PortOut {
SDL_AudioStream* stream;
u32 samples_num;
u32 freq;
OrbisAudioOutParamFormat format;
OrbisAudioOutPort type;
int channels_num;
std::array<int, 8> volume;
u8 sample_size;
bool is_open;
};
std::shared_mutex m_mutex;
std::array<PortOut, Libraries::AudioOut::SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
void* Open(bool is_float, int num_channels, u32 sample_rate) override;
void Close(void* impl) override;
void Output(void* impl, const void* ptr, size_t size) override;
void SetVolume(void* impl, std::array<int, 8> ch_volumes) override;
};
} // namespace Libraries::AudioOut