mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-25 11:05:28 +03:00
Reimplement audout and audren using yuzu audio_core
The yuzu audio_core code is mostly untouched, with a set of wrappers used to bridge it with skyline kernel primitives. Huge thanks to maide and their advice, whom without this wouldn't have been possible.
This commit is contained in:
parent
ef5456f939
commit
01febe75c4
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -57,3 +57,9 @@
|
||||
[submodule "app/libraries/thread-pool"]
|
||||
path = app/libraries/thread-pool
|
||||
url = https://github.com/bshoshany/thread-pool.git
|
||||
[submodule "app/libraries/cubeb"]
|
||||
path = app/libraries/cubeb
|
||||
url = https://github.com/skyline-emu/cubeb
|
||||
[submodule "app/libraries/audio-core"]
|
||||
path = app/libraries/audio-core
|
||||
url = https://github.com/skyline-emu/audio-core
|
||||
|
@ -51,10 +51,6 @@ add_subdirectory("libraries/fmt")
|
||||
add_subdirectory("libraries/tzcode")
|
||||
target_compile_options(tzcode PRIVATE -Wno-everything)
|
||||
|
||||
# Oboe
|
||||
add_subdirectory("libraries/oboe")
|
||||
include_directories(SYSTEM "libraries/oboe/include")
|
||||
|
||||
# LZ4
|
||||
set(LZ4_BUILD_CLI OFF CACHE BOOL "Build LZ4 CLI" FORCE)
|
||||
set(LZ4_BUILD_LEGACY_LZ4C OFF CACHE BOOL "Build lz4c progam with legacy argument support" FORCE)
|
||||
@ -95,6 +91,13 @@ include_directories(SYSTEM "libraries/opus/include")
|
||||
add_subdirectory("libraries/opus")
|
||||
target_compile_definitions(opus PRIVATE OPUS_WILL_BE_SLOW=1) # libopus will warn when built without optimizations
|
||||
|
||||
# Cubeb
|
||||
set(USE_AAUDIO ON)
|
||||
set(USE_SANITIZERS OFF)
|
||||
set(BUNDLE_SPEEX ON)
|
||||
add_subdirectory("libraries/cubeb")
|
||||
include_directories(SYSTEM "libraries/cubeb/include")
|
||||
|
||||
# Perfetto SDK
|
||||
include_directories(SYSTEM "libraries/perfetto/sdk")
|
||||
add_library(perfetto STATIC libraries/perfetto/sdk/perfetto.cc)
|
||||
@ -142,6 +145,11 @@ add_subdirectory("libraries/shader-compiler")
|
||||
target_include_directories(shader_recompiler PUBLIC "libraries/shader-compiler/include")
|
||||
target_link_libraries_system(shader_recompiler Boost::intrusive Boost::container range-v3)
|
||||
|
||||
# yuzu Audio Core
|
||||
add_subdirectory("libraries/audio-core")
|
||||
include_directories(SYSTEM "libraries/audio-core/include")
|
||||
target_link_libraries_system(audio_core Boost::intrusive Boost::container range-v3)
|
||||
|
||||
# Skyline
|
||||
add_library(skyline SHARED
|
||||
${source_DIR}/driver_jni.cpp
|
||||
@ -168,9 +176,6 @@ add_library(skyline SHARED
|
||||
${source_DIR}/skyline/kernel/types/KPrivateMemory.cpp
|
||||
${source_DIR}/skyline/kernel/types/KSyncObject.cpp
|
||||
${source_DIR}/skyline/audio.cpp
|
||||
${source_DIR}/skyline/audio/track.cpp
|
||||
${source_DIR}/skyline/audio/resampler.cpp
|
||||
${source_DIR}/skyline/audio/adpcm_decoder.cpp
|
||||
${source_DIR}/skyline/gpu.cpp
|
||||
${source_DIR}/skyline/gpu/trait_manager.cpp
|
||||
${source_DIR}/skyline/gpu/memory_manager.cpp
|
||||
@ -265,9 +270,7 @@ add_library(skyline SHARED
|
||||
${source_DIR}/skyline/services/audio/IAudioOut.cpp
|
||||
${source_DIR}/skyline/services/audio/IAudioDevice.cpp
|
||||
${source_DIR}/skyline/services/audio/IAudioRendererManager.cpp
|
||||
${source_DIR}/skyline/services/audio/IAudioRenderer/IAudioRenderer.cpp
|
||||
${source_DIR}/skyline/services/audio/IAudioRenderer/voice.cpp
|
||||
${source_DIR}/skyline/services/audio/IAudioRenderer/memory_pool.cpp
|
||||
${source_DIR}/skyline/services/audio/IAudioRenderer.cpp
|
||||
${source_DIR}/skyline/services/settings/ISettingsServer.cpp
|
||||
${source_DIR}/skyline/services/settings/ISystemSettingsServer.cpp
|
||||
${source_DIR}/skyline/services/apm/IManager.cpp
|
||||
@ -397,5 +400,5 @@ target_include_directories(skyline PRIVATE ${source_DIR}/skyline)
|
||||
# target_precompile_headers(skyline PRIVATE ${source_DIR}/skyline/common.h) # PCH will currently break Intellisense
|
||||
target_compile_options(skyline PRIVATE -Wall -Wno-unknown-attributes -Wno-c++20-extensions -Wno-c++17-extensions -Wno-c99-designator -Wno-reorder -Wno-missing-braces -Wno-unused-variable -Wno-unused-private-field -Wno-dangling-else -Wconversion -fsigned-bitfields)
|
||||
|
||||
target_link_libraries(skyline PRIVATE shader_recompiler)
|
||||
target_link_libraries_system(skyline android perfetto fmt lz4_static tzcode oboe vkma mbedcrypto opus Boost::intrusive Boost::container range-v3 adrenotools tsl::robin_map)
|
||||
target_link_libraries(skyline PRIVATE shader_recompiler audio_core)
|
||||
target_link_libraries_system(skyline android perfetto fmt lz4_static tzcode vkma mbedcrypto opus Boost::intrusive Boost::container range-v3 adrenotools tsl::robin_map)
|
||||
|
1
app/libraries/audio-core
Submodule
1
app/libraries/audio-core
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 645cb10fb1e73a0bdfc0d3693087201845f8195c
|
1
app/libraries/cubeb
Submodule
1
app/libraries/cubeb
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 2d93d734ecff78deb060abd727712bae8ab0489a
|
@ -1,80 +1,55 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <audio_core/audio_core.h>
|
||||
#include <audio_core/audio_out_manager.h>
|
||||
#include <audio_core/common/settings.h>
|
||||
#include <common/settings.h>
|
||||
#include <common/utils.h>
|
||||
#include "audio.h"
|
||||
|
||||
namespace skyline::audio {
|
||||
Audio::Audio(const DeviceState &state) : oboe::AudioStreamCallback() {
|
||||
settings = std::shared_ptr<Settings>{state.settings};
|
||||
|
||||
builder.setChannelCount(constant::StereoChannelCount);
|
||||
builder.setSampleRate(constant::SampleRate);
|
||||
builder.setFormat(constant::PcmFormat);
|
||||
builder.setUsage(oboe::Usage::Game);
|
||||
builder.setCallback(this);
|
||||
builder.setSharingMode(oboe::SharingMode::Exclusive);
|
||||
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
|
||||
|
||||
builder.openManagedStream(outputStream);
|
||||
outputStream->requestStart();
|
||||
namespace AudioCore::Log {
|
||||
void Debug(const std::string &message) {
|
||||
skyline::Logger::Write(skyline::Logger::LogLevel::Debug, message);
|
||||
}
|
||||
|
||||
Audio::~Audio() {
|
||||
outputStream->requestStop();
|
||||
void Info(const std::string &message) {
|
||||
skyline::Logger::Write(skyline::Logger::LogLevel::Info, message);
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioTrack> Audio::OpenTrack(u8 channelCount, u32 sampleRate, const std::function<void()> &releaseCallback) {
|
||||
std::scoped_lock trackGuard{trackLock};
|
||||
|
||||
auto track{std::make_shared<AudioTrack>(channelCount, sampleRate, releaseCallback)};
|
||||
audioTracks.push_back(track);
|
||||
|
||||
return track;
|
||||
void Warn(const std::string &message) {
|
||||
skyline::Logger::Write(skyline::Logger::LogLevel::Warn, message);
|
||||
}
|
||||
|
||||
void Audio::CloseTrack(std::shared_ptr<AudioTrack> &track) {
|
||||
std::scoped_lock trackGuard{trackLock};
|
||||
|
||||
audioTracks.erase(std::remove(audioTracks.begin(), audioTracks.end(), track), audioTracks.end());
|
||||
}
|
||||
|
||||
oboe::DataCallbackResult Audio::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) {
|
||||
auto destBuffer{static_cast<i16 *>(audioData)};
|
||||
auto streamSamples{static_cast<size_t>(numFrames) * static_cast<size_t>(audioStream->getChannelCount())};
|
||||
size_t writtenSamples{};
|
||||
|
||||
{
|
||||
std::scoped_lock trackGuard{trackLock};
|
||||
|
||||
for (auto &track : audioTracks) {
|
||||
if (track->playbackState == AudioOutState::Stopped)
|
||||
continue;
|
||||
|
||||
std::scoped_lock bufferGuard{track->bufferLock};
|
||||
|
||||
if (!*settings->isAudioOutputDisabled) {
|
||||
auto trackSamples{track->samples.Read(span(destBuffer, streamSamples), [](i16 *source, i16 *destination) {
|
||||
*destination = Saturate<i16, i32>(static_cast<u32>(*destination) + static_cast<u32>(*source));
|
||||
}, static_cast<ssize_t>(writtenSamples))};
|
||||
|
||||
writtenSamples = std::max(trackSamples, writtenSamples);
|
||||
|
||||
track->sampleCounter += trackSamples;
|
||||
} else {
|
||||
track->sampleCounter += streamSamples;
|
||||
}
|
||||
track->CheckReleasedBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
if (streamSamples > writtenSamples)
|
||||
memset(destBuffer + writtenSamples, 0, (streamSamples - writtenSamples) * sizeof(i16));
|
||||
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
|
||||
void Audio::onErrorAfterClose(oboe::AudioStream *audioStream, oboe::Result error) {
|
||||
builder.openManagedStream(outputStream);
|
||||
outputStream->requestStart();
|
||||
void Error(const std::string &message) {
|
||||
skyline::Logger::Write(skyline::Logger::LogLevel::Error, message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
skyline::u64 GetClockTicks() {
|
||||
return skyline::util::GetTimeTicks();
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds GetClockNs() {
|
||||
return std::chrono::nanoseconds{skyline::util::GetTimeNs()};
|
||||
}
|
||||
}
|
||||
|
||||
namespace skyline::audio {
|
||||
Audio::Audio(const DeviceState &state)
|
||||
: audioOutManager{std::make_unique<AudioCore::AudioOut::Manager>(audioSystem)},
|
||||
audioRendererManager{std::make_unique<AudioCore::AudioRenderer::Manager>(audioSystem)} {
|
||||
AudioCore::Settings::values.volume = *state.settings->isAudioOutputDisabled ? 0 : 200;
|
||||
}
|
||||
|
||||
Audio::~Audio() = default;
|
||||
|
||||
void Audio::Pause() {
|
||||
audioSystem.AudioCore().GetOutputSink().SetSystemVolume(0.0f);
|
||||
}
|
||||
|
||||
void Audio::Resume() {
|
||||
audioSystem.AudioCore().GetOutputSink().SetSystemVolume(1.0f);
|
||||
}
|
||||
}
|
||||
|
@ -3,61 +3,33 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common/settings.h>
|
||||
#include <audio/track.h>
|
||||
#include <audio_core/core/core.h>
|
||||
#include <common.h>
|
||||
|
||||
namespace AudioCore::AudioOut {
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace skyline::audio {
|
||||
/**
|
||||
* @brief The Audio class is used to mix audio from all tracks
|
||||
* @brief The Audio class is used to bridge yuzu's audio core with services
|
||||
*/
|
||||
class Audio : public oboe::AudioStreamCallback {
|
||||
private:
|
||||
oboe::AudioStreamBuilder builder;
|
||||
oboe::ManagedStream outputStream;
|
||||
std::vector<std::shared_ptr<AudioTrack>> audioTracks;
|
||||
std::mutex trackLock; //!< Synchronizes modifications to the audio tracks
|
||||
std::shared_ptr<Settings> settings;
|
||||
|
||||
class Audio {
|
||||
public:
|
||||
Core::System audioSystem{};
|
||||
std::unique_ptr<AudioCore::AudioOut::Manager> audioOutManager;
|
||||
std::unique_ptr<AudioCore::AudioRenderer::Manager> audioRendererManager;
|
||||
|
||||
Audio(const DeviceState &state);
|
||||
|
||||
~Audio();
|
||||
|
||||
void Pause() {
|
||||
outputStream->requestPause();
|
||||
}
|
||||
void Pause();
|
||||
|
||||
void Resume() {
|
||||
outputStream->requestStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Opens a new track that can be used to play sound
|
||||
* @param channelCount The amount channels that are present in the track
|
||||
* @param sampleRate The sample rate of the track
|
||||
* @param releaseCallback The callback to call when a buffer has been released
|
||||
* @return A shared pointer to a new AudioTrack object
|
||||
*/
|
||||
std::shared_ptr<AudioTrack> OpenTrack(u8 channelCount, u32 sampleRate, const std::function<void()> &releaseCallback);
|
||||
|
||||
/**
|
||||
* @brief Closes a track and frees its data
|
||||
*/
|
||||
void CloseTrack(std::shared_ptr<AudioTrack> &track);
|
||||
|
||||
/**
|
||||
* @brief The callback oboe uses to get audio sample data
|
||||
* @param audioStream The audio stream we are being called by
|
||||
* @param audioData The raw audio sample data
|
||||
* @param numFrames The amount of frames the sample data needs to contain
|
||||
*/
|
||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames);
|
||||
|
||||
/**
|
||||
* @brief The callback oboe uses to notify the application about stream closure
|
||||
* @param audioStream The audio stream we are being called by
|
||||
* @param error The error due to which the stream is being closed
|
||||
*/
|
||||
void onErrorAfterClose(oboe::AudioStream *audioStream, oboe::Result error);
|
||||
void Resume();
|
||||
};
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include "common.h"
|
||||
#include "adpcm_decoder.h"
|
||||
|
||||
namespace skyline::audio {
|
||||
AdpcmDecoder::AdpcmDecoder(std::vector<std::array<i16, 2>> coefficients) : coefficients(std::move(coefficients)) {}
|
||||
|
||||
std::vector<i16> AdpcmDecoder::Decode(span<u8> adpcmData) {
|
||||
constexpr size_t BytesPerFrame{0x8};
|
||||
constexpr size_t SamplesPerFrame{0xE};
|
||||
|
||||
size_t remainingSamples{(adpcmData.size() / BytesPerFrame) * SamplesPerFrame};
|
||||
|
||||
std::vector<i16> output;
|
||||
output.reserve(remainingSamples);
|
||||
|
||||
size_t inputOffset{};
|
||||
|
||||
while (inputOffset < adpcmData.size()) {
|
||||
FrameHeader header{adpcmData[inputOffset++]};
|
||||
|
||||
size_t frameSamples{std::min(SamplesPerFrame, remainingSamples)};
|
||||
|
||||
i32 ctx{};
|
||||
|
||||
for (size_t index{}; index < frameSamples; index++) {
|
||||
i32 sample{};
|
||||
|
||||
if (index & 1) {
|
||||
sample = (ctx << 28) >> 28;
|
||||
} else {
|
||||
ctx = adpcmData[inputOffset++];
|
||||
sample = (ctx << 24) >> 28;
|
||||
}
|
||||
|
||||
i32 prediction{history[0] * coefficients[header.coefficientIndex][0] + history[1] * coefficients[header.coefficientIndex][1]};
|
||||
sample = (sample * (0x800 << header.scale) + prediction + 0x400) >> 11;
|
||||
|
||||
auto saturated{audio::Saturate<i16, i32>(sample)};
|
||||
output.push_back(saturated);
|
||||
history[1] = history[0];
|
||||
history[0] = saturated;
|
||||
}
|
||||
|
||||
remainingSamples -= frameSamples;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::audio {
|
||||
/**
|
||||
* @brief The AdpcmDecoder class handles decoding single channel ADPCM (Adaptive Differential Pulse-Code Modulation) data
|
||||
*/
|
||||
class AdpcmDecoder {
|
||||
private:
|
||||
union FrameHeader {
|
||||
u8 raw;
|
||||
|
||||
struct {
|
||||
u8 scale : 4; //!< The scale factor for this frame
|
||||
u8 coefficientIndex : 3;
|
||||
u8 _pad_ : 1;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(FrameHeader) == 0x1);
|
||||
|
||||
std::array<i32, 2> history{}; //!< The previous samples for decoding the ADPCM stream
|
||||
std::vector<std::array<i16, 2>> coefficients; //!< The coefficients for decoding the ADPCM stream
|
||||
|
||||
public:
|
||||
AdpcmDecoder(std::vector<std::array<i16, 2>> coefficients);
|
||||
|
||||
/**
|
||||
* @brief Decodes a buffer of ADPCM data into I16 PCM
|
||||
*/
|
||||
std::vector<i16> Decode(span <u8> adpcmData);
|
||||
};
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <oboe/Oboe.h>
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline {
|
||||
namespace constant {
|
||||
constexpr u16 SampleRate{48000}; //!< The common sampling rate to use for audio output
|
||||
constexpr u8 StereoChannelCount{2}; //!< Channels to use for stereo audio output
|
||||
constexpr u8 SurroundChannelCount{6}; //!< Channels to use for surround audio output (downsampled by backend)
|
||||
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 {
|
||||
enum class AudioFormat : u8 {
|
||||
Invalid = 0, //!< An invalid PCM format
|
||||
Int8 = 1, //!< 8 bit integer PCM
|
||||
Int16 = 2, //!< 16 bit integer PCM
|
||||
Int24 = 3, //!< 24 bit integer PCM
|
||||
Int32 = 4, //!< 32 bit integer PCM
|
||||
Float = 5, //!< Floating point PCM
|
||||
ADPCM = 6, //!< Adaptive differential PCM
|
||||
};
|
||||
|
||||
enum class AudioOutState : u8 {
|
||||
Started = 0, //!< Stream is started and is playing
|
||||
Stopped = 1, //!< Stream is stopped, there are no samples left to play
|
||||
Paused = 2, //!< Stream is paused, some samples may not have been played yet
|
||||
};
|
||||
|
||||
struct BufferIdentifier {
|
||||
u64 tag;
|
||||
u64 finalSample; //!< The final sample this buffer will be played in, after that the buffer can be safely released
|
||||
bool released; //!< If the buffer has been released (fully played back)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Saturates the specified value according to the numeric limits of Out
|
||||
* @tparam Out The return value type and the numeric limit clamp
|
||||
* @tparam Intermediate The intermediate type that is converted to from In before clamping
|
||||
*/
|
||||
template<typename Out, typename Intermediate, typename In>
|
||||
inline Out Saturate(In value) {
|
||||
return static_cast<Out>(std::clamp(static_cast<Intermediate>(value), static_cast<Intermediate>(std::numeric_limits<Out>::min()), static_cast<Intermediate>(std::numeric_limits<Out>::max())));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
// 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
|
||||
*/
|
||||
inline 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;
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include "common.h"
|
||||
#include "resampler.h"
|
||||
|
||||
namespace skyline::audio {
|
||||
/**
|
||||
* @brief The coefficients for each index of a single output frame
|
||||
*/
|
||||
struct LutEntry {
|
||||
i32 a;
|
||||
i32 b;
|
||||
i32 c;
|
||||
i32 d;
|
||||
};
|
||||
|
||||
// @fmt:off
|
||||
constexpr std::array<LutEntry, 128> CurveLut0{{
|
||||
{6600, 19426, 6722, 3}, {6479, 19424, 6845, 9}, {6359, 19419, 6968, 15}, {6239, 19412, 7093, 22},
|
||||
{6121, 19403, 7219, 28}, {6004, 19391, 7345, 34}, {5888, 19377, 7472, 41}, {5773, 19361, 7600, 48},
|
||||
{5659, 19342, 7728, 55}, {5546, 19321, 7857, 62}, {5434, 19298, 7987, 69}, {5323, 19273, 8118, 77},
|
||||
{5213, 19245, 8249, 84}, {5104, 19215, 8381, 92}, {4997, 19183, 8513, 101}, {4890, 19148, 8646, 109},
|
||||
{4785, 19112, 8780, 118}, {4681, 19073, 8914, 127}, {4579, 19031, 9048, 137}, {4477, 18988, 9183, 147},
|
||||
{4377, 18942, 9318, 157}, {4277, 18895, 9454, 168}, {4179, 18845, 9590, 179}, {4083, 18793, 9726, 190},
|
||||
{3987, 18738, 9863, 202}, {3893, 18682, 10000, 215}, {3800, 18624, 10137, 228}, {3709, 18563, 10274, 241},
|
||||
{3618, 18500, 10411, 255}, {3529, 18436, 10549, 270}, {3441, 18369, 10687, 285}, {3355, 18300, 10824, 300},
|
||||
{3269, 18230, 10962, 317}, {3186, 18157, 11100, 334}, {3103, 18082, 11238, 351}, {3022, 18006, 11375, 369},
|
||||
{2942, 17927, 11513, 388}, {2863, 17847, 11650, 408}, {2785, 17765, 11788, 428}, {2709, 17681, 11925, 449},
|
||||
{2635, 17595, 12062, 471}, {2561, 17507, 12198, 494}, {2489, 17418, 12334, 517}, {2418, 17327, 12470, 541},
|
||||
{2348, 17234, 12606, 566}, {2280, 17140, 12741, 592}, {2213, 17044, 12876, 619}, {2147, 16946, 13010, 647},
|
||||
{2083, 16846, 13144, 675}, {2020, 16745, 13277, 704}, {1958, 16643, 13409, 735}, {1897, 16539, 13541, 766},
|
||||
{1838, 16434, 13673, 798}, {1780, 16327, 13803, 832}, {1723, 16218, 13933, 866}, {1667, 16109, 14062, 901},
|
||||
{1613, 15998, 14191, 937}, {1560, 15885, 14318, 975}, {1508, 15772, 14445, 1013}, {1457, 15657, 14571, 1052},
|
||||
{1407, 15540, 14695, 1093}, {1359, 15423, 14819, 1134}, {1312, 15304, 14942, 1177}, {1266, 15185, 15064, 1221},
|
||||
{1221, 15064, 15185, 1266}, {1177, 14942, 15304, 1312}, {1134, 14819, 15423, 1359}, {1093, 14695, 15540, 1407},
|
||||
{1052, 14571, 15657, 1457}, {1013, 14445, 15772, 1508}, {975, 14318, 15885, 1560}, {937, 14191, 15998, 1613},
|
||||
{901, 14062, 16109, 1667}, {866, 13933, 16218, 1723}, {832, 13803, 16327, 1780}, {798, 13673, 16434, 1838},
|
||||
{766, 13541, 16539, 1897}, {735, 13409, 16643, 1958}, {704, 13277, 16745, 2020}, {675, 13144, 16846, 2083},
|
||||
{647, 13010, 16946, 2147}, {619, 12876, 17044, 2213}, {592, 12741, 17140, 2280}, {566, 12606, 17234, 2348},
|
||||
{541, 12470, 17327, 2418}, {517, 12334, 17418, 2489}, {494, 12198, 17507, 2561}, {471, 12062, 17595, 2635},
|
||||
{449, 11925, 17681, 2709}, {428, 11788, 17765, 2785}, {408, 11650, 17847, 2863}, {388, 11513, 17927, 2942},
|
||||
{369, 11375, 18006, 3022}, {351, 11238, 18082, 3103}, {334, 11100, 18157, 3186}, {317, 10962, 18230, 3269},
|
||||
{300, 10824, 18300, 3355}, {285, 10687, 18369, 3441}, {270, 10549, 18436, 3529}, {255, 10411, 18500, 3618},
|
||||
{241, 10274, 18563, 3709}, {228, 10137, 18624, 3800}, {215, 10000, 18682, 3893}, {202, 9863, 18738, 3987},
|
||||
{190, 9726, 18793, 4083}, {179, 9590, 18845, 4179}, {168, 9454, 18895, 4277}, {157, 9318, 18942, 4377},
|
||||
{147, 9183, 18988, 4477}, {137, 9048, 19031, 4579}, {127, 8914, 19073, 4681}, {118, 8780, 19112, 4785},
|
||||
{109, 8646, 19148, 4890}, {101, 8513, 19183, 4997}, {92, 8381, 19215, 5104}, {84, 8249, 19245, 5213},
|
||||
{77, 8118, 19273, 5323}, {69, 7987, 19298, 5434}, {62, 7857, 19321, 5546}, {55, 7728, 19342, 5659},
|
||||
{48, 7600, 19361, 5773}, {41, 7472, 19377, 5888}, {34, 7345, 19391, 6004}, {28, 7219, 19403, 6121},
|
||||
{22, 7093, 19412, 6239}, {15, 6968, 19419, 6359}, {9, 6845, 19424, 6479}, {3, 6722, 19426, 6600}}};
|
||||
|
||||
constexpr std::array<LutEntry, 128> CurveLut1{{
|
||||
{-68, 32639, 69, -5}, {-200, 32630, 212, -15}, {-328, 32613, 359, -26}, {-450, 32586, 512, -36},
|
||||
{-568, 32551, 669, -47}, {-680, 32507, 832, -58}, {-788, 32454, 1000, -69}, {-891, 32393, 1174, -80},
|
||||
{-990, 32323, 1352, -92}, {-1084, 32244, 1536, -103}, {-1173, 32157, 1724, -115}, {-1258, 32061, 1919, -128},
|
||||
{-1338, 31956, 2118, -140}, {-1414, 31844, 2322, -153}, {-1486, 31723, 2532, -167}, {-1554, 31593, 2747, -180},
|
||||
{-1617, 31456, 2967, -194}, {-1676, 31310, 3192, -209}, {-1732, 31157, 3422, -224}, {-1783, 30995, 3657, -240},
|
||||
{-1830, 30826, 3897, -256}, {-1874, 30649, 4143, -272}, {-1914, 30464, 4393, -289}, {-1951, 30272, 4648, -307},
|
||||
{-1984, 30072, 4908, -325}, {-2014, 29866, 5172, -343}, {-2040, 29652, 5442, -362}, {-2063, 29431, 5716, -382},
|
||||
{-2083, 29203, 5994, -403}, {-2100, 28968, 6277, -424}, {-2114, 28727, 6565, -445}, {-2125, 28480, 6857, -468},
|
||||
{-2133, 28226, 7153, -490}, {-2139, 27966, 7453, -514}, {-2142, 27700, 7758, -538}, {-2142, 27428, 8066, -563},
|
||||
{-2141, 27151, 8378, -588}, {-2136, 26867, 8694, -614}, {-2130, 26579, 9013, -641}, {-2121, 26285, 9336, -668},
|
||||
{-2111, 25987, 9663, -696}, {-2098, 25683, 9993, -724}, {-2084, 25375, 10326, -753}, {-2067, 25063, 10662, -783},
|
||||
{-2049, 24746, 11000, -813}, {-2030, 24425, 11342, -844}, {-2009, 24100, 11686, -875}, {-1986, 23771, 12033, -907},
|
||||
{-1962, 23438, 12382, -939}, {-1937, 23103, 12733, -972}, {-1911, 22764, 13086, -1005}, {-1883, 22422, 13441, -1039},
|
||||
{-1855, 22077, 13798, -1072}, {-1825, 21729, 14156, -1107}, {-1795, 21380, 14516, -1141}, {-1764, 21027, 14877, -1176},
|
||||
{-1732, 20673, 15239, -1211}, {-1700, 20317, 15602, -1246}, {-1667, 19959, 15965, -1282}, {-1633, 19600, 16329, -1317},
|
||||
{-1599, 19239, 16694, -1353}, {-1564, 18878, 17058, -1388}, {-1530, 18515, 17423, -1424}, {-1495, 18151, 17787, -1459},
|
||||
{-1459, 17787, 18151, -1495}, {-1424, 17423, 18515, -1530}, {-1388, 17058, 18878, -1564}, {-1353, 16694, 19239, -1599},
|
||||
{-1317, 16329, 19600, -1633}, {-1282, 15965, 19959, -1667}, {-1246, 15602, 20317, -1700}, {-1211, 15239, 20673, -1732},
|
||||
{-1176, 14877, 21027, -1764}, {-1141, 14516, 21380, -1795}, {-1107, 14156, 21729, -1825}, {-1072, 13798, 22077, -1855},
|
||||
{-1039, 13441, 22422, -1883}, {-1005, 13086, 22764, -1911}, {-972, 12733, 23103, -1937}, {-939, 12382, 23438, -1962},
|
||||
{-907, 12033, 23771, -1986}, {-875, 11686, 24100, -2009}, {-844, 11342, 24425, -2030}, {-813, 11000, 24746, -2049},
|
||||
{-783, 10662, 25063, -2067}, {-753, 10326, 25375, -2084}, {-724, 9993, 25683, -2098}, {-696, 9663, 25987, -2111},
|
||||
{-668, 9336, 26285, -2121}, {-641, 9013, 26579, -2130}, {-614, 8694, 26867, -2136}, {-588, 8378, 27151, -2141},
|
||||
{-563, 8066, 27428, -2142}, {-538, 7758, 27700, -2142}, {-514, 7453, 27966, -2139}, {-490, 7153, 28226, -2133},
|
||||
{-468, 6857, 28480, -2125}, {-445, 6565, 28727, -2114}, {-424, 6277, 28968, -2100}, {-403, 5994, 29203, -2083},
|
||||
{-382, 5716, 29431, -2063}, {-362, 5442, 29652, -2040}, {-343, 5172, 29866, -2014}, {-325, 4908, 30072, -1984},
|
||||
{-307, 4648, 30272, -1951}, {-289, 4393, 30464, -1914}, {-272, 4143, 30649, -1874}, {-256, 3897, 30826, -1830},
|
||||
{-240, 3657, 30995, -1783}, {-224, 3422, 31157, -1732}, {-209, 3192, 31310, -1676}, {-194, 2967, 31456, -1617},
|
||||
{-180, 2747, 31593, -1554}, {-167, 2532, 31723, -1486}, {-153, 2322, 31844, -1414}, {-140, 2118, 31956, -1338},
|
||||
{-128, 1919, 32061, -1258}, {-115, 1724, 32157, -1173}, {-103, 1536, 32244, -1084}, {-92, 1352, 32323, -990},
|
||||
{-80, 1174, 32393, -891}, {-69, 1000, 32454, -788}, {-58, 832, 32507, -680}, {-47, 669, 32551, -568},
|
||||
{-36, 512, 32586, -450}, {-26, 359, 32613, -328}, {-15, 212, 32630, -200}, {-5, 69, 32639, -68}}};
|
||||
|
||||
constexpr std::array<LutEntry, 128> CurveLut2{{
|
||||
{3195, 26287, 3329, -32}, {3064, 26281, 3467, -34}, {2936, 26270, 3608, -38}, {2811, 26253, 3751, -42},
|
||||
{2688, 26230, 3897, -46}, {2568, 26202, 4046, -50}, {2451, 26169, 4199, -54}, {2338, 26130, 4354, -58},
|
||||
{2227, 26085, 4512, -63}, {2120, 26035, 4673, -67}, {2015, 25980, 4837, -72}, {1912, 25919, 5004, -76},
|
||||
{1813, 25852, 5174, -81}, {1716, 25780, 5347, -87}, {1622, 25704, 5522, -92}, {1531, 25621, 5701, -98},
|
||||
{1442, 25533, 5882, -103}, {1357, 25440, 6066, -109}, {1274, 25342, 6253, -115}, {1193, 25239, 6442, -121},
|
||||
{1115, 25131, 6635, -127}, {1040, 25018, 6830, -133}, {967, 24899, 7027, -140}, {897, 24776, 7227, -146},
|
||||
{829, 24648, 7430, -153}, {764, 24516, 7635, -159}, {701, 24379, 7842, -166}, {641, 24237, 8052, -174},
|
||||
{583, 24091, 8264, -181}, {526, 23940, 8478, -187}, {472, 23785, 8695, -194}, {420, 23626, 8914, -202},
|
||||
{371, 23462, 9135, -209}, {324, 23295, 9358, -215}, {279, 23123, 9583, -222}, {236, 22948, 9809, -230},
|
||||
{194, 22769, 10038, -237}, {154, 22586, 10269, -243}, {117, 22399, 10501, -250}, {81, 22208, 10735, -258},
|
||||
{47, 22015, 10970, -265}, {15, 21818, 11206, -271}, {-16, 21618, 11444, -277}, {-44, 21415, 11684, -283},
|
||||
{-71, 21208, 11924, -290}, {-97, 20999, 12166, -296}, {-121, 20786, 12409, -302}, {-143, 20571, 12653, -306},
|
||||
{-163, 20354, 12898, -311}, {-183, 20134, 13143, -316}, {-201, 19911, 13389, -321}, {-218, 19686, 13635, -325},
|
||||
{-234, 19459, 13882, -328}, {-248, 19230, 14130, -332}, {-261, 18998, 14377, -335}, {-273, 18765, 14625, -337},
|
||||
{-284, 18531, 14873, -339}, {-294, 18295, 15121, -341}, {-302, 18057, 15369, -341}, {-310, 17817, 15617, -341},
|
||||
{-317, 17577, 15864, -340}, {-323, 17335, 16111, -340}, {-328, 17092, 16357, -338}, {-332, 16848, 16603, -336},
|
||||
{-336, 16603, 16848, -332}, {-338, 16357, 17092, -328}, {-340, 16111, 17335, -323}, {-340, 15864, 17577, -317},
|
||||
{-341, 15617, 17817, -310}, {-341, 15369, 18057, -302}, {-341, 15121, 18295, -294}, {-339, 14873, 18531, -284},
|
||||
{-337, 14625, 18765, -273}, {-335, 14377, 18998, -261}, {-332, 14130, 19230, -248}, {-328, 13882, 19459, -234},
|
||||
{-325, 13635, 19686, -218}, {-321, 13389, 19911, -201}, {-316, 13143, 20134, -183}, {-311, 12898, 20354, -163},
|
||||
{-306, 12653, 20571, -143}, {-302, 12409, 20786, -121}, {-296, 12166, 20999, -97}, {-290, 11924, 21208, -71},
|
||||
{-283, 11684, 21415, -44}, {-277, 11444, 21618, -16}, {-271, 11206, 21818, 15}, {-265, 10970, 22015, 47},
|
||||
{-258, 10735, 22208, 81}, {-250, 10501, 22399, 117}, {-243, 10269, 22586, 154}, {-237, 10038, 22769, 194},
|
||||
{-230, 9809, 22948, 236}, {-222, 9583, 23123, 279}, {-215, 9358, 23295, 324}, {-209, 9135, 23462, 371},
|
||||
{-202, 8914, 23626, 420}, {-194, 8695, 23785, 472}, {-187, 8478, 23940, 526}, {-181, 8264, 24091, 583},
|
||||
{-174, 8052, 24237, 641}, {-166, 7842, 24379, 701}, {-159, 7635, 24516, 764}, {-153, 7430, 24648, 829},
|
||||
{-146, 7227, 24776, 897}, {-140, 7027, 24899, 967}, {-133, 6830, 25018, 1040}, {-127, 6635, 25131, 1115},
|
||||
{-121, 6442, 25239, 1193}, {-115, 6253, 25342, 1274}, {-109, 6066, 25440, 1357}, {-103, 5882, 25533, 1442},
|
||||
{-98, 5701, 25621, 1531}, {-92, 5522, 25704, 1622}, {-87, 5347, 25780, 1716}, {-81, 5174, 25852, 1813},
|
||||
{-76, 5004, 25919, 1912}, {-72, 4837, 25980, 2015}, {-67, 4673, 26035, 2120}, {-63, 4512, 26085, 2227},
|
||||
{-58, 4354, 26130, 2338}, {-54, 4199, 26169, 2451}, {-50, 4046, 26202, 2568}, {-46, 3897, 26230, 2688},
|
||||
{-42, 3751, 26253, 2811}, {-38, 3608, 26270, 2936}, {-34, 3467, 26281, 3064}, {-32, 3329, 26287, 3195}}};
|
||||
// @fmt:on
|
||||
|
||||
std::vector<i16> Resampler::ResampleBuffer(span<i16> inputBuffer, double ratio, u8 channelCount) {
|
||||
auto step{static_cast<u32>(ratio * 0x8000)};
|
||||
auto outputSize{static_cast<size_t>(inputBuffer.size() / ratio)};
|
||||
std::vector<i16> outputBuffer(outputSize);
|
||||
|
||||
const std::array<LutEntry, 128> &lut = [step] {
|
||||
if (step > 0xAAAA)
|
||||
return CurveLut0;
|
||||
else if (step <= 0x8000)
|
||||
return CurveLut1;
|
||||
else
|
||||
return CurveLut2;
|
||||
}();
|
||||
|
||||
for (size_t outIndex{}, inIndex{}; outIndex < outputSize; outIndex += channelCount) {
|
||||
u32 lutIndex{fraction >> 8};
|
||||
|
||||
for (u8 channel{}; channel < channelCount; channel++) {
|
||||
if (((inIndex + 3) * channelCount + channel) >= inputBuffer.size())
|
||||
continue;
|
||||
|
||||
i32 data{inputBuffer[(inIndex + 0) * channelCount + channel] * lut[lutIndex].a +
|
||||
inputBuffer[(inIndex + 1) * channelCount + channel] * lut[lutIndex].b +
|
||||
inputBuffer[(inIndex + 2) * channelCount + channel] * lut[lutIndex].c +
|
||||
inputBuffer[(inIndex + 3) * channelCount + channel] * lut[lutIndex].d};
|
||||
|
||||
outputBuffer[outIndex + channel] = Saturate<i16, i32>(data >> 15);
|
||||
}
|
||||
|
||||
u32 newOffset{fraction + step};
|
||||
inIndex += newOffset >> 15;
|
||||
fraction = newOffset & 0x7FFF;
|
||||
}
|
||||
|
||||
return outputBuffer;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::audio {
|
||||
/**
|
||||
* @brief The Resampler class handles resampling audio PCM data
|
||||
*/
|
||||
class Resampler {
|
||||
private:
|
||||
u32 fraction{}; //!< The fractional value used for storing the resamplers last frame
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Resamples the given sample buffer by the given ratio
|
||||
* @param inputBuffer A buffer containing PCM sample data
|
||||
* @param ratio The conversion ratio needed
|
||||
* @param channelCount The amount of channels the buffer contains
|
||||
*/
|
||||
std::vector<i16> ResampleBuffer(span <i16> inputBuffer, double ratio, u8 channelCount);
|
||||
};
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include "downmixer.h"
|
||||
#include "track.h"
|
||||
|
||||
namespace skyline::audio {
|
||||
AudioTrack::AudioTrack(u8 channelCount, u32 sampleRate, std::function<void()> releaseCallback)
|
||||
: channelCount(channelCount),
|
||||
sampleRate(sampleRate),
|
||||
releaseCallback(std::move(releaseCallback)) {
|
||||
if (sampleRate != constant::SampleRate)
|
||||
throw exception("Unsupported audio sample rate: {}", sampleRate);
|
||||
|
||||
if (channelCount != constant::StereoChannelCount && channelCount != constant::SurroundChannelCount)
|
||||
throw exception("Unsupported quantity of audio channels: {}", channelCount);
|
||||
}
|
||||
|
||||
void AudioTrack::Stop() {
|
||||
auto allSamplesReleased{[&]() {
|
||||
std::scoped_lock lock{bufferLock};
|
||||
return identifiers.empty() || identifiers.back().released;
|
||||
}};
|
||||
|
||||
while (!allSamplesReleased());
|
||||
playbackState = AudioOutState::Stopped;
|
||||
}
|
||||
|
||||
bool AudioTrack::ContainsBuffer(u64 tag) {
|
||||
std::scoped_lock lock(bufferLock);
|
||||
|
||||
// Iterate from front of queue as we don't want released samples
|
||||
for (auto identifier{identifiers.crbegin()}; identifier != identifiers.crend(); identifier++) {
|
||||
if (identifier->released)
|
||||
return false;
|
||||
|
||||
if (identifier->tag == tag)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u64> AudioTrack::GetReleasedBuffers(u32 max) {
|
||||
std::vector<u64> bufferIds;
|
||||
std::scoped_lock lock(bufferLock);
|
||||
|
||||
for (u32 index{}; index < max; index++) {
|
||||
if (identifiers.empty() || !identifiers.back().released)
|
||||
break;
|
||||
bufferIds.push_back(identifiers.back().tag);
|
||||
identifiers.pop_back();
|
||||
}
|
||||
|
||||
return bufferIds;
|
||||
}
|
||||
|
||||
void AudioTrack::AppendBuffer(u64 tag, span<i16> buffer) {
|
||||
std::scoped_lock lock(bufferLock);
|
||||
|
||||
size_t size{(channelCount == constant::SurroundChannelCount) ? (buffer.size() / sizeof(Surround51Sample)) * sizeof(StereoSample) : buffer.size()};
|
||||
BufferIdentifier identifier{
|
||||
.released = false,
|
||||
.tag = tag,
|
||||
.finalSample = identifiers.empty() ? size : (size + identifiers.front().finalSample)
|
||||
};
|
||||
|
||||
identifiers.push_front(identifier);
|
||||
|
||||
if (channelCount == constant::SurroundChannelCount) {
|
||||
auto stereoBuffer{DownMix(buffer.cast<Surround51Sample>())};
|
||||
samples.Append(span(stereoBuffer).cast<i16>());
|
||||
} else {
|
||||
samples.Append(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioTrack::CheckReleasedBuffers() {
|
||||
bool anyReleased{};
|
||||
|
||||
for (auto &identifier : identifiers) {
|
||||
if (identifier.finalSample <= sampleCounter && !identifier.released) {
|
||||
anyReleased = true;
|
||||
identifier.released = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyReleased)
|
||||
releaseCallback();
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <kernel/types/KEvent.h>
|
||||
#include <common/circular_buffer.h>
|
||||
#include "common.h"
|
||||
|
||||
namespace skyline::audio {
|
||||
/**
|
||||
* @brief The AudioTrack class manages the buffers for an audio stream
|
||||
*/
|
||||
class AudioTrack {
|
||||
private:
|
||||
std::function<void()> releaseCallback; //!< Callback called when a buffer has been played
|
||||
std::deque<BufferIdentifier> identifiers; //!< Queue of all appended buffer identifiers
|
||||
|
||||
u8 channelCount;
|
||||
u32 sampleRate;
|
||||
|
||||
public:
|
||||
CircularBuffer<i16, constant::SampleRate * constant::StereoChannelCount * 10> samples; //!< A circular buffer with all appended audio samples
|
||||
std::mutex bufferLock; //!< Synchronizes appending to audio buffers
|
||||
|
||||
AudioOutState playbackState{AudioOutState::Stopped}; //!< The current state of playback
|
||||
u64 sampleCounter{}; //!< A counter used for tracking when buffers have been played and can be released
|
||||
|
||||
/**
|
||||
* @param channelCount The amount channels that will be present in the track
|
||||
* @param sampleRate The sample rate to use for the track
|
||||
* @param releaseCallback A callback to call when a buffer has been played
|
||||
*/
|
||||
AudioTrack(u8 channelCount, u32 sampleRate, std::function<void()> releaseCallback);
|
||||
|
||||
/**
|
||||
* @brief Starts audio playback using data from appended buffers
|
||||
*/
|
||||
void Start() {
|
||||
playbackState = AudioOutState::Started;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stops audio playback, this waits for audio playback to finish before returning
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* @brief Checks if a buffer has been released
|
||||
* @param tag The tag of the buffer to check
|
||||
* @return True if the given buffer hasn't been released
|
||||
*/
|
||||
bool ContainsBuffer(u64 tag);
|
||||
|
||||
/**
|
||||
* @brief Gets the IDs of all newly released buffers
|
||||
* @param max The maximum amount of buffers to return
|
||||
* @return A vector containing the identifiers of the buffers
|
||||
*/
|
||||
std::vector<u64> GetReleasedBuffers(u32 max);
|
||||
|
||||
/**
|
||||
* @brief Appends audio samples to the output buffer
|
||||
* @param tag The tag of the buffer
|
||||
* @param buffer A span containing the source sample buffer
|
||||
*/
|
||||
void AppendBuffer(u64 tag, span<i16> buffer = {});
|
||||
|
||||
/**
|
||||
* @brief Checks if any buffers have been released and calls the appropriate callback for them
|
||||
* @note bufferLock MUST be locked when calling this
|
||||
*/
|
||||
void CheckReleasedBuffers();
|
||||
};
|
||||
}
|
@ -22,7 +22,9 @@ namespace skyline {
|
||||
*/
|
||||
constexpr Result() = default;
|
||||
|
||||
constexpr explicit Result(u16 module, u16 id) : module(module), id(id) {}
|
||||
constexpr Result(u16 module, u16 id) : module{module}, id{id} {}
|
||||
|
||||
constexpr explicit Result(u32 raw) : raw{raw} {}
|
||||
|
||||
constexpr operator u32() const {
|
||||
return raw;
|
||||
|
@ -1,69 +1,46 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#include <audio_core/audio_core.h>
|
||||
#include <audio.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include <audio/common.h>
|
||||
#include "IAudioDevice.h"
|
||||
|
||||
namespace skyline::service::audio {
|
||||
IAudioDevice::IAudioDevice(const DeviceState &state, ServiceManager &manager)
|
||||
: BaseService(state, manager),
|
||||
systemEvent(std::make_shared<type::KEvent>(state, true)),
|
||||
outputEvent(std::make_shared<type::KEvent>(state, true)),
|
||||
inputEvent(std::make_shared<type::KEvent>(state, true)) {}
|
||||
IAudioDevice::IAudioDevice(const DeviceState &state, ServiceManager &manager, u64 appletResourceUserId, u32 revision)
|
||||
: BaseService{state, manager},
|
||||
event{std::make_shared<type::KEvent>(state, true)},
|
||||
impl{state.audio->audioSystem, appletResourceUserId, revision} {}
|
||||
|
||||
Result IAudioDevice::ListAudioDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
span buffer{request.outputBuf.at(0)};
|
||||
std::array<std::string_view, 3> devices{"AudioTvOutput\0", "AudioStereoJackOutput\0", "AudioBuiltInSpeakerOutput\0"};
|
||||
for (std::string_view deviceName : devices) {
|
||||
if (deviceName.size() > buffer.size())
|
||||
throw exception("The buffer supplied to ListAudioDeviceName is too small");
|
||||
buffer.copy_from(deviceName);
|
||||
buffer = buffer.subspan(deviceName.size());
|
||||
}
|
||||
response.Push<u32>(devices.size());
|
||||
|
||||
std::vector<AudioCore::AudioRenderer::AudioDevice::AudioDeviceName> outputNames{};
|
||||
u32 writtenCount{impl.ListAudioDeviceName(outputNames, buffer.size() / sizeof(AudioCore::AudioRenderer::AudioDevice::AudioDeviceName))};
|
||||
response.Push<u32>(writtenCount);
|
||||
buffer.copy_from(outputNames);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::SetAudioDeviceOutputVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
float volume{request.Pop<float>()};
|
||||
auto name{request.inputBuf.at(0).as_string(true)};
|
||||
|
||||
if (name == "AudioTvOutput")
|
||||
impl.SetDeviceVolumes(volume);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::GetAudioDeviceOutputVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto name{request.inputBuf.at(0).as_string(true)};
|
||||
|
||||
response.Push<float>(name == "AudioTvOutput" ? impl.GetDeviceVolume(name) : 1.0f);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::GetActiveAudioDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
std::string_view deviceName{"AudioStereoJackOutput\0"};
|
||||
if (deviceName.size() > request.outputBuf.at(0).size())
|
||||
throw exception("The buffer supplied to GetActiveAudioDeviceName is too small");
|
||||
request.outputBuf.at(0).copy_from(deviceName);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::QueryAudioDeviceSystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle{state.process->InsertItem(systemEvent)};
|
||||
Logger::Debug("Audio Device System Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::GetActiveChannelCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(constant::StereoChannelCount);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::QueryAudioDeviceInputEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle{state.process->InsertItem(inputEvent)};
|
||||
Logger::Debug("Audio Device Input Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::QueryAudioDeviceOutputEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle{state.process->InsertItem(outputEvent)};
|
||||
Logger::Debug("Audio Device Output Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::GetActiveAudioOutputDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
std::string_view deviceName{"AudioTvOutput\0"};
|
||||
if (deviceName.size() > request.outputBuf.at(0).size())
|
||||
throw exception("The buffer supplied to GetActiveAudioDeviceName is too small");
|
||||
@ -71,16 +48,40 @@ namespace skyline::service::audio {
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::QueryAudioDeviceSystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle{state.process->InsertItem(event)};
|
||||
event->Signal();
|
||||
Logger::Debug("Audio Device System Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::GetActiveChannelCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(state.audio->audioSystem.AudioCore().GetOutputSink().GetDeviceChannels());
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::QueryAudioDeviceInputEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle{state.process->InsertItem(event)};
|
||||
Logger::Debug("Audio Device Input Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::QueryAudioDeviceOutputEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle{state.process->InsertItem(event)};
|
||||
Logger::Debug("Audio Device Output Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioDevice::ListAudioOutputDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
span buffer{request.outputBuf.at(0)};
|
||||
std::array<std::string_view, 3> devices{"AudioExternalOutput\0", "AudioBuiltInSpeakerOutput\0", "AudioTvOutput\0"};
|
||||
for (std::string_view deviceName : devices) {
|
||||
if (deviceName.size() > buffer.size())
|
||||
throw exception("The buffer supplied to ListAudioDeviceName is too small");
|
||||
buffer.copy_from(deviceName);
|
||||
buffer = buffer.subspan(deviceName.size());
|
||||
}
|
||||
response.Push<u32>(devices.size());
|
||||
|
||||
std::vector<AudioCore::AudioRenderer::AudioDevice::AudioDeviceName> outputNames{};
|
||||
u32 writtenCount{impl.ListAudioOutputDeviceName(outputNames, buffer.size() / sizeof(AudioCore::AudioRenderer::AudioDevice::AudioDeviceName))};
|
||||
response.Push<u32>(writtenCount);
|
||||
buffer.copy_from(outputNames);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <audio_core/renderer/audio_device.h>
|
||||
#include <services/serviceman.h>
|
||||
|
||||
namespace skyline::service::audio {
|
||||
@ -12,34 +13,29 @@ namespace skyline::service::audio {
|
||||
*/
|
||||
class IAudioDevice : public BaseService {
|
||||
private:
|
||||
std::shared_ptr<type::KEvent> systemEvent; //!< Signalled on all audio device changes
|
||||
std::shared_ptr<type::KEvent> inputEvent; //!< Signalled on audio input device changes
|
||||
std::shared_ptr<type::KEvent> outputEvent; //!< Signalled on audio output device changes
|
||||
|
||||
std::shared_ptr<type::KEvent> event; //!< Signalled on all audio device changes
|
||||
AudioCore::AudioRenderer::AudioDevice impl;
|
||||
|
||||
public:
|
||||
IAudioDevice(const DeviceState &state, ServiceManager &manager);
|
||||
IAudioDevice(const DeviceState &state, ServiceManager &manager, u64 appletResourceUserId, u32 revision);
|
||||
|
||||
/**
|
||||
* @brief Returns a list of the available audio devices
|
||||
* @url https://switchbrew.org/wiki/Audio_services#ListAudioDeviceName
|
||||
*/
|
||||
Result ListAudioDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Sets the volume of an audio output
|
||||
* @url https://switchbrew.org/wiki/Audio_services#SetAudioDeviceOutputVolume
|
||||
*/
|
||||
Result SetAudioDeviceOutputVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the active audio output device
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioDeviceOutputVolume
|
||||
*/
|
||||
Result GetAudioDeviceOutputVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result GetActiveAudioDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the audio device system event
|
||||
*/
|
||||
Result QueryAudioDeviceSystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
@ -51,25 +47,22 @@ namespace skyline::service::audio {
|
||||
|
||||
Result QueryAudioDeviceOutputEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetActiveAudioOutputDeviceName
|
||||
*/
|
||||
Result GetActiveAudioOutputDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result ListAudioOutputDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
SERVICE_DECL(
|
||||
SFUNC(0x0, IAudioDevice, ListAudioDeviceName),
|
||||
SFUNC(0x1, IAudioDevice, SetAudioDeviceOutputVolume),
|
||||
SFUNC(0x2, IAudioDevice, GetActiveAudioDeviceName),
|
||||
SFUNC(0x3, IAudioDevice, GetActiveAudioDeviceName),
|
||||
SFUNC(0x4, IAudioDevice, QueryAudioDeviceSystemEvent),
|
||||
SFUNC(0x5, IAudioDevice, GetActiveChannelCount),
|
||||
SFUNC(0x6, IAudioDevice, ListAudioDeviceName),
|
||||
SFUNC(0x7, IAudioDevice, SetAudioDeviceOutputVolume),
|
||||
SFUNC(0x8, IAudioDevice, GetAudioDeviceOutputVolume),
|
||||
SFUNC(0xA, IAudioDevice, GetActiveAudioDeviceName),
|
||||
SFUNC(0xB, IAudioDevice, QueryAudioDeviceInputEvent),
|
||||
SFUNC(0xC, IAudioDevice, QueryAudioDeviceOutputEvent),
|
||||
SFUNC(0xD, IAudioDevice, GetActiveAudioOutputDeviceName),
|
||||
SFUNC(0xD, IAudioDevice, GetActiveAudioDeviceName),
|
||||
SFUNC(0xE, IAudioDevice, ListAudioOutputDeviceName)
|
||||
)
|
||||
};
|
||||
|
@ -1,60 +1,47 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#include <audio.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include "IAudioOut.h"
|
||||
|
||||
namespace skyline::service::audio {
|
||||
IAudioOut::IAudioOut(const DeviceState &state, ServiceManager &manager, u8 channelCount, u32 sampleRate)
|
||||
: sampleRate(sampleRate),
|
||||
channelCount(channelCount),
|
||||
releaseEvent(std::make_shared<type::KEvent>(state, false)),
|
||||
BaseService(state, manager) {
|
||||
track = state.audio->OpenTrack(channelCount, constant::SampleRate, [this]() { releaseEvent->Signal(); });
|
||||
IAudioOut::IAudioOut(const DeviceState &state, ServiceManager &manager, size_t sessionId,
|
||||
std::string_view deviceName, AudioCore::AudioOut::AudioOutParameter parameters,
|
||||
KHandle handle, u32 appletResourceUserId)
|
||||
: BaseService{state, manager},
|
||||
releaseEvent{std::make_shared<type::KEvent>(state, false)},
|
||||
releaseEventWrapper{[releaseEvent = this->releaseEvent]() { releaseEvent->Signal(); },
|
||||
[releaseEvent = this->releaseEvent]() { releaseEvent->ResetSignal(); }},
|
||||
impl{std::make_shared<AudioCore::AudioOut::Out>(state.audio->audioSystem, *state.audio->audioOutManager, &releaseEventWrapper, sessionId)} {
|
||||
|
||||
if (impl->GetSystem().Initialize(std::string{deviceName}, parameters, handle, appletResourceUserId).IsError())
|
||||
Logger::Warn("Failed to initialise Audio Out");
|
||||
}
|
||||
|
||||
IAudioOut::~IAudioOut() {
|
||||
state.audio->CloseTrack(track);
|
||||
impl->Free();
|
||||
}
|
||||
|
||||
Result IAudioOut::GetAudioOutState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(static_cast<u32>(track->playbackState));
|
||||
response.Push(static_cast<u32>(impl->GetState()));
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioOut::StartAudioOut(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
Logger::Debug("Start playback");
|
||||
track->Start();
|
||||
return {};
|
||||
return Result{impl->StartSystem()};
|
||||
}
|
||||
|
||||
Result IAudioOut::StopAudioOut(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
Logger::Debug("Stop playback");
|
||||
track->Stop();
|
||||
return {};
|
||||
return Result{impl->StopSystem()};
|
||||
}
|
||||
|
||||
Result IAudioOut::AppendAudioOutBuffer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
struct Data {
|
||||
i16 *nextBuffer;
|
||||
i16 *sampleBuffer;
|
||||
u64 sampleCapacity;
|
||||
u64 sampleSize;
|
||||
u64 sampleOffset;
|
||||
} &data{request.inputBuf.at(0).as<Data>()};
|
||||
const auto &buffer{request.inputBuf.at(0).as<AudioCore::AudioOut::AudioOutBuffer>()};
|
||||
auto tag{request.Pop<u64>()};
|
||||
|
||||
Logger::Debug("Appending buffer at 0x{:X}, Size: 0x{:X}", data.sampleBuffer, data.sampleSize);
|
||||
|
||||
span samples(data.sampleBuffer, data.sampleSize / sizeof(i16));
|
||||
if (sampleRate != constant::SampleRate) {
|
||||
auto resampledBuffer{resampler.ResampleBuffer(samples, static_cast<double>(sampleRate) / constant::SampleRate, channelCount)};
|
||||
track->AppendBuffer(tag, resampledBuffer);
|
||||
} else {
|
||||
track->AppendBuffer(tag, samples);
|
||||
}
|
||||
|
||||
return {};
|
||||
return Result{impl->AppendBuffer(buffer, tag)};
|
||||
}
|
||||
|
||||
Result IAudioOut::RegisterBufferEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
@ -65,22 +52,45 @@ namespace skyline::service::audio {
|
||||
}
|
||||
|
||||
Result IAudioOut::GetReleasedAudioOutBuffer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto maxCount{static_cast<u32>(request.outputBuf.at(0).size() >> 3)};
|
||||
auto releasedBuffers{track->GetReleasedBuffers(maxCount)};
|
||||
auto count{static_cast<u32>(releasedBuffers.size())};
|
||||
auto maxCount{request.outputBuf.at(0).size() >> 3};
|
||||
|
||||
std::vector<u64> releasedBuffers(maxCount);
|
||||
auto count{impl->GetReleasedBuffers(releasedBuffers)};
|
||||
|
||||
// Fill rest of output buffer with zeros
|
||||
releasedBuffers.resize(maxCount, 0);
|
||||
request.outputBuf.at(0).copy_from(releasedBuffers);
|
||||
|
||||
response.Push<u32>(count);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioOut::ContainsAudioOutBuffer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto tag{request.Pop<u64>()};
|
||||
response.Push(static_cast<u32>(impl->ContainsAudioBuffer(tag)));
|
||||
return {};
|
||||
}
|
||||
|
||||
response.Push(static_cast<u32>(track->ContainsBuffer(tag)));
|
||||
Result IAudioOut::GetAudioOutBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(impl->GetBufferCount());
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioOut::GetAudioOutPlayedSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(impl->GetPlayedSampleCount());
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioOut::FlushAudioOutBuffers(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(static_cast<u32>(impl->FlushAudioOutBuffers()));
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioOut::SetAudioOutVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto volume{request.Pop<float>()};
|
||||
impl->SetVolume(volume);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioOut::GetAudioOutVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(impl->GetVolume());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <audio_core/out/audio_out_system.h>
|
||||
#include <audio_core/out/audio_out.h>
|
||||
#include <audio_core/core/hle/kernel/k_event.h>
|
||||
#include <services/serviceman.h>
|
||||
#include <audio/resampler.h>
|
||||
#include <audio.h>
|
||||
|
||||
namespace skyline::service::audio {
|
||||
/**
|
||||
@ -14,68 +16,82 @@ namespace skyline::service::audio {
|
||||
*/
|
||||
class IAudioOut : public BaseService {
|
||||
private:
|
||||
skyline::audio::Resampler resampler; //!< The audio resampler object used to resample audio
|
||||
std::shared_ptr<skyline::audio::AudioTrack> track; //!< The audio track associated with the audio out
|
||||
std::shared_ptr<type::KEvent> releaseEvent; //!< The KEvent that is signalled when a buffer has been released
|
||||
std::shared_ptr<type::KEvent> releaseEvent;
|
||||
KernelShim::KEvent releaseEventWrapper;
|
||||
|
||||
u32 sampleRate;
|
||||
u8 channelCount;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param channelCount The channel count of the audio data the audio out will be fed
|
||||
* @param sampleRate The sample rate of the audio data the audio out will be fed
|
||||
*/
|
||||
IAudioOut(const DeviceState &state, ServiceManager &manager, u8 channelCount, u32 sampleRate);
|
||||
std::shared_ptr<AudioCore::AudioOut::Out> impl;
|
||||
|
||||
IAudioOut(const DeviceState &state, ServiceManager &manager, size_t sessionId,
|
||||
std::string_view deviceName, AudioCore::AudioOut::AudioOutParameter parameters,
|
||||
KHandle handle, u32 appletResourceUserId);
|
||||
|
||||
/**
|
||||
* @brief Closes the audio track
|
||||
*/
|
||||
~IAudioOut();
|
||||
|
||||
/**
|
||||
* @brief Returns the playback state of the audio output
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioOutState
|
||||
*/
|
||||
Result GetAudioOutState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Starts playback using data from appended samples
|
||||
* @url https://switchbrew.org/wiki/Audio_services#StartAudioOut
|
||||
*/
|
||||
Result StartAudioOut(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Stops playback of audio, waits for all samples to be released
|
||||
* @url https://switchbrew.org/wiki/Audio_services#StartAudioOut
|
||||
*/
|
||||
Result StopAudioOut(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Appends sample data to the output buffer
|
||||
* @url https://switchbrew.org/wiki/Audio_services#AppendAudioOutBuffer
|
||||
*/
|
||||
Result AppendAudioOutBuffer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the sample release KEvent
|
||||
* @url https://switchbrew.org/wiki/Audio_services#AppendAudioOutBuffer
|
||||
*/
|
||||
Result RegisterBufferEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the IDs of all pending released buffers
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetReleasedAudioOutBuffer
|
||||
*/
|
||||
Result GetReleasedAudioOutBuffer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Checks if the given buffer ID is in the playback queue
|
||||
* @url https://switchbrew.org/wiki/Audio_services#ContainsAudioOutBuffer
|
||||
*/
|
||||
Result ContainsAudioOutBuffer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
SERVICE_DECL(
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioOutBufferCount
|
||||
*/
|
||||
Result GetAudioOutBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioOutPlayedSampleCount
|
||||
*/
|
||||
Result GetAudioOutPlayedSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#FlushAudioOutBuffers
|
||||
*/
|
||||
Result FlushAudioOutBuffers(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#SetAudioOutVolume
|
||||
*/
|
||||
Result SetAudioOutVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioOutVolume
|
||||
*/
|
||||
Result GetAudioOutVolume(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
SERVICE_DECL(
|
||||
SFUNC(0x0, IAudioOut, GetAudioOutState),
|
||||
SFUNC(0x1, IAudioOut, StartAudioOut),
|
||||
SFUNC(0x2, IAudioOut, StopAudioOut),
|
||||
@ -84,7 +100,12 @@ namespace skyline::service::audio {
|
||||
SFUNC(0x5, IAudioOut, GetReleasedAudioOutBuffer),
|
||||
SFUNC(0x6, IAudioOut, ContainsAudioOutBuffer),
|
||||
SFUNC(0x7, IAudioOut, AppendAudioOutBuffer),
|
||||
SFUNC(0x8, IAudioOut, GetReleasedAudioOutBuffer)
|
||||
)
|
||||
SFUNC(0x8, IAudioOut, GetReleasedAudioOutBuffer),
|
||||
SFUNC(0x9, IAudioOut, GetAudioOutBufferCount),
|
||||
SFUNC(0xA, IAudioOut, GetAudioOutPlayedSampleCount),
|
||||
SFUNC(0xB, IAudioOut, FlushAudioOutBuffers),
|
||||
SFUNC(0xC, IAudioOut, SetAudioOutVolume),
|
||||
SFUNC(0xD, IAudioOut, GetAudioOutVolume),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#include <audio_core/audio_out_manager.h>
|
||||
#include <audio.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include "IAudioOutManager.h"
|
||||
#include "IAudioOut.h"
|
||||
@ -16,28 +19,40 @@ namespace skyline::service::audio {
|
||||
}
|
||||
|
||||
Result IAudioOutManager::OpenAudioOut(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
struct AudioInputParams {
|
||||
u32 sampleRate;
|
||||
u16 channelCount;
|
||||
u16 _pad_;
|
||||
};
|
||||
auto &inputParams{request.Pop<AudioInputParams>()};
|
||||
auto &inputParams{request.Pop<AudioCore::AudioOut::AudioOutParameter>()};
|
||||
auto appletResourceUserId{request.Pop<u64>()};
|
||||
std::string_view deviceName{request.inputBuf.at(0).as_string(true)};
|
||||
auto handle{request.copyHandles.at(0)};
|
||||
|
||||
inputParams.sampleRate = inputParams.sampleRate ? inputParams.sampleRate : constant::SampleRate;
|
||||
inputParams.channelCount = inputParams.channelCount <= constant::StereoChannelCount ? constant::StereoChannelCount : constant::SurroundChannelCount;
|
||||
inputParams._pad_ = 0;
|
||||
Logger::Debug("Opening an IAudioOut with sample rate: {}, channel count: {}", inputParams.sampleRate, inputParams.channelCount);
|
||||
manager.RegisterService(std::make_shared<IAudioOut>(state, manager, inputParams.channelCount, inputParams.sampleRate), session, response);
|
||||
response.Push<AudioInputParams>(inputParams);
|
||||
response.Push(static_cast<u32>(skyline::audio::AudioFormat::Int16));
|
||||
response.Push(static_cast<u32>(skyline::audio::AudioOutState::Stopped));
|
||||
auto &audioOutManager{*state.audio->audioOutManager};
|
||||
if (auto result{audioOutManager.LinkToManager()}; result.IsError()) {
|
||||
Logger::Warn("Failed to link Audio Out to manager");
|
||||
return Result{result};
|
||||
}
|
||||
|
||||
size_t sessionId{};
|
||||
if (auto result{audioOutManager.AcquireSessionId(sessionId)}; result.IsError()) {
|
||||
Logger::Warn("Failed to acquire audio session");
|
||||
return Result{result};
|
||||
}
|
||||
|
||||
auto audioOut{std::make_shared<IAudioOut>(state, manager, sessionId, deviceName, inputParams, handle, appletResourceUserId)};
|
||||
manager.RegisterService(audioOut, session, response);
|
||||
|
||||
audioOutManager.sessions[sessionId] = audioOut->impl;
|
||||
audioOutManager.applet_resource_user_ids[sessionId] = appletResourceUserId;
|
||||
|
||||
auto &outSystem{audioOut->impl->GetSystem()};
|
||||
response.Push<AudioCore::AudioOut::AudioOutParameterInternal>({
|
||||
.sample_rate = outSystem.GetSampleRate(),
|
||||
.channel_count = outSystem.GetChannelCount(),
|
||||
.sample_format = static_cast<u32>(outSystem.GetSampleFormat()),
|
||||
.state = static_cast<u32>(outSystem.GetState())
|
||||
});
|
||||
|
||||
std::memset(request.outputBuf.at(0).data(), 0, request.outputBuf.at(0).size());
|
||||
|
||||
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));
|
||||
request.outputBuf.at(0).copy_from(outSystem.GetName());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
|
101
app/src/main/cpp/skyline/services/audio/IAudioRenderer.cpp
Normal file
101
app/src/main/cpp/skyline/services/audio/IAudioRenderer.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#include <audio.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include "IAudioRenderer.h"
|
||||
|
||||
namespace skyline::service::audio {
|
||||
IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager,
|
||||
AudioCore::AudioRenderer::Manager &rendererManager,
|
||||
const AudioCore::AudioRendererParameterInternal ¶ms,
|
||||
u64 transferMemorySize, u32 processHandle, u64 appletResourceUserId, i32 sessionId)
|
||||
: BaseService{state, manager},
|
||||
renderedEvent{std::make_shared<type::KEvent>(state, true)},
|
||||
renderedEventWrapper{[renderedEvent = this->renderedEvent]() { renderedEvent->Signal(); },
|
||||
[renderedEvent = this->renderedEvent]() { renderedEvent->ResetSignal(); }},
|
||||
transferMemoryWrapper{transferMemorySize},
|
||||
impl{state.audio->audioSystem, rendererManager, &renderedEventWrapper} {
|
||||
impl.Initialize(params, &transferMemoryWrapper, transferMemorySize, processHandle, appletResourceUserId, sessionId);
|
||||
}
|
||||
|
||||
IAudioRenderer::~IAudioRenderer() {
|
||||
impl.Finalize();
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(impl.GetSystem().GetSampleRate());
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(impl.GetSystem().GetSampleCount());
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(impl.GetSystem().GetMixBufferCount());
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(impl.GetSystem().IsActive() ? 0 : 1);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto input{request.inputBuf.at(0)};
|
||||
auto output{request.outputBuf.at(0)};
|
||||
auto performanceOutput{request.outputBuf.size() > 1 ? request.outputBuf.at(1) : span<u8>{}};
|
||||
|
||||
if (auto result{impl.RequestUpdate(input, performanceOutput, output)}; result.IsError()) {
|
||||
Logger::Error("Update failed error: 0x{:X}", u32{result});
|
||||
return Result{result};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
impl.GetSystem().Start();
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
impl.GetSystem().Stop();
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
if (impl.GetSystem().GetExecutionMode() == AudioCore::ExecutionMode::Manual)
|
||||
return Result{Service::Audio::ResultNotSupported};
|
||||
|
||||
auto handle{state.process->InsertItem(renderedEvent)};
|
||||
Logger::Debug("System Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::SetRenderingTimeLimit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
u32 limit{request.Pop<u32>()};
|
||||
impl.GetSystem().SetRenderingTimeLimit(limit);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetRenderingTimeLimit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(impl.GetSystem().GetRenderingTimeLimit());
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::SetVoiceDropParameter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
float voiceDropParam{request.Pop<float>()};
|
||||
impl.GetSystem().SetVoiceDropParameter(voiceDropParam);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetVoiceDropParameter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<float>(impl.GetSystem().GetVoiceDropParameter());
|
||||
return {};
|
||||
}
|
||||
}
|
84
app/src/main/cpp/skyline/services/audio/IAudioRenderer.h
Normal file
84
app/src/main/cpp/skyline/services/audio/IAudioRenderer.h
Normal file
@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <audio_core/core/hle/kernel/k_event.h>
|
||||
#include <audio_core/core/hle/kernel/k_transfer_memory.h>
|
||||
#include <audio_core/renderer/audio_renderer.h>
|
||||
#include <services/serviceman.h>
|
||||
|
||||
namespace skyline::service::audio {
|
||||
/**
|
||||
* @brief IAudioRenderer is used to control an audio renderer output
|
||||
* @url https://switchbrew.org/wiki/Audio_services#IAudioRenderer
|
||||
*/
|
||||
class IAudioRenderer : public BaseService {
|
||||
private:
|
||||
std::shared_ptr<type::KEvent> renderedEvent;
|
||||
KernelShim::KEvent renderedEventWrapper;
|
||||
KernelShim::KTransferMemory transferMemoryWrapper;
|
||||
AudioCore::AudioRenderer::Renderer impl;
|
||||
|
||||
public:
|
||||
IAudioRenderer(const DeviceState &state, ServiceManager &manager,
|
||||
AudioCore::AudioRenderer::Manager &rendererManager,
|
||||
const AudioCore::AudioRendererParameterInternal ¶ms,
|
||||
u64 transferMemorySize, u32 processHandle, u64 appletResourceUserId, i32 sessionId);
|
||||
|
||||
~IAudioRenderer();
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetSampleRate
|
||||
*/
|
||||
Result GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetSampleCount
|
||||
*/
|
||||
Result GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetMixBufferCount
|
||||
*/
|
||||
Result GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioRendererState (stubbed)?
|
||||
*/
|
||||
Result GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result SetRenderingTimeLimit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result GetRenderingTimeLimit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result SetVoiceDropParameter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
Result GetVoiceDropParameter(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
SERVICE_DECL(
|
||||
SFUNC(0x0, IAudioRenderer, GetSampleRate),
|
||||
SFUNC(0x1, IAudioRenderer, GetSampleCount),
|
||||
SFUNC(0x2, IAudioRenderer, GetMixBufferCount),
|
||||
SFUNC(0x3, IAudioRenderer, GetState),
|
||||
SFUNC(0x4, IAudioRenderer, RequestUpdate),
|
||||
SFUNC(0x5, IAudioRenderer, Start),
|
||||
SFUNC(0x6, IAudioRenderer, Stop),
|
||||
SFUNC(0x7, IAudioRenderer, QuerySystemEvent),
|
||||
SFUNC(0x8, IAudioRenderer, SetRenderingTimeLimit),
|
||||
SFUNC(0x9, IAudioRenderer, GetRenderingTimeLimit),
|
||||
SFUNC(0xA, IAudioRenderer, RequestUpdate),
|
||||
SFUNC(0xC, IAudioRenderer, SetVoiceDropParameter),
|
||||
SFUNC(0xD, IAudioRenderer, GetVoiceDropParameter)
|
||||
)
|
||||
};
|
||||
}
|
@ -1,182 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <common/settings.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include "IAudioRenderer.h"
|
||||
|
||||
namespace skyline::service::audio::IAudioRenderer {
|
||||
IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParameters ¶meters)
|
||||
: systemEvent(std::make_shared<type::KEvent>(state, true)), parameters(parameters), BaseService(state, manager) {
|
||||
track = state.audio->OpenTrack(constant::StereoChannelCount, constant::SampleRate, [&]() { systemEvent->Signal(); });
|
||||
track->Start();
|
||||
|
||||
memoryPools.resize(parameters.effectCount + parameters.voiceCount * 4);
|
||||
effects.resize(parameters.effectCount);
|
||||
voices.resize(parameters.voiceCount, Voice(state));
|
||||
|
||||
// Fill track with empty samples that we will triple buffer
|
||||
track->AppendBuffer(0);
|
||||
track->AppendBuffer(1);
|
||||
track->AppendBuffer(2);
|
||||
}
|
||||
|
||||
IAudioRenderer::~IAudioRenderer() {
|
||||
state.audio->CloseTrack(track);
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(parameters.sampleRate);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(parameters.sampleCount);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(parameters.subMixCount);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(static_cast<u32>(playbackState));
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto input{request.inputBuf.at(0).data()};
|
||||
|
||||
auto inputHeader{*reinterpret_cast<UpdateDataHeader *>(input)};
|
||||
revisionInfo.SetUserRevision(inputHeader.revision);
|
||||
input += sizeof(UpdateDataHeader);
|
||||
input += inputHeader.behaviorSize; // Unused
|
||||
|
||||
span memoryPoolsIn(reinterpret_cast<MemoryPoolIn *>(input), memoryPools.size());
|
||||
input += inputHeader.memoryPoolSize;
|
||||
for (size_t i{}; i < memoryPools.size(); i++)
|
||||
memoryPools[i].ProcessInput(memoryPoolsIn[i]);
|
||||
|
||||
input += inputHeader.voiceResourceSize;
|
||||
|
||||
span voicesIn(reinterpret_cast<VoiceIn *>(input), parameters.voiceCount);
|
||||
input += inputHeader.voiceSize;
|
||||
for (u32 i{}; i < voicesIn.size(); i++)
|
||||
voices[i].ProcessInput(voicesIn[i]);
|
||||
|
||||
span effectsIn(reinterpret_cast<EffectIn *>(input), parameters.effectCount);
|
||||
for (u32 i{}; i < effectsIn.size(); i++)
|
||||
effects[i].ProcessInput(effectsIn[i]);
|
||||
|
||||
if (!*state.settings->isAudioOutputDisabled)
|
||||
UpdateAudio();
|
||||
|
||||
UpdateDataHeader outputHeader{
|
||||
.revision = constant::RevMagic,
|
||||
.behaviorSize = 0xB0,
|
||||
.memoryPoolSize = (parameters.effectCount + parameters.voiceCount * 4) * static_cast<u32>(sizeof(MemoryPoolOut)),
|
||||
.voiceSize = parameters.voiceCount * static_cast<u32>(sizeof(VoiceOut)),
|
||||
.effectSize = parameters.effectCount * static_cast<u32>(sizeof(EffectOut)),
|
||||
.sinkSize = parameters.sinkCount * 0x20,
|
||||
.performanceManagerSize = 0x10,
|
||||
.elapsedFrameCountInfoSize = 0x0
|
||||
};
|
||||
|
||||
if (revisionInfo.ElapsedFrameCountSupported())
|
||||
outputHeader.elapsedFrameCountInfoSize = 0x10;
|
||||
|
||||
outputHeader.totalSize = sizeof(UpdateDataHeader) +
|
||||
outputHeader.behaviorSize +
|
||||
outputHeader.memoryPoolSize +
|
||||
outputHeader.voiceSize +
|
||||
outputHeader.effectSize +
|
||||
outputHeader.sinkSize +
|
||||
outputHeader.performanceManagerSize +
|
||||
outputHeader.elapsedFrameCountInfoSize;
|
||||
|
||||
auto output{request.outputBuf.at(0).data()};
|
||||
std::memset(output, 0, request.outputBuf.at(0).size());
|
||||
|
||||
*reinterpret_cast<UpdateDataHeader *>(output) = outputHeader;
|
||||
output += sizeof(UpdateDataHeader);
|
||||
|
||||
for (const auto &memoryPool : memoryPools) {
|
||||
*reinterpret_cast<MemoryPoolOut *>(output) = memoryPool.output;
|
||||
output += sizeof(MemoryPoolOut);
|
||||
}
|
||||
|
||||
for (const auto &voice : voices) {
|
||||
*reinterpret_cast<VoiceOut *>(output) = voice.output;
|
||||
output += sizeof(VoiceOut);
|
||||
}
|
||||
|
||||
for (const auto &effect : effects) {
|
||||
*reinterpret_cast<EffectOut *>(output) = effect.output;
|
||||
output += sizeof(EffectOut);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void IAudioRenderer::UpdateAudio() {
|
||||
auto released{track->GetReleasedBuffers(2)};
|
||||
|
||||
for (auto &tag : released) {
|
||||
MixFinalBuffer();
|
||||
track->AppendBuffer(tag, sampleBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void IAudioRenderer::MixFinalBuffer() {
|
||||
u32 writtenSamples{};
|
||||
|
||||
for (auto &voice : voices) {
|
||||
if (!voice.Playable())
|
||||
continue;
|
||||
|
||||
u32 bufferOffset{};
|
||||
u32 pendingSamples{constant::MixBufferSize};
|
||||
|
||||
while (pendingSamples > 0) {
|
||||
u32 voiceBufferOffset{};
|
||||
u32 voiceBufferSize{};
|
||||
auto &voiceSamples{voice.GetBufferData(pendingSamples, voiceBufferOffset, voiceBufferSize)};
|
||||
|
||||
if (voiceBufferSize == 0)
|
||||
break;
|
||||
|
||||
pendingSamples -= voiceBufferSize / constant::StereoChannelCount;
|
||||
|
||||
for (auto index{voiceBufferOffset}; index < voiceBufferOffset + voiceBufferSize; index++) {
|
||||
if (writtenSamples == bufferOffset) {
|
||||
sampleBuffer[bufferOffset] = skyline::audio::Saturate<i16, i32>(voiceSamples[index] * voice.volume);
|
||||
|
||||
writtenSamples++;
|
||||
} else {
|
||||
sampleBuffer[bufferOffset] = skyline::audio::Saturate<i16, i32>(sampleBuffer[bufferOffset] + (voiceSamples[index] * voice.volume));
|
||||
}
|
||||
|
||||
bufferOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result IAudioRenderer::Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
playbackState = skyline::audio::AudioOutState::Started;
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
playbackState = skyline::audio::AudioOutState::Stopped;
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRenderer::QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle{state.process->InsertItem(systemEvent)};
|
||||
Logger::Debug("System Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
return {};
|
||||
}
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <services/serviceman.h>
|
||||
#include <audio.h>
|
||||
#include "memory_pool.h"
|
||||
#include "effect.h"
|
||||
#include "voice.h"
|
||||
#include "revision_info.h"
|
||||
|
||||
namespace skyline {
|
||||
namespace constant {
|
||||
constexpr u8 BufferAlignment{0x40}; //!< The alignment for all audren buffers
|
||||
}
|
||||
|
||||
namespace service::audio::IAudioRenderer {
|
||||
/**
|
||||
* @brief The parameters used to configure an IAudioRenderer
|
||||
*/
|
||||
struct AudioRendererParameters {
|
||||
u32 sampleRate;
|
||||
u32 sampleCount;
|
||||
u32 mixBufferCount;
|
||||
u32 subMixCount;
|
||||
u32 voiceCount;
|
||||
u32 sinkCount;
|
||||
u32 effectCount;
|
||||
u32 performanceManagerCount;
|
||||
u32 voiceDropEnable;
|
||||
u32 splitterCount;
|
||||
u32 splitterDestinationDataCount;
|
||||
u32 _unk0_;
|
||||
u32 revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameters) == 0x34);
|
||||
|
||||
/**
|
||||
* @brief Header containing information about the software side audren implementation and the sizes of all input data
|
||||
*/
|
||||
struct UpdateDataHeader {
|
||||
u32 revision;
|
||||
u32 behaviorSize;
|
||||
u32 memoryPoolSize;
|
||||
u32 voiceSize;
|
||||
u32 voiceResourceSize;
|
||||
u32 effectSize;
|
||||
u32 mixSize;
|
||||
u32 sinkSize;
|
||||
u32 performanceManagerSize;
|
||||
u32 _unk0_;
|
||||
u32 elapsedFrameCountInfoSize;
|
||||
u32 _unk1_[4];
|
||||
u32 totalSize;
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40);
|
||||
|
||||
/**
|
||||
* @brief IAudioRenderer is used to control an audio renderer output
|
||||
* @url https://switchbrew.org/wiki/Audio_services#IAudioRenderer
|
||||
*/
|
||||
class IAudioRenderer : public BaseService {
|
||||
private:
|
||||
AudioRendererParameters parameters;
|
||||
RevisionInfo revisionInfo{}; //!< Stores info about supported features for the audren revision used
|
||||
std::shared_ptr<skyline::audio::AudioTrack> track; //!< The audio track associated with the audio renderer
|
||||
std::shared_ptr<type::KEvent> systemEvent; //!< The KEvent that is signalled when the DSP has processed all the commands
|
||||
std::vector<MemoryPool> memoryPools;
|
||||
std::vector<Effect> effects;
|
||||
std::vector<Voice> voices;
|
||||
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};
|
||||
|
||||
/**
|
||||
* @brief Obtains new sample data from voices and mixes it together into the sample buffer
|
||||
* @return The amount of samples present in the buffer
|
||||
*/
|
||||
void MixFinalBuffer();
|
||||
|
||||
/**
|
||||
* @brief Appends all released buffers with new mixed sample data
|
||||
*/
|
||||
void UpdateAudio();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param parameters The parameters to use for rendering
|
||||
*/
|
||||
IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParameters ¶meters);
|
||||
|
||||
/**
|
||||
* @brief Closes the audio track
|
||||
*/
|
||||
~IAudioRenderer();
|
||||
|
||||
/**
|
||||
* @brief Returns the sample rate
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetSampleRate
|
||||
*/
|
||||
Result GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the sample count
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetSampleCount
|
||||
*/
|
||||
Result GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the number of mix buffers
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetMixBufferCount
|
||||
*/
|
||||
Result GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the state of the renderer
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioRendererState (stubbed)?
|
||||
*/
|
||||
Result GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Updates the audio renderer state and appends new data to playback buffers
|
||||
*/
|
||||
Result RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Start the audio stream from the renderer
|
||||
*/
|
||||
Result Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Stop the audio stream from the renderer
|
||||
*/
|
||||
Result Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the sample release KEvent
|
||||
*/
|
||||
Result QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
SERVICE_DECL(
|
||||
SFUNC(0x0, IAudioRenderer, GetSampleRate),
|
||||
SFUNC(0x1, IAudioRenderer, GetSampleCount),
|
||||
SFUNC(0x2, IAudioRenderer, GetMixBufferCount),
|
||||
SFUNC(0x3, IAudioRenderer, GetState),
|
||||
SFUNC(0x4, IAudioRenderer, RequestUpdate),
|
||||
SFUNC(0x5, IAudioRenderer, Start),
|
||||
SFUNC(0x6, IAudioRenderer, Stop),
|
||||
SFUNC(0x7, IAudioRenderer, QuerySystemEvent),
|
||||
SFUNC(0xA, IAudioRenderer, RequestUpdate)
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::service::audio::IAudioRenderer {
|
||||
enum class EffectState : u8 {
|
||||
None = 0, //!< The effect isn't being used
|
||||
New = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Input containing information on what effects to use on an audio stream
|
||||
*/
|
||||
struct EffectIn {
|
||||
u8 _unk0_;
|
||||
u8 isNew; //!< Whether the effect was used in the previous samples
|
||||
u8 _unk1_[0xBE];
|
||||
};
|
||||
static_assert(sizeof(EffectIn) == 0xC0);
|
||||
|
||||
/**
|
||||
* @brief Returned to inform the guest of the state of an effect
|
||||
*/
|
||||
struct EffectOut {
|
||||
EffectState state;
|
||||
u8 _pad0_[15];
|
||||
};
|
||||
static_assert(sizeof(EffectOut) == 0x10);
|
||||
|
||||
/**
|
||||
* @brief The Effect class stores the state of audio post processing effects
|
||||
*/
|
||||
class Effect {
|
||||
public:
|
||||
EffectOut output{};
|
||||
|
||||
void ProcessInput(const EffectIn &input) {
|
||||
if (input.isNew)
|
||||
output.state = EffectState::New;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include "memory_pool.h"
|
||||
|
||||
namespace skyline::service::audio::IAudioRenderer {
|
||||
void MemoryPool::ProcessInput(const MemoryPoolIn &input) {
|
||||
if (input.state == MemoryPoolState::RequestAttach)
|
||||
output.state = MemoryPoolState::Attached;
|
||||
else if (input.state == MemoryPoolState::RequestDetach)
|
||||
output.state = MemoryPoolState::Detached;
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::service::audio::IAudioRenderer {
|
||||
enum class MemoryPoolState : u32 {
|
||||
Invalid = 0,
|
||||
Unknown = 1,
|
||||
RequestDetach = 2,
|
||||
Detached = 3,
|
||||
RequestAttach = 4,
|
||||
Attached = 5,
|
||||
Released = 6,
|
||||
};
|
||||
|
||||
struct MemoryPoolIn {
|
||||
u64 address;
|
||||
u64 size;
|
||||
MemoryPoolState state;
|
||||
u32 _unk0_;
|
||||
u64 _unk1_;
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolIn) == 0x20);
|
||||
|
||||
struct MemoryPoolOut {
|
||||
MemoryPoolState state{MemoryPoolState::Detached};
|
||||
u32 _unk0_;
|
||||
u64 _unk1_;
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolOut) == 0x10);
|
||||
|
||||
/**
|
||||
* @brief The MemoryPool class stores the state of a memory pool
|
||||
*/
|
||||
class MemoryPool {
|
||||
public:
|
||||
MemoryPoolOut output{};
|
||||
|
||||
/**
|
||||
* @brief Processes the input memory pool data from the guest and sets internal data based off it
|
||||
* @param input The input data struct from guest
|
||||
*/
|
||||
void ProcessInput(const MemoryPoolIn &input);
|
||||
};
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline {
|
||||
namespace constant {
|
||||
constexpr u32 SupportedRevision{11}; //!< The audren revision our implementation supports
|
||||
constexpr u32 Rev0Magic{util::MakeMagic<u32>("REV0")}; //!< The HOS 1.0 revision magic
|
||||
constexpr u32 RevMagic{Rev0Magic + (SupportedRevision << 24)}; //!< The revision magic for our supported revision
|
||||
|
||||
namespace supportTags {
|
||||
constexpr u32 Splitter{2}; //!< The revision splitter support was added
|
||||
constexpr u32 SplitterBugFix{5}; //!< The revision the splitter buffer was made aligned
|
||||
constexpr u32 PerformanceMetricsDataFormatV2{5}; //!< The revision a new performance metrics format is used
|
||||
constexpr u32 VaradicCommandBufferSize{5}; //!< The revision support for varying command buffer sizes was added
|
||||
constexpr u32 ElapsedFrameCount{5}; //!< The revision support for counting elapsed frames was added
|
||||
}
|
||||
}
|
||||
|
||||
namespace service::audio::IAudioRenderer {
|
||||
/**
|
||||
* @brief Extracts the audren version from a revision magic
|
||||
* @param revision The revision magic
|
||||
* @return The revision number from the magic
|
||||
*/
|
||||
inline u32 ExtractVersionFromRevision(u32 revision) {
|
||||
return (revision - constant::Rev0Magic) >> 24;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The RevisionInfo class is used to query the supported features of various audren revisions
|
||||
*/
|
||||
class RevisionInfo {
|
||||
private:
|
||||
u32 userRevision; //!< The current audren revision of the guest
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Extracts the audren revision from the magic and sets the behaviour revision to it
|
||||
* @param revision The revision magic from guest
|
||||
*/
|
||||
void SetUserRevision(u32 revision) {
|
||||
userRevision = ExtractVersionFromRevision(revision);
|
||||
|
||||
if (userRevision > constant::SupportedRevision)
|
||||
throw exception("Unsupported audren revision: {}", userRevision);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the splitter is supported
|
||||
*/
|
||||
bool SplitterSupported() {
|
||||
return userRevision >= constant::supportTags::Splitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the splitter is fixed
|
||||
*/
|
||||
bool SplitterBugFixed() {
|
||||
return userRevision >= constant::supportTags::SplitterBugFix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the new performance metrics format is used
|
||||
*/
|
||||
bool UsesPerformanceMetricDataFormatV2() {
|
||||
return userRevision >= constant::supportTags::PerformanceMetricsDataFormatV2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether varying command buffer sizes are supported
|
||||
*/
|
||||
bool VaradicCommandBufferSizeSupported() {
|
||||
return userRevision >= constant::supportTags::VaradicCommandBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether elapsed frame counting is supported
|
||||
*/
|
||||
bool ElapsedFrameCountSupported() {
|
||||
return userRevision >= constant::supportTags::ElapsedFrameCount;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include <audio/downmixer.h>
|
||||
#include "voice.h"
|
||||
|
||||
namespace skyline::service::audio::IAudioRenderer {
|
||||
void Voice::SetWaveBufferIndex(u8 index) {
|
||||
bufferIndex = index & 3;
|
||||
bufferReload = true;
|
||||
}
|
||||
|
||||
Voice::Voice(const DeviceState &state) : state(state) {}
|
||||
|
||||
void Voice::ProcessInput(const VoiceIn &input) {
|
||||
// Voice no longer in use, reset it
|
||||
if (acquired && !input.acquired) {
|
||||
bufferReload = true;
|
||||
bufferIndex = 0;
|
||||
sampleOffset = 0;
|
||||
|
||||
output.playedSamplesCount = 0;
|
||||
output.playedWaveBuffersCount = 0;
|
||||
output.voiceDropsCount = 0;
|
||||
}
|
||||
|
||||
acquired = input.acquired;
|
||||
|
||||
if (!acquired)
|
||||
return;
|
||||
|
||||
if (input.firstUpdate) {
|
||||
if (input.format != skyline::audio::AudioFormat::Int16 && input.format != skyline::audio::AudioFormat::ADPCM)
|
||||
throw exception("Unsupported voice PCM format: {}", input.format);
|
||||
|
||||
format = input.format;
|
||||
sampleRate = input.sampleRate;
|
||||
|
||||
if (input.channelCount > (input.format == skyline::audio::AudioFormat::ADPCM ? 1 : 6))
|
||||
throw exception("Unsupported voice channel count: {}", input.channelCount);
|
||||
|
||||
channelCount = static_cast<u8>(input.channelCount);
|
||||
|
||||
if (input.format == skyline::audio::AudioFormat::ADPCM) {
|
||||
std::vector<std::array<i16, 2>> adpcmCoefficients(input.adpcmCoeffsSize / (sizeof(u16) * 2));
|
||||
span(adpcmCoefficients).copy_from(span(input.adpcmCoeffs, input.adpcmCoeffsSize / sizeof(u32)));
|
||||
|
||||
adpcmDecoder = skyline::audio::AdpcmDecoder(std::move(adpcmCoefficients));
|
||||
}
|
||||
|
||||
SetWaveBufferIndex(static_cast<u8>(input.baseWaveBufferIndex));
|
||||
}
|
||||
|
||||
waveBuffers = input.waveBuffers;
|
||||
volume = input.volume;
|
||||
playbackState = input.playbackState;
|
||||
}
|
||||
|
||||
void Voice::UpdateBuffers() {
|
||||
const auto ¤tBuffer{waveBuffers.at(bufferIndex)};
|
||||
span buffer(currentBuffer.pointer, currentBuffer.size);
|
||||
|
||||
if (currentBuffer.size == 0)
|
||||
return;
|
||||
|
||||
switch (format) {
|
||||
case skyline::audio::AudioFormat::Int16:
|
||||
samples.resize(currentBuffer.size / sizeof(i16));
|
||||
span(samples).copy_from(buffer);
|
||||
break;
|
||||
case skyline::audio::AudioFormat::ADPCM: {
|
||||
samples = adpcmDecoder->Decode(buffer);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw exception("Unsupported PCM format used by Voice: {}", format);
|
||||
}
|
||||
|
||||
if (sampleRate != constant::SampleRate)
|
||||
samples = resampler.ResampleBuffer(samples, static_cast<double>(sampleRate) / constant::SampleRate, channelCount);
|
||||
|
||||
if (channelCount == 1 && constant::StereoChannelCount != channelCount) {
|
||||
auto originalSize{samples.size()};
|
||||
samples.resize((originalSize / channelCount) * constant::StereoChannelCount);
|
||||
|
||||
for (auto monoIndex{originalSize - 1}, targetIndex{samples.size()}; monoIndex > 0; monoIndex--) {
|
||||
auto sample{samples[monoIndex]};
|
||||
for (u8 i{}; i < constant::StereoChannelCount; i++)
|
||||
samples[--targetIndex] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
if (channelCount == constant::SurroundChannelCount) {
|
||||
span(samples).copy_from(span(skyline::audio::DownMix(span(samples).cast<skyline::audio::Surround51Sample>())));
|
||||
samples.resize((samples.size() / constant::SurroundChannelCount) * constant::StereoChannelCount);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<i16> &Voice::GetBufferData(u32 maxSamples, u32 &outOffset, u32 &outSize) {
|
||||
auto ¤tBuffer{waveBuffers.at(bufferIndex)};
|
||||
|
||||
if (!acquired || playbackState != skyline::audio::AudioOutState::Started) {
|
||||
outSize = 0;
|
||||
return samples;
|
||||
}
|
||||
|
||||
if (bufferReload) {
|
||||
bufferReload = false;
|
||||
UpdateBuffers();
|
||||
}
|
||||
|
||||
outOffset = sampleOffset;
|
||||
outSize = std::min(maxSamples * constant::StereoChannelCount, static_cast<u32>(samples.size() - sampleOffset));
|
||||
|
||||
output.playedSamplesCount += outSize / constant::StereoChannelCount;
|
||||
sampleOffset += outSize;
|
||||
|
||||
if (sampleOffset == samples.size()) {
|
||||
sampleOffset = 0;
|
||||
|
||||
if (currentBuffer.lastBuffer)
|
||||
playbackState = skyline::audio::AudioOutState::Paused;
|
||||
|
||||
if (!currentBuffer.looping)
|
||||
SetWaveBufferIndex(static_cast<u8>(bufferIndex + 1));
|
||||
|
||||
output.playedWaveBuffersCount++;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <audio/resampler.h>
|
||||
#include <audio/adpcm_decoder.h>
|
||||
#include <audio.h>
|
||||
|
||||
namespace skyline::service::audio::IAudioRenderer {
|
||||
struct BiquadFilter {
|
||||
u8 enable;
|
||||
u8 _pad0_;
|
||||
u16 b0;
|
||||
u16 b1;
|
||||
u16 b2;
|
||||
u16 a1;
|
||||
u16 a2;
|
||||
};
|
||||
static_assert(sizeof(BiquadFilter) == 0xC);
|
||||
|
||||
struct WaveBuffer {
|
||||
u8 *pointer;
|
||||
u64 size;
|
||||
u32 firstSampleOffset;
|
||||
u32 lastSampleOffset;
|
||||
u8 looping; //!< Whether to loop the buffer
|
||||
u8 lastBuffer; //!< Whether this is the last populated buffer
|
||||
u16 _unk0_;
|
||||
u32 _unk1_;
|
||||
u64 adpcmLoopContextPosition;
|
||||
u64 adpcmLoopContextSize;
|
||||
u64 _unk2_;
|
||||
};
|
||||
static_assert(sizeof(WaveBuffer) == 0x38);
|
||||
|
||||
struct VoiceIn {
|
||||
u32 slot;
|
||||
u32 nodeId;
|
||||
u8 firstUpdate; //!< Whether this voice is newly added
|
||||
u8 acquired; //!< Whether the sample is in use
|
||||
skyline::audio::AudioOutState playbackState;
|
||||
skyline::audio::AudioFormat format;
|
||||
u32 sampleRate;
|
||||
u32 priority;
|
||||
u32 _unk0_;
|
||||
u32 channelCount;
|
||||
float pitch;
|
||||
float volume;
|
||||
std::array<BiquadFilter, 2> biquadFilters;
|
||||
u32 appendedWaveBuffersCount;
|
||||
u32 baseWaveBufferIndex;
|
||||
u32 _unk1_;
|
||||
u32 *adpcmCoeffs;
|
||||
u64 adpcmCoeffsSize;
|
||||
u32 destination;
|
||||
u32 _pad0_;
|
||||
std::array<WaveBuffer, 4> waveBuffers;
|
||||
std::array<u32, 6> voiceChannelResourceIds;
|
||||
u32 _pad1_[6];
|
||||
};
|
||||
static_assert(sizeof(VoiceIn) == 0x170);
|
||||
|
||||
|
||||
struct VoiceOut {
|
||||
u64 playedSamplesCount;
|
||||
u32 playedWaveBuffersCount;
|
||||
u32 voiceDropsCount; //!< The amount of time audio frames have been dropped due to the rendering time limit
|
||||
};
|
||||
static_assert(sizeof(VoiceOut) == 0x10);
|
||||
|
||||
/**
|
||||
* @brief The Voice class manages an audio voice
|
||||
*/
|
||||
class Voice {
|
||||
private:
|
||||
const DeviceState &state;
|
||||
std::array<WaveBuffer, 4> waveBuffers;
|
||||
std::vector<i16> samples; //!< A vector containing processed sample data
|
||||
skyline::audio::Resampler resampler; //!< The resampler object used for changing the sample rate of a wave buffer's stream
|
||||
std::optional<skyline::audio::AdpcmDecoder> adpcmDecoder;
|
||||
|
||||
bool acquired{false}; //!< If the voice is in use
|
||||
bool bufferReload{true};
|
||||
u8 bufferIndex{}; //!< The index of the wave buffer currently in use
|
||||
u32 sampleOffset{}; //!< The offset in the sample data of the current wave buffer
|
||||
u32 sampleRate{};
|
||||
u8 channelCount{};
|
||||
skyline::audio::AudioOutState playbackState{skyline::audio::AudioOutState::Stopped};
|
||||
skyline::audio::AudioFormat format{skyline::audio::AudioFormat::Invalid};
|
||||
|
||||
/**
|
||||
* @brief Updates the sample buffer with data from the current wave buffer and processes it
|
||||
*/
|
||||
void UpdateBuffers();
|
||||
|
||||
/**
|
||||
* @brief Sets the current wave buffer index to use
|
||||
* @param index The index to use
|
||||
*/
|
||||
void SetWaveBufferIndex(u8 index);
|
||||
|
||||
public:
|
||||
VoiceOut output{};
|
||||
float volume{};
|
||||
|
||||
Voice(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Reads the input voice data from the guest and sets internal data based off it
|
||||
* @param input The input data struct from guest
|
||||
*/
|
||||
void ProcessInput(const VoiceIn &input);
|
||||
|
||||
/**
|
||||
* @brief Obtains the voices audio sample data, updating it if required
|
||||
* @param maxSamples The maximum amount of samples the output buffer should contain
|
||||
* @return A vector of I16 PCM sample data
|
||||
*/
|
||||
std::vector<i16> &GetBufferData(u32 maxSamples, u32 &outOffset, u32 &outSize);
|
||||
|
||||
/**
|
||||
* @return If the voice is currently playable
|
||||
*/
|
||||
bool Playable() {
|
||||
return acquired && playbackState == skyline::audio::AudioOutState::Started && waveBuffers[bufferIndex].size != 0;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,90 +1,65 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#include "IAudioRenderer/IAudioRenderer.h"
|
||||
#include <audio_core/common/audio_renderer_parameter.h>
|
||||
#include <audio_core/audio_render_manager.h>
|
||||
#include <common/utils.h>
|
||||
#include <audio.h>
|
||||
#include "IAudioRenderer.h"
|
||||
#include "IAudioDevice.h"
|
||||
#include "IAudioRendererManager.h"
|
||||
|
||||
namespace skyline::service::audio {
|
||||
IAudioRendererManager::IAudioRendererManager(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {}
|
||||
IAudioRendererManager::IAudioRendererManager(const DeviceState &state, ServiceManager &manager)
|
||||
: BaseService(state, manager) {}
|
||||
|
||||
Result IAudioRendererManager::OpenAudioRenderer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
IAudioRenderer::AudioRendererParameters params{request.Pop<IAudioRenderer::AudioRendererParameters>()};
|
||||
const auto ¶ms{request.Pop<AudioCore::AudioRendererParameterInternal>()};
|
||||
u64 transferMemorySize{request.Pop<u64>()};
|
||||
u64 appletResourceUserId{request.Pop<u64>()};
|
||||
auto transferMemoryHandle{request.copyHandles.at(0)};
|
||||
auto processHandle{request.copyHandles.at(1)};
|
||||
|
||||
Logger::Debug("Opening a rev {} IAudioRenderer with sample rate: {}, voice count: {}, effect count: {}", IAudioRenderer::ExtractVersionFromRevision(params.revision), params.sampleRate, params.voiceCount, params.effectCount);
|
||||
i32 sessionId{state.audio->audioRendererManager->GetSessionId()};
|
||||
if (sessionId == -1) {
|
||||
Logger::Warn("Out of audio renderer sessions!");
|
||||
return Result{Service::Audio::ResultOutOfSessions};
|
||||
}
|
||||
|
||||
manager.RegisterService(std::make_shared<IAudioRenderer::IAudioRenderer>(state, manager, params), session, response);
|
||||
manager.RegisterService(std::make_shared<IAudioRenderer>(state, manager,
|
||||
*state.audio->audioRendererManager,
|
||||
params,
|
||||
transferMemorySize, processHandle, appletResourceUserId, sessionId),
|
||||
session, response);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRendererManager::GetAudioRendererWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
IAudioRenderer::AudioRendererParameters params{request.Pop<IAudioRenderer::AudioRendererParameters>()};
|
||||
Result IAudioRendererManager::GetWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
const auto ¶ms{request.Pop<AudioCore::AudioRendererParameterInternal>()};
|
||||
|
||||
IAudioRenderer::RevisionInfo revisionInfo{};
|
||||
revisionInfo.SetUserRevision(params.revision);
|
||||
u64 size{};
|
||||
auto err{state.audio->audioRendererManager->GetWorkBufferSize(params, size)};
|
||||
if (err.IsError())
|
||||
Logger::Warn("Failed to calculate work buffer size");
|
||||
|
||||
u32 totalMixCount{params.subMixCount + 1};
|
||||
|
||||
u64 size{util::AlignUp(params.mixBufferCount * 4, constant::BufferAlignment) +
|
||||
params.subMixCount * 0x400 +
|
||||
totalMixCount * 0x940 +
|
||||
params.voiceCount * 0x3F0 +
|
||||
util::AlignUp(totalMixCount * 8, 16) +
|
||||
util::AlignUp(params.voiceCount * 8, 16) +
|
||||
util::AlignUp(((params.sinkCount + params.subMixCount) * 0x3C0 + params.sampleCount * 4) * (params.mixBufferCount + 6), constant::BufferAlignment) + (params.sinkCount + params.subMixCount) * 0x2C0 + (params.effectCount + params.voiceCount * 4) * 0x30 + 0x50};
|
||||
|
||||
if (revisionInfo.SplitterSupported()) {
|
||||
i32 nodeStateWorkSize{static_cast<i32>(util::AlignUp(totalMixCount, constant::BufferAlignment))};
|
||||
if (nodeStateWorkSize < 0)
|
||||
nodeStateWorkSize |= 7;
|
||||
|
||||
nodeStateWorkSize = static_cast<i32>(4 * (totalMixCount * totalMixCount) + 12 * totalMixCount + static_cast<u32>(2 * (nodeStateWorkSize / 8)));
|
||||
|
||||
i32 edgeMatrixWorkSize{static_cast<i32>(util::AlignUp(totalMixCount * totalMixCount, constant::BufferAlignment))};
|
||||
if (edgeMatrixWorkSize < 0)
|
||||
edgeMatrixWorkSize |= 7;
|
||||
|
||||
edgeMatrixWorkSize /= 8;
|
||||
size += util::AlignUp(static_cast<u32>(edgeMatrixWorkSize + nodeStateWorkSize), 16);
|
||||
}
|
||||
|
||||
u64 splitterWorkSize{};
|
||||
if (revisionInfo.SplitterSupported()) {
|
||||
splitterWorkSize += params.splitterDestinationDataCount * 0xE0 + params.splitterCount * 0x20;
|
||||
|
||||
if (revisionInfo.SplitterBugFixed())
|
||||
splitterWorkSize += util::AlignUp(4 * params.splitterDestinationDataCount, 16);
|
||||
}
|
||||
|
||||
size = params.sinkCount * 0x170 + (params.sinkCount + params.subMixCount) * 0x280 + params.effectCount * 0x4C0 + ((size + splitterWorkSize + 0x30 * params.effectCount + (4 * params.voiceCount) + 0x8F) & ~0x3FUL) + ((params.voiceCount << 8) | 0x40);
|
||||
|
||||
if (params.performanceManagerCount > 0) {
|
||||
i64 performanceMetricsBufferSize{};
|
||||
if (revisionInfo.UsesPerformanceMetricDataFormatV2()) {
|
||||
performanceMetricsBufferSize = (params.voiceCount + params.effectCount + totalMixCount + params.sinkCount) + 0x990;
|
||||
} else {
|
||||
performanceMetricsBufferSize = ((static_cast<i64>((params.voiceCount + params.effectCount + totalMixCount + params.sinkCount)) << 32) >> 0x1C) + 0x658;
|
||||
}
|
||||
|
||||
size += static_cast<u64>((performanceMetricsBufferSize * (params.performanceManagerCount + 1) + 0xFF) & ~0x3FL);
|
||||
}
|
||||
|
||||
if (revisionInfo.VaradicCommandBufferSizeSupported()) {
|
||||
size += params.effectCount * 0x840 + params.subMixCount * 0x5A38 + params.sinkCount * 0x148 + params.splitterDestinationDataCount * 0x540 + (params.splitterCount * 0x68 + 0x2E0) * params.voiceCount + ((params.voiceCount + params.subMixCount + params.effectCount + params.sinkCount + 0x65) << 6) + 0x3F8 + 0x7E;
|
||||
} else {
|
||||
size += 0x1807E;
|
||||
}
|
||||
|
||||
size = util::AlignUp(size, 0x1000);
|
||||
|
||||
Logger::Debug("Work buffer size: 0x{:X}", size);
|
||||
response.Push<u64>(size);
|
||||
return {};
|
||||
|
||||
return Result{err};
|
||||
}
|
||||
|
||||
Result IAudioRendererManager::GetAudioDeviceService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
manager.RegisterService(SRVREG(IAudioDevice), session, response);
|
||||
u64 appletResourceUserId{request.Pop<u64>()};
|
||||
manager.RegisterService(std::make_shared<IAudioDevice>(state, manager, appletResourceUserId, util::MakeMagic<u32>("REV1")), session, response);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IAudioRendererManager::GetAudioDeviceServiceWithRevisionInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
u32 revision{request.Pop<u32>()};
|
||||
u64 appletResourceUserId{request.Pop<u64>()};
|
||||
manager.RegisterService(std::make_shared<IAudioDevice>(state, manager, appletResourceUserId, revision), session, response);
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2022 yuzu Emulator Project (https://github.com/yuzu-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -22,7 +23,7 @@ namespace skyline::service::audio {
|
||||
/**
|
||||
* @brief Calculates the size of the buffer the guest needs to allocate for IAudioRendererManager
|
||||
*/
|
||||
Result GetAudioRendererWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
Result GetWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to an instance of an IAudioDevice
|
||||
@ -30,11 +31,17 @@ namespace skyline::service::audio {
|
||||
*/
|
||||
Result GetAudioDeviceService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to an instance of an IAudioDevice
|
||||
* @url https://switchbrew.org/wiki/Audio_services#GetAudioDeviceServiceWithRevisionInfo
|
||||
*/
|
||||
Result GetAudioDeviceServiceWithRevisionInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
SERVICE_DECL(
|
||||
SFUNC(0x0, IAudioRendererManager, OpenAudioRenderer),
|
||||
SFUNC(0x1, IAudioRendererManager, GetAudioRendererWorkBufferSize),
|
||||
SFUNC(0x1, IAudioRendererManager, GetWorkBufferSize),
|
||||
SFUNC(0x2, IAudioRendererManager, GetAudioDeviceService),
|
||||
SFUNC(0x4, IAudioRendererManager, GetAudioDeviceService)
|
||||
SFUNC(0x4, IAudioRendererManager, GetAudioDeviceServiceWithRevisionInfo)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user