From 3c86a9906a67f5856afd3e87f5aa9b1e9e7e8bd8 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 30 Dec 2024 18:58:36 -0800 Subject: [PATCH] audio: Format info cleanup. --- src/core/libraries/audio/audioout.cpp | 178 +++++-------------------- src/core/libraries/audio/audioout.h | 36 +++-- src/core/libraries/audio/sdl_audio.cpp | 16 ++- 3 files changed, 71 insertions(+), 159 deletions(-) diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 0910546b9..d69454c39 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/config.h" @@ -22,111 +23,28 @@ std::array ports_out{}; static std::unique_ptr audio; -static std::string_view GetAudioOutPort(OrbisAudioOutPort port) { - switch (port) { - case OrbisAudioOutPort::Main: - return "MAIN"; - case OrbisAudioOutPort::Bgm: - return "BGM"; - case OrbisAudioOutPort::Voice: - return "VOICE"; - case OrbisAudioOutPort::Personal: - return "PERSONAL"; - case OrbisAudioOutPort::Padspk: - return "PADSPK"; - case OrbisAudioOutPort::Aux: - return "AUX"; - default: - return "INVALID"; - } -} - -static std::string_view GetAudioOutParamFormat(OrbisAudioOutParamFormat param) { - switch (param) { - case OrbisAudioOutParamFormat::S16Mono: - 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"); - } +static AudioFormatInfo GetFormatInfo(const OrbisAudioOutParamFormat format) { + static constexpr std::array format_infos = {{ + // S16Mono + {false, 2, 1, {0}}, + // S16Stereo + {false, 2, 2, {0, 1}}, + // S16_8CH + {false, 2, 8, {0, 1, 2, 3, 4, 5, 6, 7}}, + // FloatMono + {true, 4, 1, {0}}, + // FloatStereo + {true, 4, 2, {0, 1}}, + // Float_8CH + {true, 4, 8, {0, 1, 2, 3, 4, 5, 6, 7}}, + // S16_8CH_Std + {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}}, + }}; + const auto index = static_cast(format); + ASSERT_MSG(index < format_infos.size(), "Unknown audio format {}", index); + return format_infos[index]; } int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() { @@ -268,7 +186,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta case OrbisAudioOutPort::Bgm: case OrbisAudioOutPort::Voice: 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; case OrbisAudioOutPort::Personal: case OrbisAudioOutPort::Padspk: @@ -357,7 +275,7 @@ static void AudioOutputThread(PortOut* port, const std::stop_token& stop) { } Common::AccurateTimer timer( - std::chrono::nanoseconds(1000000000ULL * port->buffer_frames / port->freq)); + std::chrono::nanoseconds(1000000000ULL * port->buffer_frames / port->sample_rate)); while (true) { timer.Start(); { @@ -381,9 +299,9 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, LOG_INFO(Lib_AudioOut, "id = {} port_type = {} index = {} length = {} sample_rate = {} " "param_type = {} attr = {}", - user_id, GetAudioOutPort(port_type), index, length, sample_rate, - GetAudioOutParamFormat(param_type.data_format), - GetAudioOutParamAttr(param_type.attributes)); + user_id, magic_enum::enum_name(port_type), index, length, sample_rate, + magic_enum::enum_name(param_type.data_format.Value()), + magic_enum::enum_name(param_type.attributes.Value())); if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) && (port_type != OrbisAudioOutPort::Aux)) { 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->format = format; - port->is_float = IsFormatFloat(format); - port->freq = sample_rate; - port->sample_size = GetFormatSampleSize(format); - port->channels_num = GetFormatNumChannels(format); - port->frame_size = port->sample_size * port->channels_num; + port->format_info = GetFormatInfo(format); + port->sample_rate = sample_rate; port->buffer_frames = length; - port->buffer_size = port->frame_size * port->buffer_frames; port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB); 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_thread.Run( [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}; port.output_cv.wait(lock, [&] { return !port.output_ready; }); if (ptr != nullptr) { - std::memcpy(port.output_buffer, ptr, port.buffer_size); + std::memcpy(port.output_buffer, ptr, port.BufferSize()); 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; } - 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]; + for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) { + if (flag & 0x1u) { + port.volume[i] = vol[i]; } } diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index 7463d1e62..4f7378dcd 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -15,12 +15,12 @@ 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; -constexpr int SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value +constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22; +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 OrbisAudioOutParamFormat { +enum class OrbisAudioOutParamFormat : u32 { S16Mono = 0, S16Stereo = 1, S16_8CH = 2, @@ -31,7 +31,7 @@ enum class OrbisAudioOutParamFormat { Float_8CH_Std = 7 }; -enum class OrbisAudioOutParamAttr { +enum class OrbisAudioOutParamAttr : u32 { None = 0, Restricted = 1, MixToMain = 2, @@ -60,6 +60,19 @@ struct OrbisAudioOutPortState { 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 channel_layout; + + [[nodiscard]] u16 FrameSize() const { + return sample_size * num_channels; + } +}; + struct PortOut { std::unique_ptr impl{}; @@ -70,15 +83,14 @@ struct PortOut { Kernel::Thread output_thread{}; OrbisAudioOutPort type; - OrbisAudioOutParamFormat format; - bool is_float; - u32 freq; - u8 sample_size; - u8 channels_num; - u32 frame_size; + AudioFormatInfo format_info; + u32 sample_rate; u32 buffer_frames; - u32 buffer_size; - std::array volume; + std::array volume; + + [[nodiscard]] u32 BufferSize() const { + return buffer_frames * format_info.FrameSize(); + } }; int PS4_SYSV_ABI sceAudioOutDeviceIdOpen(); diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp index 82281b661..59d2d5cfb 100644 --- a/src/core/libraries/audio/sdl_audio.cpp +++ b/src/core/libraries/audio/sdl_audio.cpp @@ -14,7 +14,7 @@ namespace Libraries::AudioOut { class SDLPortBackend : public PortBackend { public: 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, // so set the sample frames hint to the number of frames per buffer. const auto samples_num_str = std::to_string(port.buffer_frames); @@ -23,9 +23,9 @@ public: samples_num_str, SDL_GetError()); } const SDL_AudioSpec fmt = { - .format = port.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE, - .channels = port.channels_num, - .freq = static_cast(port.freq), + .format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE, + .channels = port.format_info.num_channels, + .freq = static_cast(port.sample_rate), }; stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); @@ -34,6 +34,14 @@ public: return; } 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)) { LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError()); SDL_DestroyAudioStream(stream);