From bbb4c109d74c0f3db71f186928c039b4b83459e7 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 26 Mar 2024 19:39:37 +0100 Subject: [PATCH] audio: allow to choose channel layouts --- rpcs3/Emu/Audio/AudioBackend.cpp | 79 ++++++-- rpcs3/Emu/Audio/AudioBackend.h | 201 ++++++++++++++++----- rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp | 27 ++- rpcs3/Emu/Audio/Cubeb/CubebBackend.h | 4 +- rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp | 5 +- rpcs3/Emu/Audio/FAudio/FAudioBackend.h | 2 +- rpcs3/Emu/Audio/Null/NullAudioBackend.h | 2 +- rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp | 5 +- rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h | 2 +- rpcs3/Emu/Cell/Modules/cellAudio.cpp | 20 +- rpcs3/Emu/Cell/Modules/cellAudio.h | 4 +- rpcs3/Emu/Cell/Modules/cellRec.cpp | 2 +- rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp | 17 +- rpcs3/Emu/Cell/lv2/sys_rsxaudio.h | 5 +- rpcs3/Emu/system_config.h | 1 + rpcs3/Emu/system_config_types.cpp | 21 +++ rpcs3/Emu/system_config_types.h | 12 ++ rpcs3/rpcs3qt/emu_settings.cpp | 13 ++ rpcs3/rpcs3qt/emu_settings_type.h | 2 + rpcs3/rpcs3qt/settings_dialog.cpp | 3 + rpcs3/rpcs3qt/settings_dialog.ui | 12 ++ rpcs3/rpcs3qt/tooltips.h | 3 +- 22 files changed, 343 insertions(+), 99 deletions(-) diff --git a/rpcs3/Emu/Audio/AudioBackend.cpp b/rpcs3/Emu/Audio/AudioBackend.cpp index 320ec753bc..83c0046717 100644 --- a/rpcs3/Emu/Audio/AudioBackend.cpp +++ b/rpcs3/Emu/Audio/AudioBackend.cpp @@ -33,7 +33,12 @@ u32 AudioBackend::get_sample_size() const u32 AudioBackend::get_channels() const { - return static_cast>(m_channels); + return m_channels; +} + +audio_channel_layout AudioBackend::get_channel_layout() const +{ + return m_layout; } bool AudioBackend::get_convert_to_s16() const @@ -141,23 +146,63 @@ AudioChannelCnt AudioBackend::get_max_channel_count(u32 device_index) return count; } -AudioChannelCnt AudioBackend::convert_channel_count(u64 raw) +u32 AudioBackend::default_layout_channel_count(audio_channel_layout layout) { - switch (raw) + switch (layout) { - default: - case 8: - return AudioChannelCnt::SURROUND_7_1; - case 7: - case 6: - return AudioChannelCnt::SURROUND_5_1; - case 5: - case 4: - case 3: - case 2: - case 1: - return AudioChannelCnt::STEREO; - case 0: - fmt::throw_exception("Unsupported channel count"); + case audio_channel_layout::mono: return 1; + case audio_channel_layout::stereo: return 2; + case audio_channel_layout::stereo_lfe: return 3; + case audio_channel_layout::quadraphonic: return 4; + case audio_channel_layout::quadraphonic_lfe: return 5; + case audio_channel_layout::surround_5_1: return 6; + case audio_channel_layout::surround_7_1: return 8; + default: fmt::throw_exception("Unsupported layout %d", static_cast(layout)); } } + +u32 AudioBackend::layout_channel_count(u32 channels, audio_channel_layout layout) +{ + if (channels == 0) + { + fmt::throw_exception("Unsupported channel count"); + } + + return std::min(channels, default_layout_channel_count(layout)); +} + +audio_channel_layout AudioBackend::default_layout(u32 channels) +{ + switch (channels) + { + case 1: return audio_channel_layout::mono; + case 2: return audio_channel_layout::stereo; + case 3: return audio_channel_layout::stereo_lfe; + case 4: return audio_channel_layout::quadraphonic; + case 5: return audio_channel_layout::quadraphonic_lfe; + case 6: return audio_channel_layout::surround_5_1; + case 7: return audio_channel_layout::surround_5_1; + case 8: return audio_channel_layout::surround_7_1; + default: return audio_channel_layout::stereo; + } +} + +void AudioBackend::setup_channel_layout(u32 input_channel_count, u32 output_channel_count, audio_channel_layout layout, logs::channel& log) +{ + const u32 channels = std::min(input_channel_count, output_channel_count); + + if (layout != audio_channel_layout::automatic && output_channel_count > input_channel_count) + { + log.warning("Mixing from %d to %d channels is not implemented. Falling back to automatic layout.", input_channel_count, output_channel_count); + layout = audio_channel_layout::automatic; + } + + if (layout != audio_channel_layout::automatic && channels < default_layout_channel_count(layout)) + { + log.warning("Can't use layout %s with %d channels. Falling back to automatic layout.", layout, channels); + layout = audio_channel_layout::automatic; + } + + m_layout = layout == audio_channel_layout::automatic ? default_layout(channels) : layout; + m_channels = layout_channel_count(channels, m_layout); +} diff --git a/rpcs3/Emu/Audio/AudioBackend.h b/rpcs3/Emu/Audio/AudioBackend.h index 497b4c97e6..cbd8e045c9 100644 --- a/rpcs3/Emu/Audio/AudioBackend.h +++ b/rpcs3/Emu/Audio/AudioBackend.h @@ -1,8 +1,10 @@ #pragma once #include "util/types.hpp" +#include "util/logs.hpp" #include "Utilities/mutex.h" #include "Utilities/StrFmt.h" +#include "Emu/system_config_types.h" #include enum : u32 @@ -30,6 +32,7 @@ enum class AudioSampleSize : u32 S16 = sizeof(s16), }; +// This enum is only used for emulation enum class AudioChannelCnt : u32 { STEREO = 2, @@ -69,7 +72,7 @@ public: // If dev_id is empty, then default device will be selected. // May override channel count if device has smaller number of channels. // Should return 'true' on success. - virtual bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0; + virtual bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt, audio_channel_layout layout) = 0; // Reset backend state. Blocks until data callback returns. virtual void Close() = 0; @@ -122,6 +125,7 @@ public: u32 get_sample_size() const; u32 get_channels() const; + audio_channel_layout get_channel_layout() const; bool get_convert_to_s16() const; @@ -158,51 +162,91 @@ public: */ static AudioChannelCnt get_max_channel_count(u32 device_index); + /* + * Get default channel count for a layout + */ + static u32 default_layout_channel_count(audio_channel_layout layout); + /* * Converts raw channel count to value usable by backends */ - static AudioChannelCnt convert_channel_count(u64 raw); + static u32 layout_channel_count(u32 channels, audio_channel_layout layout); + + /* + * Get the default layout for raw channel count + */ + static audio_channel_layout default_layout(u32 channels); /* * Downmix audio stream. */ - template + template static void downmix(u32 sample_cnt, const f32* src, f32* dst) { - static_assert(from == AudioChannelCnt::SURROUND_5_1 || from == AudioChannelCnt::SURROUND_7_1, "Cannot downmix FROM channel count"); - static_assert(static_cast(from) > static_cast(to), "FROM channel count must be bigger than TO"); + const u32 dst_ch_cnt = default_layout_channel_count(dst_layout); + if (static_cast(src_ch_cnt) <= dst_ch_cnt) fmt::throw_exception("src channel count must be bigger than dst channel count"); static constexpr f32 center_coef = std::numbers::sqrt2_v / 2; static constexpr f32 surround_coef = std::numbers::sqrt2_v / 2; - for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast(from), dst_sample += static_cast(to)) + for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast(src_ch_cnt), dst_sample += dst_ch_cnt) { - const f32 left = src[src_sample + 0]; - const f32 right = src[src_sample + 1]; - const f32 center = src[src_sample + 2]; - const f32 low_freq = src[src_sample + 3]; - - if constexpr (from == AudioChannelCnt::SURROUND_5_1) + const f32 left = src[src_sample + 0]; + const f32 right = src[src_sample + 1]; + + if constexpr (src_ch_cnt == AudioChannelCnt::STEREO) { - static_assert(to == AudioChannelCnt::STEREO, "Invalid TO channel count"); - + if constexpr (dst_layout == audio_channel_layout::mono) + { + dst[dst_sample + 0] = left + right; + } + } + else if constexpr (src_ch_cnt == AudioChannelCnt::SURROUND_5_1) + { + const f32 center = src[src_sample + 2]; + const f32 low_freq = src[src_sample + 3]; const f32 side_left = src[src_sample + 4]; const f32 side_right = src[src_sample + 5]; - const f32 mid = center * center_coef; - dst[dst_sample + 0] = left + mid + side_left * surround_coef; - dst[dst_sample + 1] = right + mid + side_right * surround_coef; - } - else if constexpr (from == AudioChannelCnt::SURROUND_7_1) - { - static_assert(to == AudioChannelCnt::STEREO || to == AudioChannelCnt::SURROUND_5_1, "Invalid TO channel count"); + if constexpr (dst_layout == audio_channel_layout::quadraphonic || dst_layout == audio_channel_layout::quadraphonic_lfe) + { + const f32 mid = center * center_coef; + dst[dst_sample + 0] = left + mid; + dst[dst_sample + 1] = right + mid; + dst[dst_sample + 2] = side_left; + dst[dst_sample + 3] = side_right; + if constexpr (dst_layout == audio_channel_layout::quadraphonic_lfe) + { + dst[dst_sample + 4] = low_freq; + } + } + else if constexpr (dst_layout == audio_channel_layout::stereo || dst_layout == audio_channel_layout::stereo_lfe) + { + const f32 mid = center * center_coef; + dst[dst_sample + 0] = left + mid + side_left * surround_coef; + dst[dst_sample + 1] = right + mid + side_right * surround_coef; + + if constexpr (dst_layout == audio_channel_layout::stereo_lfe) + { + dst[dst_sample + 2] = low_freq; + } + } + else if constexpr (dst_layout == audio_channel_layout::mono) + { + dst[dst_sample + 0] = left + right + center + side_left + side_right; + } + } + else if constexpr (src_ch_cnt == AudioChannelCnt::SURROUND_7_1) + { + const f32 center = src[src_sample + 2]; + const f32 low_freq = src[src_sample + 3]; const f32 rear_left = src[src_sample + 4]; const f32 rear_right = src[src_sample + 5]; const f32 side_left = src[src_sample + 6]; const f32 side_right = src[src_sample + 7]; - if constexpr (to == AudioChannelCnt::SURROUND_5_1) + if constexpr (dst_layout == audio_channel_layout::surround_5_1) { dst[dst_sample + 0] = left; dst[dst_sample + 1] = right; @@ -211,59 +255,126 @@ public: dst[dst_sample + 4] = side_left + rear_left; dst[dst_sample + 5] = side_right + rear_right; } - else + else if constexpr (dst_layout == audio_channel_layout::quadraphonic || dst_layout == audio_channel_layout::quadraphonic_lfe) + { + const f32 mid = center * center_coef; + dst[dst_sample + 0] = left + mid; + dst[dst_sample + 1] = right + mid; + dst[dst_sample + 2] = side_left + rear_left; + dst[dst_sample + 3] = side_right + rear_right; + + if constexpr (dst_layout == audio_channel_layout::quadraphonic_lfe) + { + dst[dst_sample + 4] = low_freq; + } + } + else if constexpr (dst_layout == audio_channel_layout::stereo || dst_layout == audio_channel_layout::stereo_lfe) { const f32 mid = center * center_coef; dst[dst_sample + 0] = left + mid + (side_left + rear_left) * surround_coef; dst[dst_sample + 1] = right + mid + (side_right + rear_right) * surround_coef; + + if constexpr (dst_layout == audio_channel_layout::stereo_lfe) + { + dst[dst_sample + 2] = low_freq; + } + } + else if constexpr (dst_layout == audio_channel_layout::mono) + { + dst[dst_sample + 0] = left + right + center + side_left + rear_left + side_right + rear_right; } } } } - static void downmix(u32 sample_cnt, u32 src_ch_cnt, u32 dst_ch_cnt, const f32* src, f32* dst) + static void downmix(u32 sample_cnt, u32 src_ch_cnt, audio_channel_layout dst_layout, const f32* src, f32* dst) { + const u32 dst_ch_cnt = default_layout_channel_count(dst_layout); + if (src_ch_cnt <= dst_ch_cnt) { return; } - if (src_ch_cnt == static_cast(AudioChannelCnt::SURROUND_7_1)) + switch (src_ch_cnt) { - if (dst_ch_cnt == static_cast(AudioChannelCnt::SURROUND_5_1)) + case static_cast(AudioChannelCnt::SURROUND_7_1): + { + switch (dst_layout) { - AudioBackend::downmix(sample_cnt, src, dst); - } - else if (dst_ch_cnt == static_cast(AudioChannelCnt::STEREO)) - { - AudioBackend::downmix(sample_cnt, src, dst); - } - else - { - fmt::throw_exception("Invalid downmix combination: %u -> %u", src_ch_cnt, dst_ch_cnt); + case audio_channel_layout::mono: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::stereo: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::stereo_lfe: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::quadraphonic: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::quadraphonic_lfe: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::surround_5_1: + AudioBackend::downmix(sample_cnt, src, dst); + break; + default: + fmt::throw_exception("Invalid downmix combination: %u -> %s", src_ch_cnt, dst_layout); } + break; } - else if (src_ch_cnt == static_cast(AudioChannelCnt::SURROUND_5_1)) + case static_cast(AudioChannelCnt::SURROUND_5_1): { - if (dst_ch_cnt == static_cast(AudioChannelCnt::STEREO)) + switch (dst_layout) { - AudioBackend::downmix(sample_cnt, src, dst); - } - else - { - fmt::throw_exception("Invalid downmix combination: %u -> %u", src_ch_cnt, dst_ch_cnt); + case audio_channel_layout::mono: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::stereo: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::stereo_lfe: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::quadraphonic: + AudioBackend::downmix(sample_cnt, src, dst); + break; + case audio_channel_layout::quadraphonic_lfe: + AudioBackend::downmix(sample_cnt, src, dst); + break; + default: + fmt::throw_exception("Invalid downmix combination: %u -> %s", src_ch_cnt, dst_layout); } + break; } - else + case static_cast(AudioChannelCnt::STEREO): { - fmt::throw_exception("Invalid downmix combination: %u -> %u", src_ch_cnt, dst_ch_cnt); + switch (dst_layout) + { + case audio_channel_layout::mono: + AudioBackend::downmix(sample_cnt, src, dst); + break; + default: + fmt::throw_exception("Invalid downmix combination: %u -> %s", src_ch_cnt, dst_layout); + } + break; + } + default: + { + fmt::throw_exception("Invalid downmix combination: %u -> %s", src_ch_cnt, dst_layout); + } } } protected: + void setup_channel_layout(u32 input_channel_count, u32 output_channel_count, audio_channel_layout layout, logs::channel& log); + AudioSampleSize m_sample_size = AudioSampleSize::FLOAT; AudioFreq m_sampling_rate = AudioFreq::FREQ_48K; - AudioChannelCnt m_channels = AudioChannelCnt::STEREO; + u32 m_channels = 2; + audio_channel_layout m_layout = audio_channel_layout::automatic; std::timed_mutex m_cb_mutex{}; std::function m_write_callback{}; diff --git a/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp b/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp index d191d42ed3..750e8f0ccd 100644 --- a/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp +++ b/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp @@ -96,7 +96,7 @@ bool CubebBackend::DefaultDeviceChanged() return !device.handle || device.id != m_default_device; } -bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt, audio_channel_layout layout) { if (!Initialized()) { @@ -118,7 +118,7 @@ bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize { if (use_default_device) { - device = GetDefaultDeviceAlt(freq, sample_size, ch_cnt); + device = GetDefaultDeviceAlt(freq, sample_size, static_cast(ch_cnt)); if (!device.handle) { @@ -148,7 +148,9 @@ bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize m_sampling_rate = freq; m_sample_size = sample_size; - m_channels = static_cast(std::min(static_cast(convert_channel_count(device.ch_cnt)), static_cast(ch_cnt))); + + setup_channel_layout(static_cast(ch_cnt), device.ch_cnt, layout, Cubeb); + full_sample_size = get_channels() * get_sample_size(); cubeb_stream_params stream_param{}; @@ -157,14 +159,19 @@ bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize stream_param.channels = get_channels(); stream_param.layout = [&]() { - switch (m_channels) + switch (m_layout) { - case AudioChannelCnt::STEREO: return CUBEB_LAYOUT_STEREO; - case AudioChannelCnt::SURROUND_5_1: return CUBEB_LAYOUT_3F2_LFE; - case AudioChannelCnt::SURROUND_7_1: return CUBEB_LAYOUT_3F4_LFE; - default: - fmt::throw_exception("Invalid audio channel count"); + case audio_channel_layout::automatic: break; + case audio_channel_layout::mono: return CUBEB_LAYOUT_MONO; + case audio_channel_layout::stereo: return CUBEB_LAYOUT_STEREO; + case audio_channel_layout::stereo_lfe: return CUBEB_LAYOUT_STEREO_LFE; + case audio_channel_layout::quadraphonic: return CUBEB_LAYOUT_QUAD; + case audio_channel_layout::quadraphonic_lfe: return CUBEB_LAYOUT_QUAD_LFE; + case audio_channel_layout::surround_5_1: return CUBEB_LAYOUT_3F2_LFE; + case audio_channel_layout::surround_7_1: return CUBEB_LAYOUT_3F4_LFE; } + + fmt::throw_exception("Invalid audio layout %d", static_cast(m_layout)); }(); stream_param.prefs = m_dev_collection_cb_enabled && device.handle ? CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING : CUBEB_STREAM_PREF_NONE; @@ -348,7 +355,7 @@ CubebBackend::device_handle CubebBackend::GetDevice(std::string_view dev_id) return result; }; -CubebBackend::device_handle CubebBackend::GetDefaultDeviceAlt(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +CubebBackend::device_handle CubebBackend::GetDefaultDeviceAlt(AudioFreq freq, AudioSampleSize sample_size, u32 ch_cnt) { Cubeb.notice("Starting alternative search for default device with freq=%d, sample_size=%d and ch_cnt=%d", static_cast(freq), static_cast(sample_size), static_cast(ch_cnt)); diff --git a/rpcs3/Emu/Audio/Cubeb/CubebBackend.h b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h index 949d05b16d..f8c53e7e84 100644 --- a/rpcs3/Emu/Audio/Cubeb/CubebBackend.h +++ b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h @@ -22,7 +22,7 @@ public: bool Operational() override; bool DefaultDeviceChanged() override; - bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; + bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt, audio_channel_layout layout) override; void Close() override; f64 GetCallbackFrameLen() override; @@ -62,5 +62,5 @@ private: }; device_handle GetDevice(std::string_view dev_id = ""); - device_handle GetDefaultDeviceAlt(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt); + device_handle GetDefaultDeviceAlt(AudioFreq freq, AudioSampleSize sample_size, u32 ch_cnt); }; diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp index 06fe9d602a..58cd189e77 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp @@ -122,7 +122,7 @@ bool FAudioBackend::Operational() return m_source_voice != nullptr && !m_reset_req.observe(); } -bool FAudioBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +bool FAudioBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt, audio_channel_layout layout) { if (!Initialized()) { @@ -165,7 +165,8 @@ bool FAudioBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSiz m_sampling_rate = freq; m_sample_size = sample_size; - m_channels = static_cast(std::min(static_cast(convert_channel_count(vd.InputChannels)), static_cast(ch_cnt)));; + + setup_channel_layout(static_cast(ch_cnt), vd.InputChannels, layout, FAudio_); FAudioWaveFormatEx waveformatex; waveformatex.wFormatTag = get_convert_to_s16() ? FAUDIO_FORMAT_PCM : FAUDIO_FORMAT_IEEE_FLOAT; diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h index 66802a8bf5..3f3d6e2ef8 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h @@ -24,7 +24,7 @@ public: bool Initialized() override; bool Operational() override; - bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; + bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt, audio_channel_layout layout) override; void Close() override; f64 GetCallbackFrameLen() override; diff --git a/rpcs3/Emu/Audio/Null/NullAudioBackend.h b/rpcs3/Emu/Audio/Null/NullAudioBackend.h index 3afdeafb79..abb78ff460 100644 --- a/rpcs3/Emu/Audio/Null/NullAudioBackend.h +++ b/rpcs3/Emu/Audio/Null/NullAudioBackend.h @@ -10,7 +10,7 @@ public: std::string_view GetName() const override { return "Null"sv; } - bool Open(std::string_view /* dev_id */, AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */) override + bool Open(std::string_view /* dev_id */, AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */, audio_channel_layout /*layout*/) override { Close(); return true; diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp index a0515f3068..3931638c69 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp @@ -198,7 +198,7 @@ void XAudio2Backend::Pause() } } -bool XAudio2Backend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +bool XAudio2Backend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt, audio_channel_layout layout) { if (!Initialized()) { @@ -258,7 +258,8 @@ bool XAudio2Backend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSi m_sampling_rate = freq; m_sample_size = sample_size; - m_channels = static_cast(std::min(static_cast(convert_channel_count(vd.InputChannels)), static_cast(ch_cnt))); + + setup_channel_layout(static_cast(ch_cnt), vd.InputChannels, layout, XAudio); WAVEFORMATEX waveformatex{}; waveformatex.wFormatTag = get_convert_to_s16() ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT; diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h index 896d4dfc7d..e03cf6cc51 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h @@ -28,7 +28,7 @@ public: bool Operational() override; bool DefaultDeviceChanged() override; - bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; + bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt, audio_channel_layout layout) override; void Close() override; f64 GetCallbackFrameLen() override; diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index d81de37f0d..db5dddeae4 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -76,12 +76,14 @@ void cell_audio_config::reset(bool backend_changed) const auto& [req_ch_cnt, downmix] = AudioBackend::get_channel_count_and_downmixer(0); // CELL_AUDIO_OUT_PRIMARY f64 cb_frame_len = 0.0; u32 ch_cnt = 2; + audio_channel_layout ch_layout = audio_channel_layout::stereo; - if (backend->Open(raw.audio_device, freq, sample_size, req_ch_cnt)) + if (backend->Open(raw.audio_device, freq, sample_size, req_ch_cnt, raw.channel_layout)) { cb_frame_len = backend->GetCallbackFrameLen(); ch_cnt = backend->get_channels(); - cellAudio.notice("Opened audio backend (sampling_rate=%d, sample_size=%d, channels=%d)", backend->get_sampling_rate(), backend->get_sample_size(), backend->get_channels()); + ch_layout = backend->get_channel_layout(); + cellAudio.notice("Opened audio backend (sampling_rate=%d, sample_size=%d, channels=%d, layout=%s)", backend->get_sampling_rate(), backend->get_sample_size(), backend->get_channels(), backend->get_channel_layout()); } else { @@ -89,7 +91,8 @@ void cell_audio_config::reset(bool backend_changed) } audio_downmix = downmix; - backend_ch_cnt = AudioChannelCnt{ch_cnt}; + backend_ch_cnt = ch_cnt; + backend_channel_layout = ch_layout; audio_channels = static_cast(req_ch_cnt); audio_sampling_rate = static_cast(freq); audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate; @@ -163,7 +166,7 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) return cfg.audio_min_buffer_duration; }(); - cb_ringbuf.set_buf_size(static_cast(static_cast(cfg.backend_ch_cnt) * cfg.audio_sampling_rate * cfg.audio_sample_size * buffer_dur_mult)); + cb_ringbuf.set_buf_size(static_cast(cfg.backend_ch_cnt * cfg.audio_sampling_rate * cfg.audio_sample_size * buffer_dur_mult)); backend->SetWriteCallback(std::bind(&audio_ringbuffer::backend_write_callback, this, std::placeholders::_1, std::placeholders::_2)); backend->SetStateCallback(std::bind(&audio_ringbuffer::backend_state_callback, this, std::placeholders::_1)); } @@ -220,7 +223,7 @@ float* audio_ringbuffer::get_current_buffer() const u64 audio_ringbuffer::get_enqueued_samples() const { AUDIT(cfg.buffering_enabled); - const u64 ringbuf_samples = cb_ringbuf.get_used_size() / (cfg.audio_sample_size * static_cast(cfg.backend_ch_cnt)); + const u64 ringbuf_samples = cb_ringbuf.get_used_size() / (cfg.audio_sample_size * cfg.backend_ch_cnt); if (cfg.time_stretching_enabled) { @@ -281,14 +284,14 @@ void audio_ringbuffer::process_resampled_data() { if (!cfg.time_stretching_enabled) return; - const auto& [buffer, samples] = resampler.get_samples(static_cast(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * static_cast(cfg.backend_ch_cnt)))); + const auto& [buffer, samples] = resampler.get_samples(static_cast(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.backend_ch_cnt))); commit_data(buffer, samples); } void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt) { const u32 sample_cnt_in = sample_cnt * cfg.audio_channels; - const u32 sample_cnt_out = sample_cnt * static_cast(cfg.backend_ch_cnt); + const u32 sample_cnt_out = sample_cnt * cfg.backend_ch_cnt; // Dump audio if enabled m_dump.WriteData(buf, sample_cnt_in * static_cast(AudioSampleSize::FLOAT)); @@ -301,7 +304,7 @@ void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt) } // Downmix if necessary - AudioBackend::downmix(sample_cnt_in, cfg.audio_channels, static_cast(cfg.backend_ch_cnt), buf, buf); + AudioBackend::downmix(sample_cnt_in, cfg.audio_channels, cfg.backend_channel_layout, buf, buf); if (cfg.backend->get_convert_to_s16()) { @@ -615,6 +618,7 @@ namespace audio .time_stretching_threshold = g_cfg.audio.time_stretching_threshold, .convert_to_s16 = static_cast(g_cfg.audio.convert_to_s16), .dump_to_file = static_cast(g_cfg.audio.dump_to_file), + .channel_layout = g_cfg.audio.channel_layout, .renderer = g_cfg.audio.renderer, .provider = g_cfg.audio.provider }; diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index 4225bdbca6..1e12a68c9e 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -212,6 +212,7 @@ struct cell_audio_config s64 time_stretching_threshold = 0; bool convert_to_s16 = false; bool dump_to_file = false; + audio_channel_layout channel_layout = audio_channel_layout::automatic; audio_renderer renderer = audio_renderer::null; audio_provider provider = audio_provider::none; }; @@ -222,7 +223,8 @@ struct cell_audio_config std::shared_ptr backend = nullptr; AudioChannelCnt audio_downmix = AudioChannelCnt::SURROUND_7_1; - AudioChannelCnt backend_ch_cnt = AudioChannelCnt::SURROUND_7_1; + audio_channel_layout backend_channel_layout = audio_channel_layout::surround_7_1; + u32 backend_ch_cnt = 8; u32 audio_channels = 2; u32 audio_sampling_rate = DEFAULT_AUDIO_SAMPLING_RATE; u32 audio_block_period = 0; diff --git a/rpcs3/Emu/Cell/Modules/cellRec.cpp b/rpcs3/Emu/Cell/Modules/cellRec.cpp index ab0a614b05..fda113309a 100644 --- a/rpcs3/Emu/Cell/Modules/cellRec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellRec.cpp @@ -752,7 +752,7 @@ void rec_info::start_video_provider() if (sample.channels > channels) { // Downmix channels - AudioBackend::downmix(CELL_REC_AUDIO_BLOCK_SAMPLES * sample.channels, sample.channels, channels, src, reinterpret_cast(dst_buffer.block.data())); + AudioBackend::downmix(CELL_REC_AUDIO_BLOCK_SAMPLES * sample.channels, sample.channels, audio_channel_layout::stereo, src, reinterpret_cast(dst_buffer.block.data())); } else { diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp index bdfda23c2b..427c6ecc47 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp @@ -1374,6 +1374,7 @@ rsxaudio_backend_thread::emu_audio_cfg rsxaudio_backend_thread::get_emu_cfg() .enable_time_stretching = static_cast(g_cfg.audio.enable_time_stretching), .dump_to_file = static_cast(g_cfg.audio.dump_to_file), .channels = out_ch_cnt, + .channel_layout = g_cfg.audio.channel_layout, .renderer = g_cfg.audio.renderer, .provider = g_cfg.audio.provider, .avport = convert_avport(g_cfg.audio.rsxaudio_port) @@ -1731,10 +1732,14 @@ void rsxaudio_backend_thread::backend_init(const rsxaudio_state& ra_state, const f64 cb_frame_len = 0.0; u32 backend_ch_cnt = 2; - if (backend->Open(emu_cfg.audio_device, port_cfg.freq, sample_size, ch_cnt)) + audio_channel_layout backend_channel_layout = audio_channel_layout::stereo; + + if (backend->Open(emu_cfg.audio_device, port_cfg.freq, sample_size, ch_cnt, emu_cfg.channel_layout)) { cb_frame_len = backend->GetCallbackFrameLen(); + backend_channel_layout = backend->get_channel_layout(); backend_ch_cnt = backend->get_channels(); + sys_rsxaudio.notice("Opened audio backend (sampling_rate=%d, sample_size=%d, channels=%d, layout=%s)", backend->get_sampling_rate(), backend->get_sample_size(), backend->get_channels(), backend->get_channel_layout()); } else { @@ -1775,7 +1780,7 @@ void rsxaudio_backend_thread::backend_init(const rsxaudio_state& ra_state, const { val.freq = static_cast(port_cfg.freq); val.input_ch_cnt = static_cast(port_cfg.ch_cnt); - val.output_ch_cnt = backend_ch_cnt; + val.output_channel_layout = static_cast(backend_channel_layout); val.convert_to_s16 = emu_cfg.convert_to_s16; val.avport_idx = emu_cfg.avport; val.ready = true; @@ -1834,14 +1839,16 @@ u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void* buf) if (cb_cfg.ready && !mute_state[static_cast(cb_cfg.avport_idx)] && Emu.IsRunning()) { - const u32 bytes_ch_adjusted = bytes / cb_cfg.output_ch_cnt * cb_cfg.input_ch_cnt; + const audio_channel_layout output_channel_layout = static_cast(cb_cfg.output_channel_layout); + const u32 output_ch_cnt = AudioBackend::default_layout_channel_count(output_channel_layout); + const u32 bytes_ch_adjusted = bytes / output_ch_cnt * cb_cfg.input_ch_cnt; const u32 bytes_from_rb = cb_cfg.convert_to_s16 ? bytes_ch_adjusted / static_cast(AudioSampleSize::S16) * static_cast(AudioSampleSize::FLOAT) : bytes_ch_adjusted; ensure(callback_tmp_buf.size() * static_cast(AudioSampleSize::FLOAT) >= bytes_from_rb); const u32 byte_cnt = static_cast(ringbuf.pop(callback_tmp_buf.data(), bytes_from_rb, true)); const u32 sample_cnt = byte_cnt / static_cast(AudioSampleSize::FLOAT); - const u32 sample_cnt_out = sample_cnt / cb_cfg.input_ch_cnt * cb_cfg.output_ch_cnt; + const u32 sample_cnt_out = sample_cnt / cb_cfg.input_ch_cnt * output_ch_cnt; // Buffer is in weird state - drop acquired data if (sample_cnt == 0 || sample_cnt % cb_cfg.input_ch_cnt != 0) @@ -1858,7 +1865,7 @@ u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void* buf) } // Downmix if necessary - AudioBackend::downmix(sample_cnt, cb_cfg.input_ch_cnt, cb_cfg.output_ch_cnt, callback_tmp_buf.data(), callback_tmp_buf.data()); + AudioBackend::downmix(sample_cnt, cb_cfg.input_ch_cnt, output_channel_layout, callback_tmp_buf.data(), callback_tmp_buf.data()); if (cb_cfg.target_volume != cb_cfg.current_volume) { diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h index 0e0b6327ef..609215fbe4 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h @@ -477,6 +477,7 @@ private: bool enable_time_stretching = false; bool dump_to_file = false; AudioChannelCnt channels = AudioChannelCnt::STEREO; + audio_channel_layout channel_layout = audio_channel_layout::automatic; audio_renderer renderer = audio_renderer::null; audio_provider provider = audio_provider::none; RsxaudioAvportIdx avport = RsxaudioAvportIdx::HDMI_0; @@ -503,8 +504,8 @@ private: RsxaudioAvportIdx avport_idx = RsxaudioAvportIdx::HDMI_0; u8 mute_state : SYS_RSXAUDIO_AVPORT_CNT = 0b11111; - u8 input_ch_cnt : 4 = 2; - u8 output_ch_cnt : 4 = 2; + u8 input_ch_cnt : 4 = 2; + u8 output_channel_layout : 4 = static_cast(audio_channel_layout::stereo); bool ready : 1 = false; bool convert_to_s16 : 1 = false; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 38fc78304b..4dddee05a1 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -247,6 +247,7 @@ struct cfg_root : cfg::node cfg::_bool convert_to_s16{ this, "Convert to 16 bit", false, true }; cfg::_enum format{ this, "Audio Format", audio_format::stereo, false }; cfg::uint<0, 0xFF> formats{ this, "Audio Formats", static_cast(audio_format_flag::lpcm_2_48khz), false }; + cfg::_enum channel_layout{ this, "Audio Channel Layout", audio_channel_layout::automatic, false }; cfg::string audio_device{ this, "Audio Device", "@@@default@@@", true }; cfg::_int<0, 200> volume{ this, "Master Volume", 100, true }; cfg::_bool enable_buffering{ this, "Enable Buffering", true, true }; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 4194fe7c06..6a13b6129d 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -120,6 +120,27 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](audio_channel_layout value) + { + switch (value) + { + case audio_channel_layout::automatic: return "Automatic"; + case audio_channel_layout::mono: return "Mono"; + case audio_channel_layout::stereo: return "Stereo"; + case audio_channel_layout::stereo_lfe: return "Stereo LFE"; + case audio_channel_layout::quadraphonic: return "Quadraphonic"; + case audio_channel_layout::quadraphonic_lfe: return "Quadraphonic LFE"; + case audio_channel_layout::surround_5_1: return "Surround 5.1"; + case audio_channel_layout::surround_7_1: return "Surround 7.1"; + } + + return unknown; + }); +} + template <> void fmt_class_string::format(std::string& out, u64 arg) { diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index e57910bc30..f430bec500 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -94,6 +94,18 @@ enum class audio_format_flag : unsigned dts = 0x00000008, // DTS 5.1 Ch. }; +enum class audio_channel_layout +{ + automatic, + mono, + stereo, + stereo_lfe, + quadraphonic, + quadraphonic_lfe, + surround_5_1, + surround_7_1, +}; + enum class music_handler { null, diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 3da1f7ba9d..fc5c0a103a 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1200,6 +1200,19 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case audio_avport::spdif_1: return tr("SPDIF 1", "Audio Avport"); } break; + case emu_settings_type::AudioChannelLayout: + switch (static_cast(index)) + { + case audio_channel_layout::automatic: return tr("Auto", "Audio Channel Layout"); + case audio_channel_layout::mono: return tr("Mono", "Audio Channel Layout"); + case audio_channel_layout::stereo: return tr("Stereo", "Audio Channel Layout"); + case audio_channel_layout::stereo_lfe: return tr("Stereo LFE", "Audio Channel Layout"); + case audio_channel_layout::quadraphonic: return tr("Quadraphonic", "Audio Channel Layout"); + case audio_channel_layout::quadraphonic_lfe: return tr("Quadraphonic LFE", "Audio Channel Layout"); + case audio_channel_layout::surround_5_1: return tr("Surround 5.1", "Audio Channel Layout"); + case audio_channel_layout::surround_7_1: return tr("Surround 7.1", "Audio Channel Layout"); + } + break; case emu_settings_type::LicenseArea: switch (static_cast(index)) { diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 9316014fd3..c18a7bd38f 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -134,6 +134,7 @@ enum class emu_settings_type AudioProvider, AudioAvport, AudioDevice, + AudioChannelLayout, MasterVolume, EnableBuffering, AudioBufferDuration, @@ -321,6 +322,7 @@ inline static const QMap settings_location = { emu_settings_type::AudioProvider, { "Audio", "Audio Provider"}}, { emu_settings_type::AudioAvport, { "Audio", "RSXAudio Avport"}}, { emu_settings_type::AudioDevice, { "Audio", "Audio Device"}}, + { emu_settings_type::AudioChannelLayout, { "Audio", "Audio Channel Layout"}}, { emu_settings_type::MasterVolume, { "Audio", "Master Volume"}}, { emu_settings_type::EnableBuffering, { "Audio", "Enable Buffering"}}, { emu_settings_type::AudioBufferDuration, { "Audio", "Desired Audio Buffer Duration"}}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index e031451729..0df710c76f 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1007,6 +1007,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std get_audio_output_devices(false); change_audio_output_device(0); // Set device to 'Default' }); + + m_emu_settings->EnhanceComboBox(ui->combo_audio_channel_layout, emu_settings_type::AudioChannelLayout); + SubscribeTooltip(ui->gb_audio_channel_layout, tooltips.settings.audio_channel_layout); connect(ui->combo_audio_format, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 4182b30409..c265f67f5e 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -1161,6 +1161,18 @@ + + + + Audio Output Format + + + + + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 03c3d5ad3e..c334dc9c44 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -65,7 +65,8 @@ public: const QString audio_device = tr("Controls which device is used by audio backend."); const QString audio_dump = tr("Saves all audio as a raw wave file. If unsure, leave this unchecked."); const QString convert = tr("Uses 16-bit audio samples instead of default 32-bit floating point.\nUse with buggy audio drivers if you have no sound or completely broken sound."); - const QString audio_format = tr("Determines the sound format.\nConfigure this setting if you want to switch between stereo and surround sound.\nChanging these values requires a restart of the game.\nThe manual setting will use your selected formats while the automatic setting will let the game choose from all available formats."); + const QString audio_format = tr("Determines the sound format of the emulation.\nConfigure this setting if you want to switch between stereo and surround sound.\nChanging these values requires a restart of the game.\nThe manual setting will use your selected formats while the automatic setting will let the game choose from all available formats."); + const QString audio_channel_layout = tr("Determines the sound format of RPCS3.\nUse 'Auto' to let RPCS3 decide the best format based on the audio device and the emulated audio format."); const QString master_volume = tr("Controls the overall volume of the emulation.\nValues above 100% might reduce the audio quality."); const QString enable_buffering = tr("Enables audio buffering, which reduces crackle/stutter but increases audio latency."); const QString audio_buffer_duration = tr("Target buffer duration in milliseconds.\nHigher values make the buffering algorithm's job easier, but may introduce noticeable audio latency.");