Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

audio: allow to choose channel layouts #15359

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 61 additions & 16 deletions rpcs3/Emu/Audio/AudioBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (layout)
{
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)
{
switch (raw)
if (channels == 0)
{
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");
}

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);
}
195 changes: 153 additions & 42 deletions rpcs3/Emu/Audio/AudioBackend.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -30,6 +32,7 @@ enum class AudioSampleSize : u32
S16 = sizeof(s16),
};

// This enum is only used for emulation
enum class AudioChannelCnt : u32
{
STEREO = 2,
Expand Down Expand Up @@ -69,7 +72,7 @@ class AudioBackend
// 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;
Expand Down Expand Up @@ -122,6 +125,7 @@ class AudioBackend
u32 get_sample_size() const;

u32 get_channels() const;
audio_channel_layout get_channel_layout() const;

bool get_convert_to_s16() const;

Expand Down Expand Up @@ -158,51 +162,91 @@ class AudioBackend
*/
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;
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 (from == AudioChannelCnt::SURROUND_7_1)
else if constexpr (src_ch_cnt == AudioChannelCnt::SURROUND_7_1)
{
static_assert(to == AudioChannelCnt::STEREO || to == AudioChannelCnt::SURROUND_5_1, "Invalid TO channel count");

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;
Expand All @@ -211,59 +255,126 @@ class AudioBackend
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))
{
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
case static_cast<u32>(AudioChannelCnt::SURROUND_7_1):
{
switch (dst_layout)
{
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);
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);
}
else
break;
}
case static_cast<u32>(AudioChannelCnt::STEREO):
{
switch (dst_layout)
{
fmt::throw_exception("Invalid downmix combination: %u -> %u", src_ch_cnt, dst_ch_cnt);
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;
}
else
default:
{
fmt::throw_exception("Invalid downmix combination: %u -> %u", src_ch_cnt, dst_ch_cnt);
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{};
Expand Down