mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
Audio: support 12-channel AudioOut2 output with SDL (#498)
* Audio: support 12-channel AudioOut2 output via stereo downmix * Audio: document 7.1.4 downmix matrix and align s16 coefficients * audio: use shared 12-to-8 channel conversion for SDL --------- Co-authored-by: Airsoft Project <airsoft@example.com> Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
+47
-37
@@ -105,7 +105,7 @@ private:
|
||||
Format format = Format::Unknown;
|
||||
uint64_t last_output_time = 0;
|
||||
int channels_num = 0;
|
||||
int volume[8] = {};
|
||||
int volume[12] = {};
|
||||
|
||||
SDL_AudioDeviceID audio_device = 0;
|
||||
SDL_AudioSpec audio_spec = {};
|
||||
@@ -127,7 +127,7 @@ private:
|
||||
static bool FormatIsFloat(Format format);
|
||||
static bool FormatIsStd(Format format);
|
||||
static uint32_t BytesPerSample(Format format);
|
||||
static uint32_t FrameSize(const PortOut& port);
|
||||
static uint32_t OutputChannels(const PortOut& port);
|
||||
static SDL_AudioFormat SdlFormat(Format format);
|
||||
static bool OpenSdlDevice(PortOut* port);
|
||||
static void CloseSdlDevice(PortOut* port);
|
||||
@@ -201,8 +201,14 @@ Audio::~Audio() {
|
||||
}
|
||||
|
||||
bool Audio::FormatIsFloat(Format format) {
|
||||
return (format == Format::FloatMono || format == Format::FloatStereo ||
|
||||
format == Format::Float8Ch || format == Format::Float8ChStd);
|
||||
switch (format) {
|
||||
case Format::FloatMono:
|
||||
case Format::FloatStereo:
|
||||
case Format::Float8Ch:
|
||||
case Format::Float8ChStd:
|
||||
case Format::Float12Ch: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Audio::FormatIsStd(Format format) {
|
||||
@@ -213,8 +219,9 @@ uint32_t Audio::BytesPerSample(Format format) {
|
||||
return FormatIsFloat(format) ? sizeof(float) : sizeof(int16_t);
|
||||
}
|
||||
|
||||
uint32_t Audio::FrameSize(const PortOut& port) {
|
||||
return BytesPerSample(port.format) * port.channels_num;
|
||||
uint32_t Audio::OutputChannels(const PortOut& port) {
|
||||
// SDL only takes up to 8 channels. Keep the guest buffer's channel count separate.
|
||||
return std::min(port.channels_num, 8);
|
||||
}
|
||||
|
||||
SDL_AudioFormat Audio::SdlFormat(Format format) {
|
||||
@@ -232,7 +239,7 @@ bool Audio::OpenSdlDevice(PortOut* port) {
|
||||
SDL_AudioSpec desired {};
|
||||
desired.freq = static_cast<int>(port->freq);
|
||||
desired.format = SdlFormat(port->format);
|
||||
desired.channels = static_cast<Uint8>(port->channels_num);
|
||||
desired.channels = static_cast<Uint8>(OutputChannels(*port));
|
||||
desired.samples = static_cast<Uint16>(port->samples_num);
|
||||
desired.callback = nullptr;
|
||||
|
||||
@@ -272,8 +279,9 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
|
||||
const auto frames = port.samples_num;
|
||||
const auto channels = static_cast<uint32_t>(port.channels_num);
|
||||
const auto output_channels = OutputChannels(port);
|
||||
const auto bytes_per_sample = BytesPerSample(port.format);
|
||||
const auto src_size = frames * channels * bytes_per_sample;
|
||||
const bool reorder = channels >= 8 && !FormatIsStd(port.format);
|
||||
|
||||
bool volume_changed = false;
|
||||
for (uint32_t ch = 0; ch < channels; ch++) {
|
||||
@@ -283,24 +291,35 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
}
|
||||
}
|
||||
|
||||
if (!volume_changed && !FormatIsStd(port.format)) {
|
||||
if (!volume_changed && !reorder) {
|
||||
return data;
|
||||
}
|
||||
|
||||
buffer->resize(src_size);
|
||||
buffer->resize(frames * output_channels * bytes_per_sample);
|
||||
|
||||
static constexpr uint32_t STD_8CH_MAP[8] = {0, 1, 2, 3, 6, 7, 4, 5};
|
||||
// SDL wants back speakers before side speakers; non-STD PCM has them reversed.
|
||||
static constexpr uint32_t SDL_8CH_MAP[8] = {0, 1, 2, 3, 6, 7, 4, 5};
|
||||
|
||||
if (FormatIsFloat(port.format)) {
|
||||
auto* dst = reinterpret_cast<float*>(buffer->data());
|
||||
const auto* src = static_cast<const float*>(data);
|
||||
|
||||
for (uint32_t frame = 0; frame < frames; frame++) {
|
||||
for (uint32_t ch = 0; ch < channels; ch++) {
|
||||
const auto src_ch =
|
||||
(FormatIsStd(port.format) && channels == 8 ? STD_8CH_MAP[ch] : ch);
|
||||
dst[frame * channels + ch] = src[frame * channels + src_ch] *
|
||||
(static_cast<float>(port.volume[ch]) / 32768.0f);
|
||||
for (uint32_t ch = 0; ch < output_channels; ch++) {
|
||||
const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch;
|
||||
dst[frame * output_channels + ch] =
|
||||
src[frame * channels + src_ch] *
|
||||
(static_cast<float>(port.volume[src_ch]) / 32768.0f);
|
||||
}
|
||||
if (channels == 12) {
|
||||
// Add the four top channels to their front/back channels, turning 12 into 8.
|
||||
// SDL handles the rest if the output device has fewer channels.
|
||||
static constexpr uint32_t HEIGHT_DST[4] = {0, 1, 4, 5};
|
||||
for (uint32_t ch = 0; ch < 4; ch++) {
|
||||
dst[frame * output_channels + HEIGHT_DST[ch]] +=
|
||||
src[frame * channels + 8 + ch] *
|
||||
(static_cast<float>(port.volume[8 + ch]) / 32768.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -308,17 +327,16 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
const auto* src = static_cast<const int16_t*>(data);
|
||||
|
||||
for (uint32_t frame = 0; frame < frames; frame++) {
|
||||
for (uint32_t ch = 0; ch < channels; ch++) {
|
||||
const auto src_ch =
|
||||
(FormatIsStd(port.format) && channels == 8 ? STD_8CH_MAP[ch] : ch);
|
||||
for (uint32_t ch = 0; ch < output_channels; ch++) {
|
||||
const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch;
|
||||
int64_t sample =
|
||||
static_cast<int64_t>(src[frame * channels + src_ch]) * port.volume[ch] / 32768;
|
||||
static_cast<int64_t>(src[frame * channels + src_ch]) * port.volume[src_ch] / 32768;
|
||||
if (sample > std::numeric_limits<int16_t>::max()) {
|
||||
sample = std::numeric_limits<int16_t>::max();
|
||||
} else if (sample < std::numeric_limits<int16_t>::min()) {
|
||||
sample = std::numeric_limits<int16_t>::min();
|
||||
}
|
||||
dst[frame * channels + ch] = static_cast<int16_t>(sample);
|
||||
dst[frame * output_channels + ch] = static_cast<int16_t>(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,8 +352,10 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
|
||||
}
|
||||
|
||||
std::vector<uint8_t> prepared_buffer;
|
||||
const void* prepared_data = PrepareOutputBuffer(*port, data, &prepared_buffer);
|
||||
const auto prepared_size = FrameSize(*port) * port->samples_num;
|
||||
const void* prepared_data = PrepareOutputBuffer(*port, data, &prepared_buffer);
|
||||
const auto output_channels = OutputChannels(*port);
|
||||
const auto prepared_size =
|
||||
BytesPerSample(port->format) * output_channels * port->samples_num;
|
||||
|
||||
std::vector<uint8_t> convert_buffer;
|
||||
const void* queue_data = prepared_data;
|
||||
@@ -343,7 +363,7 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
|
||||
|
||||
SDL_AudioCVT cvt {};
|
||||
const int cvt_result =
|
||||
SDL_BuildAudioCVT(&cvt, SdlFormat(port->format), static_cast<Uint8>(port->channels_num),
|
||||
SDL_BuildAudioCVT(&cvt, SdlFormat(port->format), static_cast<Uint8>(output_channels),
|
||||
static_cast<int>(port->freq), port->audio_spec.format,
|
||||
port->audio_spec.channels, port->audio_spec.freq);
|
||||
|
||||
@@ -416,6 +436,7 @@ Audio::Id Audio::AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, For
|
||||
case Format::Float8Ch:
|
||||
case Format::Signed16bit8ChStd:
|
||||
case Format::Float8ChStd: port.channels_num = 8; break;
|
||||
case Format::Float12Ch: port.channels_num = 12; break;
|
||||
default: EXIT("unknown format");
|
||||
}
|
||||
|
||||
@@ -488,20 +509,9 @@ bool Audio::AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume) {
|
||||
auto bit = bitflag & 0x1u;
|
||||
|
||||
if (bit == 1) {
|
||||
int src_index = i;
|
||||
if (port.format == Format::Float8ChStd ||
|
||||
port.format == Format::Signed16bit8ChStd) {
|
||||
switch (i) {
|
||||
case 4: src_index = 6; break;
|
||||
case 5: src_index = 7; break;
|
||||
case 6: src_index = 4; break;
|
||||
case 7: src_index = 5; break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
port.volume[i] = volume[src_index];
|
||||
port.volume[i] = volume[i];
|
||||
|
||||
LOGF("\t port.volume[%d] = volume[%d] (%d)\n", i, src_index, volume[src_index]);
|
||||
LOGF("\t port.volume[%d] = volume[%d] (%d)\n", i, i, volume[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ enum class Format {
|
||||
Float8Ch,
|
||||
Signed16bit8ChStd,
|
||||
Float8ChStd,
|
||||
Float12Ch,
|
||||
};
|
||||
|
||||
struct OutputParam {
|
||||
|
||||
@@ -261,6 +261,11 @@ static AudioInternal::Format audioout2_data_format_to_audio_format(uint32_t data
|
||||
case 8:
|
||||
return is_std ? AudioInternal::Format::Float8ChStd
|
||||
: AudioInternal::Format::Float8Ch;
|
||||
case 12:
|
||||
if (!is_std) {
|
||||
return AudioInternal::Format::Float12Ch;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user