Add 6 channel downmixing support for Audout

The specific attenuations used for each channel are taken from Ryujinx.
This commit is contained in:
Billy Laws 2022-01-22 21:42:38 +00:00 committed by PixelyIon
parent 2e1a1a965d
commit 78238d550a
10 changed files with 98 additions and 27 deletions

View File

@ -5,7 +5,7 @@
namespace skyline::audio { namespace skyline::audio {
Audio::Audio(const DeviceState &state) : oboe::AudioStreamCallback() { Audio::Audio(const DeviceState &state) : oboe::AudioStreamCallback() {
builder.setChannelCount(constant::ChannelCount); builder.setChannelCount(constant::StereoChannelCount);
builder.setSampleRate(constant::SampleRate); builder.setSampleRate(constant::SampleRate);
builder.setFormat(constant::PcmFormat); builder.setFormat(constant::PcmFormat);
builder.setFramesPerCallback(constant::MixBufferSize); builder.setFramesPerCallback(constant::MixBufferSize);

View File

@ -9,9 +9,10 @@
namespace skyline { namespace skyline {
namespace constant { namespace constant {
constexpr u16 SampleRate{48000}; //!< The common sampling rate to use for audio output constexpr u16 SampleRate{48000}; //!< The common sampling rate to use for audio output
constexpr u8 ChannelCount{2}; //!< The common amount of channels to use for audio output constexpr u8 StereoChannelCount{2}; //!< Channels to use for stereo audio output
constexpr u16 MixBufferSize{960}; //!< The size of the mix buffer by default constexpr u8 SurroundChannelCount{6}; //!< Channels to use for surround audio output (downsampled by backend)
constexpr auto PcmFormat{oboe::AudioFormat::I16}; //!< The common PCM data format to use for audio output constexpr u16 MixBufferSize{960}; //!< Default size of the audren mix buffer
constexpr auto PcmFormat{oboe::AudioFormat::I16}; //!< PCM data format to use for audio output
} }
namespace audio { namespace audio {

View File

@ -0,0 +1,57 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
#include "common.h"
namespace skyline::audio {
/**
* @brief Format of a 5.1 channel audio sample
*/
struct Surround51Sample {
i16 frontLeft;
i16 frontRight;
i16 centre;
i16 lowFrequency;
i16 backLeft;
i16 backRight;
};
/**
* @brief Format of a stereo audio sample
*/
struct StereoSample {
i16 left;
i16 right;
};
/**
* @brief Downmixes a buffer of 5.1 surround audio to stereo
*/
std::vector<StereoSample> DownMix(span<Surround51Sample> surroundSamples) {
constexpr i16 FixedPointMultiplier{1000}; //!< Avoids using floating point maths
constexpr i16 Attenuation3Db{707}; //! 10^(-3/20)
constexpr i16 Attenuation6Db{501}; //! 10^(-6/20)
constexpr i16 Attenuation12Db{251}; //! 10^(-6/20)
auto downmixChannel{[](i32 front, i32 centre, i32 lowFrequency, i32 back) {
return static_cast<i16>(front +
(centre * Attenuation3Db +
lowFrequency * Attenuation12Db +
back * Attenuation6Db) / FixedPointMultiplier);
}};
std::vector<StereoSample> stereoSamples(surroundSamples.size());
for (size_t i{}; i < surroundSamples.size(); i++) {
auto &surroundSample = surroundSamples[i];
auto &stereoSample = stereoSamples[i];
stereoSample.left = downmixChannel(surroundSample.frontLeft, surroundSample.centre, surroundSample.lowFrequency, surroundSample.backLeft);
stereoSample.right = downmixChannel(surroundSample.frontRight, surroundSample.centre, surroundSample.lowFrequency, surroundSample.backRight);
}
return stereoSamples;
}
}

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "downmixer.h"
#include "track.h" #include "track.h"
namespace skyline::audio { namespace skyline::audio {
@ -11,7 +12,7 @@ namespace skyline::audio {
if (sampleRate != constant::SampleRate) if (sampleRate != constant::SampleRate)
throw exception("Unsupported audio sample rate: {}", sampleRate); throw exception("Unsupported audio sample rate: {}", sampleRate);
if (channelCount != constant::ChannelCount) if (channelCount != constant::StereoChannelCount && channelCount != constant::SurroundChannelCount)
throw exception("Unsupported quantity of audio channels: {}", channelCount); throw exception("Unsupported quantity of audio channels: {}", channelCount);
} }
@ -64,8 +65,14 @@ namespace skyline::audio {
}; };
identifiers.push_front(identifier); identifiers.push_front(identifier);
if (channelCount == constant::SurroundChannelCount) {
auto stereoBuffer{DownMix(buffer.cast<Surround51Sample>())};
samples.Append(span(stereoBuffer).cast<i16>());
} else {
samples.Append(buffer); samples.Append(buffer);
} }
}
void AudioTrack::CheckReleasedBuffers() { void AudioTrack::CheckReleasedBuffers() {
bool anyReleased{}; bool anyReleased{};

View File

@ -20,7 +20,7 @@ namespace skyline::audio {
u32 sampleRate; u32 sampleRate;
public: public:
CircularBuffer<i16, constant::SampleRate * constant::ChannelCount * 10> samples; //!< A circular buffer with all appended audio samples CircularBuffer<i16, constant::SampleRate * constant::StereoChannelCount * 10> samples; //!< A circular buffer with all appended audio samples
std::mutex bufferLock; //!< Synchronizes appending to audio buffers std::mutex bufferLock; //!< Synchronizes appending to audio buffers
AudioOutState playbackState{AudioOutState::Stopped}; //!< The current state of playback AudioOutState playbackState{AudioOutState::Stopped}; //!< The current state of playback

View File

@ -41,7 +41,7 @@ namespace skyline::service::audio {
} }
Result IAudioDevice::GetActiveChannelCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result IAudioDevice::GetActiveChannelCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push<u32>(constant::ChannelCount); response.Push<u32>(constant::StereoChannelCount);
return {}; return {};
} }
} }

View File

@ -14,21 +14,27 @@ namespace skyline::service::audio {
} }
Result IAudioOutManager::OpenAudioOut(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result IAudioOutManager::OpenAudioOut(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto sampleRate{request.Pop<u32>()}; struct AudioInputParams {
auto channelCount{static_cast<u16>(request.Pop<u32>())}; u32 sampleRate;
u16 channelCount;
u16 _pad_;
};
auto &inputParams{request.Pop<AudioInputParams>()};
Logger::Debug("Opening an IAudioOut with sample rate: {}, channel count: {}", sampleRate, channelCount); inputParams.sampleRate = inputParams.sampleRate ? inputParams.sampleRate : constant::SampleRate;
inputParams.channelCount = inputParams.channelCount <= constant::StereoChannelCount ? constant::StereoChannelCount : constant::SurroundChannelCount;
sampleRate = sampleRate ? sampleRate : constant::SampleRate; inputParams._pad_ = 0;
channelCount = channelCount ? channelCount : constant::ChannelCount; Logger::Debug("Opening an IAudioOut with sample rate: {}, channel count: {}", inputParams.sampleRate, inputParams.channelCount);
manager.RegisterService(std::make_shared<IAudioOut>(state, manager, channelCount, sampleRate), session, response); manager.RegisterService(std::make_shared<IAudioOut>(state, manager, inputParams.channelCount, inputParams.sampleRate), session, response);
response.Push<AudioInputParams>(inputParams);
response.Push<u32>(sampleRate);
response.Push<u16>(channelCount);
response.Push<u16>(0);
response.Push(static_cast<u32>(skyline::audio::AudioFormat::Int16)); response.Push(static_cast<u32>(skyline::audio::AudioFormat::Int16));
response.Push(static_cast<u32>(skyline::audio::AudioOutState::Stopped)); response.Push(static_cast<u32>(skyline::audio::AudioOutState::Stopped));
if (request.inputBuf.at(0).empty() || !request.inputBuf.at(0)[0])
request.outputBuf.at(0).copy_from(constant::DefaultAudioOutName);
else
request.outputBuf.at(0).copy_from(request.inputBuf.at(0));
return {}; return {};
} }
} }

View File

@ -7,7 +7,7 @@
namespace skyline::service::audio::IAudioRenderer { namespace skyline::service::audio::IAudioRenderer {
IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParameters &parameters) IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParameters &parameters)
: systemEvent(std::make_shared<type::KEvent>(state, true)), parameters(parameters), BaseService(state, manager) { : systemEvent(std::make_shared<type::KEvent>(state, true)), parameters(parameters), BaseService(state, manager) {
track = state.audio->OpenTrack(constant::ChannelCount, constant::SampleRate, [&]() { systemEvent->Signal(); }); track = state.audio->OpenTrack(constant::StereoChannelCount, constant::SampleRate, [&]() { systemEvent->Signal(); });
track->Start(); track->Start();
memoryPools.resize(parameters.effectCount + parameters.voiceCount * 4); memoryPools.resize(parameters.effectCount + parameters.voiceCount * 4);
@ -144,7 +144,7 @@ namespace skyline::service::audio::IAudioRenderer {
if (voiceBufferSize == 0) if (voiceBufferSize == 0)
break; break;
pendingSamples -= voiceBufferSize / constant::ChannelCount; pendingSamples -= voiceBufferSize / constant::StereoChannelCount;
for (auto index{voiceBufferOffset}; index < voiceBufferOffset + voiceBufferSize; index++) { for (auto index{voiceBufferOffset}; index < voiceBufferOffset + voiceBufferSize; index++) {
if (writtenSamples == bufferOffset) { if (writtenSamples == bufferOffset) {

View File

@ -69,7 +69,7 @@ namespace skyline {
std::vector<MemoryPool> memoryPools; std::vector<MemoryPool> memoryPools;
std::vector<Effect> effects; std::vector<Effect> effects;
std::vector<Voice> voices; std::vector<Voice> voices;
std::array<i16, constant::MixBufferSize * constant::ChannelCount> sampleBuffer{}; //!< The final output data that is appended to the stream std::array<i16, constant::MixBufferSize * constant::StereoChannelCount> sampleBuffer{}; //!< The final output data that is appended to the stream
skyline::audio::AudioOutState playbackState{skyline::audio::AudioOutState::Stopped}; skyline::audio::AudioOutState playbackState{skyline::audio::AudioOutState::Stopped};
/** /**

View File

@ -79,13 +79,13 @@ namespace skyline::service::audio::IAudioRenderer {
if (sampleRate != constant::SampleRate) if (sampleRate != constant::SampleRate)
samples = resampler.ResampleBuffer(samples, static_cast<double>(sampleRate) / constant::SampleRate, channelCount); samples = resampler.ResampleBuffer(samples, static_cast<double>(sampleRate) / constant::SampleRate, channelCount);
if (channelCount == 1 && constant::ChannelCount != channelCount) { if (channelCount == 1 && constant::StereoChannelCount != channelCount) {
auto originalSize{samples.size()}; auto originalSize{samples.size()};
samples.resize((originalSize / channelCount) * constant::ChannelCount); samples.resize((originalSize / channelCount) * constant::StereoChannelCount);
for (auto monoIndex{originalSize - 1}, targetIndex{samples.size()}; monoIndex > 0; monoIndex--) { for (auto monoIndex{originalSize - 1}, targetIndex{samples.size()}; monoIndex > 0; monoIndex--) {
auto sample{samples[monoIndex]}; auto sample{samples[monoIndex]};
for (u8 i{}; i < constant::ChannelCount; i++) for (u8 i{}; i < constant::StereoChannelCount; i++)
samples[--targetIndex] = sample; samples[--targetIndex] = sample;
} }
} }
@ -105,9 +105,9 @@ namespace skyline::service::audio::IAudioRenderer {
} }
outOffset = sampleOffset; outOffset = sampleOffset;
outSize = std::min(maxSamples * constant::ChannelCount, static_cast<u32>(samples.size() - sampleOffset)); outSize = std::min(maxSamples * constant::StereoChannelCount, static_cast<u32>(samples.size() - sampleOffset));
output.playedSamplesCount += outSize / constant::ChannelCount; output.playedSamplesCount += outSize / constant::StereoChannelCount;
sampleOffset += outSize; sampleOffset += outSize;
if (sampleOffset == samples.size()) { if (sampleOffset == samples.size()) {