audio: allow to choose channel layouts

This commit is contained in:
Megamouse 2024-03-26 19:39:37 +01:00
parent a6fa091ab3
commit bbb4c109d7
22 changed files with 343 additions and 99 deletions

View File

@ -33,7 +33,12 @@ u32 AudioBackend::get_sample_size() const
u32 AudioBackend::get_channels() const
{
return static_cast<std::underlying_type_t<decltype(m_channels)>>(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<u32>(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);
}

View File

@ -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 <numbers>
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<AudioChannelCnt from, AudioChannelCnt to>
template<AudioChannelCnt src_ch_cnt, audio_channel_layout dst_layout>
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<u32>(from) > static_cast<u32>(to), "FROM channel count must be bigger than TO");
const u32 dst_ch_cnt = default_layout_channel_count(dst_layout);
if (static_cast<u32>(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<f32> / 2;
static constexpr f32 surround_coef = std::numbers::sqrt2_v<f32> / 2;
for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast<u32>(from), dst_sample += static_cast<u32>(to))
for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast<u32>(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<u32>(AudioChannelCnt::SURROUND_7_1))
switch (src_ch_cnt)
{
if (dst_ch_cnt == static_cast<u32>(AudioChannelCnt::SURROUND_5_1))
case static_cast<u32>(AudioChannelCnt::SURROUND_7_1):
{
switch (dst_layout)
{
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::SURROUND_5_1>(sample_cnt, src, dst);
}
else if (dst_ch_cnt == static_cast<u32>(AudioChannelCnt::STEREO))
{
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::STEREO>(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<AudioChannelCnt::SURROUND_7_1, audio_channel_layout::mono>(sample_cnt, src, dst);
break;
case audio_channel_layout::stereo:
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, audio_channel_layout::stereo>(sample_cnt, src, dst);
break;
case audio_channel_layout::stereo_lfe:
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, audio_channel_layout::stereo_lfe>(sample_cnt, src, dst);
break;
case audio_channel_layout::quadraphonic:
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, audio_channel_layout::quadraphonic>(sample_cnt, src, dst);
break;
case audio_channel_layout::quadraphonic_lfe:
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, audio_channel_layout::quadraphonic_lfe>(sample_cnt, src, dst);
break;
case audio_channel_layout::surround_5_1:
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, audio_channel_layout::surround_5_1>(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<u32>(AudioChannelCnt::SURROUND_5_1))
case static_cast<u32>(AudioChannelCnt::SURROUND_5_1):
{
if (dst_ch_cnt == static_cast<u32>(AudioChannelCnt::STEREO))
switch (dst_layout)
{
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, AudioChannelCnt::STEREO>(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<AudioChannelCnt::SURROUND_5_1, audio_channel_layout::mono>(sample_cnt, src, dst);
break;
case audio_channel_layout::stereo:
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, audio_channel_layout::stereo>(sample_cnt, src, dst);
break;
case audio_channel_layout::stereo_lfe:
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, audio_channel_layout::stereo_lfe>(sample_cnt, src, dst);
break;
case audio_channel_layout::quadraphonic:
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, audio_channel_layout::quadraphonic>(sample_cnt, src, dst);
break;
case audio_channel_layout::quadraphonic_lfe:
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, audio_channel_layout::quadraphonic_lfe>(sample_cnt, src, dst);
break;
default:
fmt::throw_exception("Invalid downmix combination: %u -> %s", src_ch_cnt, dst_layout);
}
break;
}
else
case static_cast<u32>(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<AudioChannelCnt::STEREO, audio_channel_layout::mono>(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<u32(u32, void *)> m_write_callback{};

View File

@ -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<u32>(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<AudioChannelCnt>(std::min(static_cast<u32>(convert_channel_count(device.ch_cnt)), static_cast<u32>(ch_cnt)));
setup_channel_layout(static_cast<u32>(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<u32>(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<u32>(freq), static_cast<u32>(sample_size), static_cast<u32>(ch_cnt));

View File

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

View File

@ -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<AudioChannelCnt>(std::min(static_cast<u32>(convert_channel_count(vd.InputChannels)), static_cast<u32>(ch_cnt)));;
setup_channel_layout(static_cast<u32>(ch_cnt), vd.InputChannels, layout, FAudio_);
FAudioWaveFormatEx waveformatex;
waveformatex.wFormatTag = get_convert_to_s16() ? FAUDIO_FORMAT_PCM : FAUDIO_FORMAT_IEEE_FLOAT;

View File

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

View File

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

View File

@ -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<AudioChannelCnt>(std::min(static_cast<u32>(convert_channel_count(vd.InputChannels)), static_cast<u32>(ch_cnt)));
setup_channel_layout(static_cast<u32>(ch_cnt), vd.InputChannels, layout, XAudio);
WAVEFORMATEX waveformatex{};
waveformatex.wFormatTag = get_convert_to_s16() ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;

View File

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

View File

@ -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<u32>(req_ch_cnt);
audio_sampling_rate = static_cast<u32>(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<u32>(static_cast<u32>(cfg.backend_ch_cnt) * cfg.audio_sampling_rate * cfg.audio_sample_size * buffer_dur_mult));
cb_ringbuf.set_buf_size(static_cast<u32>(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<u32>(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<u32>(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * static_cast<u32>(cfg.backend_ch_cnt))));
const auto& [buffer, samples] = resampler.get_samples(static_cast<u32>(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<u32>(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<u32>(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<u32>(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<bool>(g_cfg.audio.convert_to_s16),
.dump_to_file = static_cast<bool>(g_cfg.audio.dump_to_file),
.channel_layout = g_cfg.audio.channel_layout,
.renderer = g_cfg.audio.renderer,
.provider = g_cfg.audio.provider
};

View File

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

View File

@ -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<f32*>(dst_buffer.block.data()));
AudioBackend::downmix(CELL_REC_AUDIO_BLOCK_SAMPLES * sample.channels, sample.channels, audio_channel_layout::stereo, src, reinterpret_cast<f32*>(dst_buffer.block.data()));
}
else
{

View File

@ -1374,6 +1374,7 @@ rsxaudio_backend_thread::emu_audio_cfg rsxaudio_backend_thread::get_emu_cfg()
.enable_time_stretching = static_cast<bool>(g_cfg.audio.enable_time_stretching),
.dump_to_file = static_cast<bool>(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<u32>(port_cfg.freq);
val.input_ch_cnt = static_cast<u32>(port_cfg.ch_cnt);
val.output_ch_cnt = backend_ch_cnt;
val.output_channel_layout = static_cast<u8>(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<u8>(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<audio_channel_layout>(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<u32>(AudioSampleSize::S16) * static_cast<u32>(AudioSampleSize::FLOAT) : bytes_ch_adjusted;
ensure(callback_tmp_buf.size() * static_cast<u32>(AudioSampleSize::FLOAT) >= bytes_from_rb);
const u32 byte_cnt = static_cast<u32>(ringbuf.pop(callback_tmp_buf.data(), bytes_from_rb, true));
const u32 sample_cnt = byte_cnt / static_cast<u32>(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)
{

View File

@ -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<u8>(audio_channel_layout::stereo);
bool ready : 1 = false;
bool convert_to_s16 : 1 = false;

View File

@ -247,6 +247,7 @@ struct cfg_root : cfg::node
cfg::_bool convert_to_s16{ this, "Convert to 16 bit", false, true };
cfg::_enum<audio_format> format{ this, "Audio Format", audio_format::stereo, false };
cfg::uint<0, 0xFF> formats{ this, "Audio Formats", static_cast<u32>(audio_format_flag::lpcm_2_48khz), false };
cfg::_enum<audio_channel_layout> 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 };

View File

@ -120,6 +120,27 @@ void fmt_class_string<audio_renderer>::format(std::string& out, u64 arg)
});
}
template <>
void fmt_class_string<audio_channel_layout>::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<detail_level>::format(std::string& out, u64 arg)
{

View File

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

View File

@ -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<audio_channel_layout>(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<CellSysutilLicenseArea>(index))
{

View File

@ -134,6 +134,7 @@ enum class emu_settings_type
AudioProvider,
AudioAvport,
AudioDevice,
AudioChannelLayout,
MasterVolume,
EnableBuffering,
AudioBufferDuration,
@ -321,6 +322,7 @@ inline static const QMap<emu_settings_type, cfg_location> 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"}},

View File

@ -1007,6 +1007,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> 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<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
{

View File

@ -1161,6 +1161,18 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_audio_channel_layout">
<property name="title">
<string>Audio Output Format</string>
</property>
<layout class="QVBoxLayout" name="gb_audio_channel_layout_layout">
<item>
<widget class="QComboBox" name="combo_audio_channel_layout"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_audio_provider">
<property name="title">

View File

@ -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.");