audio: Format info cleanup.

This commit is contained in:
squidbus 2024-12-30 18:58:36 -08:00
parent 3a02d2860c
commit 3c86a9906a
3 changed files with 71 additions and 159 deletions

View File

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <shared_mutex> #include <shared_mutex>
#include <magic_enum/magic_enum.hpp>
#include "common/assert.h" #include "common/assert.h"
#include "common/config.h" #include "common/config.h"
@ -22,111 +23,28 @@ std::array<PortOut, SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
static std::unique_ptr<AudioOutBackend> audio; static std::unique_ptr<AudioOutBackend> audio;
static std::string_view GetAudioOutPort(OrbisAudioOutPort port) { static AudioFormatInfo GetFormatInfo(const OrbisAudioOutParamFormat format) {
switch (port) { static constexpr std::array<AudioFormatInfo, 8> format_infos = {{
case OrbisAudioOutPort::Main: // S16Mono
return "MAIN"; {false, 2, 1, {0}},
case OrbisAudioOutPort::Bgm: // S16Stereo
return "BGM"; {false, 2, 2, {0, 1}},
case OrbisAudioOutPort::Voice: // S16_8CH
return "VOICE"; {false, 2, 8, {0, 1, 2, 3, 4, 5, 6, 7}},
case OrbisAudioOutPort::Personal: // FloatMono
return "PERSONAL"; {true, 4, 1, {0}},
case OrbisAudioOutPort::Padspk: // FloatStereo
return "PADSPK"; {true, 4, 2, {0, 1}},
case OrbisAudioOutPort::Aux: // Float_8CH
return "AUX"; {true, 4, 8, {0, 1, 2, 3, 4, 5, 6, 7}},
default: // S16_8CH_Std
return "INVALID"; {false, 2, 8, {0, 1, 2, 3, 6, 7, 4, 5}},
} // Float_8CH_Std
} {true, 4, 8, {0, 1, 2, 3, 6, 7, 4, 5}},
}};
static std::string_view GetAudioOutParamFormat(OrbisAudioOutParamFormat param) { const auto index = static_cast<u32>(format);
switch (param) { ASSERT_MSG(index < format_infos.size(), "Unknown audio format {}", index);
case OrbisAudioOutParamFormat::S16Mono: return format_infos[index];
return "S16_MONO";
case OrbisAudioOutParamFormat::S16Stereo:
return "S16_STEREO";
case OrbisAudioOutParamFormat::S16_8CH:
return "S16_8CH";
case OrbisAudioOutParamFormat::FloatMono:
return "FLOAT_MONO";
case OrbisAudioOutParamFormat::FloatStereo:
return "FLOAT_STEREO";
case OrbisAudioOutParamFormat::Float_8CH:
return "FLOAT_8CH";
case OrbisAudioOutParamFormat::S16_8CH_Std:
return "S16_8CH_STD";
case OrbisAudioOutParamFormat::Float_8CH_Std:
return "FLOAT_8CH_STD";
default:
return "INVALID";
}
}
static std::string_view GetAudioOutParamAttr(OrbisAudioOutParamAttr attr) {
switch (attr) {
case OrbisAudioOutParamAttr::None:
return "NONE";
case OrbisAudioOutParamAttr::Restricted:
return "RESTRICTED";
case OrbisAudioOutParamAttr::MixToMain:
return "MIX_TO_MAIN";
default:
return "INVALID";
}
}
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 u8 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() { int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
@ -268,7 +186,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta
case OrbisAudioOutPort::Bgm: case OrbisAudioOutPort::Bgm:
case OrbisAudioOutPort::Voice: case OrbisAudioOutPort::Voice:
state->output = 1; state->output = 1;
state->channel = port.channels_num > 2 ? 2 : port.channels_num; state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels;
break; break;
case OrbisAudioOutPort::Personal: case OrbisAudioOutPort::Personal:
case OrbisAudioOutPort::Padspk: case OrbisAudioOutPort::Padspk:
@ -357,7 +275,7 @@ static void AudioOutputThread(PortOut* port, const std::stop_token& stop) {
} }
Common::AccurateTimer timer( Common::AccurateTimer timer(
std::chrono::nanoseconds(1000000000ULL * port->buffer_frames / port->freq)); std::chrono::nanoseconds(1000000000ULL * port->buffer_frames / port->sample_rate));
while (true) { while (true) {
timer.Start(); timer.Start();
{ {
@ -381,9 +299,9 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
LOG_INFO(Lib_AudioOut, LOG_INFO(Lib_AudioOut,
"id = {} port_type = {} index = {} length = {} sample_rate = {} " "id = {} port_type = {} index = {} length = {} sample_rate = {} "
"param_type = {} attr = {}", "param_type = {} attr = {}",
user_id, GetAudioOutPort(port_type), index, length, sample_rate, user_id, magic_enum::enum_name(port_type), index, length, sample_rate,
GetAudioOutParamFormat(param_type.data_format), magic_enum::enum_name(param_type.data_format.Value()),
GetAudioOutParamAttr(param_type.attributes)); magic_enum::enum_name(param_type.attributes.Value()));
if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) && if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) &&
(port_type != OrbisAudioOutPort::Aux)) { (port_type != OrbisAudioOutPort::Aux)) {
LOG_ERROR(Lib_AudioOut, "Invalid port type"); LOG_ERROR(Lib_AudioOut, "Invalid port type");
@ -423,19 +341,14 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
} }
port->type = port_type; port->type = port_type;
port->format = format; port->format_info = GetFormatInfo(format);
port->is_float = IsFormatFloat(format); port->sample_rate = sample_rate;
port->freq = sample_rate;
port->sample_size = GetFormatSampleSize(format);
port->channels_num = GetFormatNumChannels(format);
port->frame_size = port->sample_size * port->channels_num;
port->buffer_frames = length; port->buffer_frames = length;
port->buffer_size = port->frame_size * port->buffer_frames;
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB); port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
port->impl = audio->Open(*port); port->impl = audio->Open(*port);
port->output_buffer = std::malloc(port->buffer_size); port->output_buffer = std::malloc(port->BufferSize());
port->output_ready = false; port->output_ready = false;
port->output_thread.Run( port->output_thread.Run(
[port](const std::stop_token& stop) { AudioOutputThread(&*port, stop); }); [port](const std::stop_token& stop) { AudioOutputThread(&*port, stop); });
@ -462,7 +375,7 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) {
std::unique_lock lock{port.output_mutex}; std::unique_lock lock{port.output_mutex};
port.output_cv.wait(lock, [&] { return !port.output_ready; }); port.output_cv.wait(lock, [&] { return !port.output_ready; });
if (ptr != nullptr) { if (ptr != nullptr) {
std::memcpy(port.output_buffer, ptr, port.buffer_size); std::memcpy(port.output_buffer, ptr, port.BufferSize());
port.output_ready = true; port.output_ready = true;
} }
} }
@ -581,30 +494,9 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
} }
for (int i = 0; i < port.channels_num; i++, flag >>= 1u) { for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) {
auto bit = flag & 0x1u; if (flag & 0x1u) {
if (bit == 1) { port.volume[i] = vol[i];
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];
} }
} }

View File

@ -15,12 +15,12 @@ class PortBackend;
// Main up to 8 ports, BGM 1 port, voice up to 4 ports, // 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 // personal up to 4 ports, padspk up to 5 ports, aux 1 port
constexpr int SCE_AUDIO_OUT_NUM_PORTS = 22; constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22;
constexpr int SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
enum class OrbisAudioOutPort { Main = 0, Bgm = 1, Voice = 2, Personal = 3, Padspk = 4, Aux = 127 }; enum class OrbisAudioOutPort { Main = 0, Bgm = 1, Voice = 2, Personal = 3, Padspk = 4, Aux = 127 };
enum class OrbisAudioOutParamFormat { enum class OrbisAudioOutParamFormat : u32 {
S16Mono = 0, S16Mono = 0,
S16Stereo = 1, S16Stereo = 1,
S16_8CH = 2, S16_8CH = 2,
@ -31,7 +31,7 @@ enum class OrbisAudioOutParamFormat {
Float_8CH_Std = 7 Float_8CH_Std = 7
}; };
enum class OrbisAudioOutParamAttr { enum class OrbisAudioOutParamAttr : u32 {
None = 0, None = 0,
Restricted = 1, Restricted = 1,
MixToMain = 2, MixToMain = 2,
@ -60,6 +60,19 @@ struct OrbisAudioOutPortState {
u64 reserved64[2]; u64 reserved64[2];
}; };
struct AudioFormatInfo {
bool is_float;
u8 sample_size;
u8 num_channels;
/// Layout array remapping channel indices, specified in this order:
/// FL, FR, FC, LFE, BL, BR, SL, SR
std::array<int, 8> channel_layout;
[[nodiscard]] u16 FrameSize() const {
return sample_size * num_channels;
}
};
struct PortOut { struct PortOut {
std::unique_ptr<PortBackend> impl{}; std::unique_ptr<PortBackend> impl{};
@ -70,15 +83,14 @@ struct PortOut {
Kernel::Thread output_thread{}; Kernel::Thread output_thread{};
OrbisAudioOutPort type; OrbisAudioOutPort type;
OrbisAudioOutParamFormat format; AudioFormatInfo format_info;
bool is_float; u32 sample_rate;
u32 freq;
u8 sample_size;
u8 channels_num;
u32 frame_size;
u32 buffer_frames; u32 buffer_frames;
u32 buffer_size; std::array<s32, 8> volume;
std::array<int, 8> volume;
[[nodiscard]] u32 BufferSize() const {
return buffer_frames * format_info.FrameSize();
}
}; };
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen(); int PS4_SYSV_ABI sceAudioOutDeviceIdOpen();

View File

@ -14,7 +14,7 @@ namespace Libraries::AudioOut {
class SDLPortBackend : public PortBackend { class SDLPortBackend : public PortBackend {
public: public:
explicit SDLPortBackend(const PortOut& port) explicit SDLPortBackend(const PortOut& port)
: frame_size(port.frame_size), buffer_size(port.buffer_size) { : frame_size(port.format_info.FrameSize()), buffer_size(port.BufferSize()) {
// We want the latency for delivering frames out to be as small as possible, // We want the latency for delivering frames out to be as small as possible,
// so set the sample frames hint to the number of frames per buffer. // so set the sample frames hint to the number of frames per buffer.
const auto samples_num_str = std::to_string(port.buffer_frames); const auto samples_num_str = std::to_string(port.buffer_frames);
@ -23,9 +23,9 @@ public:
samples_num_str, SDL_GetError()); samples_num_str, SDL_GetError());
} }
const SDL_AudioSpec fmt = { const SDL_AudioSpec fmt = {
.format = port.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE, .format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE,
.channels = port.channels_num, .channels = port.format_info.num_channels,
.freq = static_cast<int>(port.freq), .freq = static_cast<int>(port.sample_rate),
}; };
stream = stream =
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr);
@ -34,6 +34,14 @@ public:
return; return;
} }
queue_threshold = CalculateQueueThreshold(); queue_threshold = CalculateQueueThreshold();
if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
port.format_info.num_channels)) {
LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
SDL_GetError());
SDL_DestroyAudioStream(stream);
stream = nullptr;
return;
}
if (!SDL_ResumeAudioStreamDevice(stream)) { if (!SDL_ResumeAudioStreamDevice(stream)) {
LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError()); LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError());
SDL_DestroyAudioStream(stream); SDL_DestroyAudioStream(stream);