Skip to content

Commit

Permalink
audio: allow to choose channel layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
Megamouse committed Mar 26, 2024
1 parent efbf044 commit e4885a9
Show file tree
Hide file tree
Showing 22 changed files with 327 additions and 95 deletions.
70 changes: 54 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,56 @@ 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)
{
if (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;
}

const u32 channels = std::min(input_channel_count, output_channel_count);
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

0 comments on commit e4885a9

Please sign in to comment.