mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-22 15:39:17 +01:00
Add support for audio renderer services
Audren is used by many games and homebrews to make use of more advanced audio features than audout.
This commit is contained in:
parent
ddd1fc61ef
commit
bbe61e37f2
@ -53,6 +53,10 @@ add_library(skyline SHARED
|
||||
${source_DIR}/skyline/services/sm/sm.cpp
|
||||
${source_DIR}/skyline/services/fatal/fatal.cpp
|
||||
${source_DIR}/skyline/services/audout/audout.cpp
|
||||
${source_DIR}/skyline/services/audren/IAudioRendererManager.cpp
|
||||
${source_DIR}/skyline/services/audren/IAudioRenderer.cpp
|
||||
${source_DIR}/skyline/services/audren/voice.cpp
|
||||
${source_DIR}/skyline/services/audren/memoryPool.cpp
|
||||
${source_DIR}/skyline/services/set/sys.cpp
|
||||
${source_DIR}/skyline/services/apm/apm.cpp
|
||||
${source_DIR}/skyline/services/am/applet.cpp
|
||||
|
186
app/src/main/cpp/skyline/services/audren/IAudioRenderer.cpp
Normal file
186
app/src/main/cpp/skyline/services/audren/IAudioRenderer.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "IAudioRenderer.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParams ¶ms) : releaseEvent(std::make_shared<type::KEvent>(state)), rendererParams(params), BaseService(state, manager, false, Service::IAudioRenderer, {
|
||||
{0x0, SFUNC(IAudioRenderer::GetSampleRate)},
|
||||
{0x1, SFUNC(IAudioRenderer::GetSampleCount)},
|
||||
{0x2, SFUNC(IAudioRenderer::GetMixBufferCount)},
|
||||
{0x3, SFUNC(IAudioRenderer::GetState)},
|
||||
{0x4, SFUNC(IAudioRenderer::RequestUpdate)},
|
||||
{0x5, SFUNC(IAudioRenderer::Start)},
|
||||
{0x6, SFUNC(IAudioRenderer::Stop)},
|
||||
{0x7, SFUNC(IAudioRenderer::QuerySystemEvent)},
|
||||
}) {
|
||||
track = state.audio->OpenTrack(audio::constant::ChannelCount, params.sampleRate, [this]() { this->releaseEvent->Signal(); });
|
||||
track->Start();
|
||||
|
||||
samplesPerBuffer = state.settings->GetInt("audren_buffer_size");
|
||||
memoryPoolCount = rendererParams.effectCount + rendererParams.voiceCount * 4;
|
||||
memoryPools.resize(memoryPoolCount);
|
||||
effects.resize(rendererParams.effectCount);
|
||||
voices.resize(rendererParams.voiceCount, Voice(state));
|
||||
|
||||
// Fill track with empty samples that we will triple buffer
|
||||
track->AppendBuffer(std::vector<i16>(), 0);
|
||||
track->AppendBuffer(std::vector<i16>(), 1);
|
||||
track->AppendBuffer(std::vector<i16>(), 2);
|
||||
}
|
||||
|
||||
IAudioRenderer::~IAudioRenderer() {
|
||||
state.audio->CloseTrack(track);
|
||||
}
|
||||
|
||||
void IAudioRenderer::GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(rendererParams.sampleRate);
|
||||
}
|
||||
|
||||
void IAudioRenderer::GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(rendererParams.sampleCount);
|
||||
}
|
||||
|
||||
void IAudioRenderer::GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push<u32>(rendererParams.subMixCount);
|
||||
}
|
||||
|
||||
void IAudioRenderer::GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(static_cast<u32>(playbackState));
|
||||
}
|
||||
|
||||
void IAudioRenderer::RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
u64 inputAddress = request.inputBuf.at(0).address;
|
||||
|
||||
auto inputHeader = state.process->GetObject<UpdateDataHeader>(inputAddress);
|
||||
revisionInfo.SetUserRevision(inputHeader.revision);
|
||||
inputAddress += sizeof(UpdateDataHeader);
|
||||
inputAddress += inputHeader.behaviorSize; // Unused
|
||||
|
||||
std::vector<MemoryPoolIn> memoryPoolsIn(memoryPoolCount);
|
||||
state.process->ReadMemory(memoryPoolsIn.data(), inputAddress, memoryPoolCount * sizeof(MemoryPoolIn));
|
||||
inputAddress += inputHeader.memoryPoolSize;
|
||||
|
||||
for (int i = 0; i < memoryPoolsIn.size(); i++)
|
||||
memoryPools[i].ProcessInput(memoryPoolsIn[i]);
|
||||
|
||||
inputAddress += inputHeader.voiceResourceSize;
|
||||
std::vector<VoiceIn> voicesIn(rendererParams.voiceCount);
|
||||
state.process->ReadMemory(voicesIn.data(), inputAddress, rendererParams.voiceCount * sizeof(VoiceIn));
|
||||
inputAddress += inputHeader.voiceSize;
|
||||
|
||||
for (int i = 0; i < voicesIn.size(); i++)
|
||||
voices[i].ProcessInput(voicesIn[i]);
|
||||
|
||||
std::vector<EffectIn> effectsIn(rendererParams.effectCount);
|
||||
state.process->ReadMemory(effectsIn.data(), inputAddress, rendererParams.effectCount * sizeof(EffectIn));
|
||||
|
||||
for (int i = 0; i < effectsIn.size(); i++)
|
||||
effects[i].ProcessInput(effectsIn[i]);
|
||||
|
||||
UpdateAudio();
|
||||
|
||||
UpdateDataHeader outputHeader {
|
||||
.revision = constant::RevMagic,
|
||||
.behaviorSize = 0xb0,
|
||||
.memoryPoolSize = (rendererParams.effectCount + rendererParams.voiceCount * 4) * static_cast<u32>(sizeof(MemoryPoolOut)),
|
||||
.voiceSize = rendererParams.voiceCount * static_cast<u32>(sizeof(VoiceOut)),
|
||||
.effectSize = rendererParams.effectCount * static_cast<u32>(sizeof(EffectOut)),
|
||||
.sinkSize = rendererParams.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;
|
||||
|
||||
u64 outputAddress = request.outputBuf.at(0).address;
|
||||
|
||||
state.process->WriteMemory(outputHeader, outputAddress);
|
||||
outputAddress += sizeof(UpdateDataHeader);
|
||||
|
||||
for (auto &memoryPool : memoryPools) {
|
||||
state.process->WriteMemory(memoryPool.output, outputAddress);
|
||||
outputAddress += sizeof(MemoryPoolOut);
|
||||
}
|
||||
|
||||
for (auto &voice : voices) {
|
||||
state.process->WriteMemory(voice.output, outputAddress);
|
||||
outputAddress += sizeof(VoiceOut);
|
||||
}
|
||||
|
||||
for (auto &effect : effects) {
|
||||
state.process->WriteMemory(effect.output, outputAddress);
|
||||
outputAddress += sizeof(EffectOut);
|
||||
}
|
||||
}
|
||||
|
||||
void IAudioRenderer::UpdateAudio() {
|
||||
std::vector<u64> released = track->GetReleasedBuffers(2);
|
||||
|
||||
for (auto &tag : released) {
|
||||
MixFinalBuffer();
|
||||
track->AppendBuffer(sampleBuffer, tag);
|
||||
}
|
||||
}
|
||||
|
||||
void IAudioRenderer::MixFinalBuffer() {
|
||||
int setIndex = 0;
|
||||
sampleBuffer.resize(samplesPerBuffer * audio::constant::ChannelCount);
|
||||
|
||||
for (auto &voice : voices) {
|
||||
if (!voice.Playable())
|
||||
continue;
|
||||
|
||||
int bufferOffset = 0;
|
||||
int pendingSamples = samplesPerBuffer;
|
||||
|
||||
while (pendingSamples > 0) {
|
||||
int voiceBufferSize = 0;
|
||||
int voiceBufferOffset = 0;
|
||||
std::vector<i16> &voiceSamples = voice.GetBufferData(pendingSamples, voiceBufferOffset, voiceBufferSize);
|
||||
|
||||
if (voiceBufferSize == 0)
|
||||
break;
|
||||
|
||||
pendingSamples -= voiceBufferSize / audio::constant::ChannelCount;
|
||||
|
||||
for (int i = voiceBufferOffset; i < voiceBufferOffset + voiceBufferSize; i++) {
|
||||
if (setIndex == bufferOffset) {
|
||||
sampleBuffer[bufferOffset] = static_cast<i16>(std::clamp(static_cast<int>(static_cast<float>(voiceSamples[i]) *
|
||||
voice.volume), static_cast<int>(std::numeric_limits<i16>::min()), static_cast<int>(std::numeric_limits<i16>::max())));
|
||||
|
||||
setIndex++;
|
||||
} else {
|
||||
sampleBuffer[bufferOffset] += static_cast<i16>(std::clamp(static_cast<int>(sampleBuffer[voiceSamples[i]]) +
|
||||
static_cast<int>(static_cast<float>(voiceSamples[i]) * voice.volume),
|
||||
static_cast<int>(std::numeric_limits<i16>::min()), static_cast<int>(std::numeric_limits<i16>::max())));
|
||||
}
|
||||
|
||||
bufferOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IAudioRenderer::Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
playbackState = audio::AudioOutState::Started;
|
||||
}
|
||||
|
||||
void IAudioRenderer::Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
playbackState = audio::AudioOutState::Stopped;
|
||||
}
|
||||
|
||||
void IAudioRenderer::QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto handle = state.process->InsertItem(releaseEvent);
|
||||
state.logger->Debug("Audren Buffer Release Event Handle: 0x{:X}", handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
}
|
||||
}
|
135
app/src/main/cpp/skyline/services/audren/IAudioRenderer.h
Normal file
135
app/src/main/cpp/skyline/services/audren/IAudioRenderer.h
Normal file
@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include <audio.h>
|
||||
#include <services/base_service.h>
|
||||
#include <services/serviceman.h>
|
||||
#include <kernel/types/KEvent.h>
|
||||
#include "memoryPool.h"
|
||||
#include "effect.h"
|
||||
#include "voice.h"
|
||||
#include "revisionInfo.h"
|
||||
|
||||
namespace skyline::service::audren {
|
||||
namespace constant {
|
||||
constexpr int BufferAlignment = 0x40; //!< The alignment for all audren buffers
|
||||
}
|
||||
/**
|
||||
* @brief The parameters used to configure an IAudioRenderer
|
||||
*/
|
||||
struct AudioRendererParams {
|
||||
u32 sampleRate; //!< The sample rate to use for the renderer
|
||||
u32 sampleCount; //!< The buffer sample count
|
||||
u32 mixBufferCount; //!< The amount of mix buffers to use
|
||||
u32 subMixCount; //!< The amount of sub mixes to use
|
||||
u32 voiceCount; //!< The amount of voices to use
|
||||
u32 sinkCount; //!< The amount of sinks to use
|
||||
u32 effectCount; //!< The amount of effects to use
|
||||
u32 performanceManagerCount; //!< The amount of performance managers to use
|
||||
u32 voiceDropEnable; //!< Whether to enable voice drop
|
||||
u32 splitterCount; //!< The amount of splitters to use
|
||||
u32 splitterDestinationDataCount; //!< The amount of splitter destination outputs to use
|
||||
u32 _unk0_;
|
||||
u32 revision; //!< The revision of audren to use
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParams) == 0x34);
|
||||
|
||||
/**
|
||||
* @brief Header containing information about the software side audren implementation
|
||||
*/
|
||||
struct UpdateDataHeader {
|
||||
u32 revision; //!< Revision of the software implementation
|
||||
u32 behaviorSize; //!< The total size of the behaviour info
|
||||
u32 memoryPoolSize; //!< The total size of all MemoryPoolIn structs
|
||||
u32 voiceSize; //!< The total size of all VoiceIn structs
|
||||
u32 voiceResourceSize; //!< The total size of the voice resources
|
||||
u32 effectSize; //!< The total size of all EffectIn structs
|
||||
u32 mixSize; //!< The total size of all mixer descriptors in the input
|
||||
u32 sinkSize; //!< The total size of all sink descriptors in the input
|
||||
u32 performanceManagerSize; //!< The total size of all performance manager descriptors in the input
|
||||
u32 _unk0_;
|
||||
u32 elapsedFrameCountInfoSize; //!< The total size of all the elapsed frame info
|
||||
u32 _unk1_[4];
|
||||
u32 totalSize; //!< The total size of the whole input
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40);
|
||||
|
||||
/**
|
||||
* @brief IAudioRenderer is used to control an audio renderer output (https://switchbrew.org/wiki/Audio_services#IAudioRenderer)
|
||||
*/
|
||||
class IAudioRenderer : public BaseService {
|
||||
private:
|
||||
std::shared_ptr<audio::AudioTrack> track; //!< The audio track associated with the audio renderer
|
||||
AudioRendererParams rendererParams; //!< The parameters to use for the renderer
|
||||
std::shared_ptr<type::KEvent> releaseEvent; //!< The KEvent that is signalled when a buffer has been released
|
||||
std::vector<MemoryPool> memoryPools; //!< An vector of all memory pools that the guest may need
|
||||
std::vector<Effect> effects; //!< An vector of all effects that the guest may need
|
||||
std::vector<Voice> voices; //!< An vector of all voices that the guest may need
|
||||
std::vector<i16> sampleBuffer; //!< The final output data that is appended to the stream
|
||||
RevisionInfo revisionInfo{}; //!< Stores info about supported features for the audren revision used
|
||||
|
||||
audio::AudioOutState playbackState{audio::AudioOutState::Stopped}; //!< The current state of playback
|
||||
size_t memoryPoolCount{}; //!< The amount of memory pools the guest may need
|
||||
int samplesPerBuffer{}; //!< The amount of samples each appended buffer should contain
|
||||
|
||||
/**
|
||||
* @brief Obtains new sample data from voices and mixes it together into the sample buffer
|
||||
*/
|
||||
void MixFinalBuffer();
|
||||
|
||||
/**
|
||||
* @brief Appends all released buffers with new mixed sample data
|
||||
*/
|
||||
void UpdateAudio();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param params The parameters to use for rendering
|
||||
*/
|
||||
IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParams ¶ms);
|
||||
|
||||
/**
|
||||
* @brief Closes the audio track
|
||||
*/
|
||||
~IAudioRenderer();
|
||||
|
||||
/**
|
||||
* @brief Returns the sample rate (https://switchbrew.org/wiki/Audio_services#GetSampleRate)
|
||||
*/
|
||||
void GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the sample count (https://switchbrew.org/wiki/Audio_services#GetSampleCount)
|
||||
*/
|
||||
void GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the number of mix buffers (https://switchbrew.org/wiki/Audio_services#GetMixBufferCount)
|
||||
*/
|
||||
void GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns the state of the renderer (https://switchbrew.org/wiki/Audio_services#GetAudioRendererState) (stubbed)?
|
||||
*/
|
||||
void GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Updates the audio renderer state and appends new data to playback buffers
|
||||
*/
|
||||
void RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Start the audio stream from the renderer
|
||||
*/
|
||||
void Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Stop the audio stream from the renderer
|
||||
*/
|
||||
void Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the sample release KEvent
|
||||
*/
|
||||
void QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
#include "IAudioRenderer.h"
|
||||
#include "IAudioRendererManager.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
IAudioRendererManager::IAudioRendererManager(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::IAudioRendererManager, {
|
||||
{0x0, SFUNC(IAudioRendererManager::OpenAudioRenderer)},
|
||||
{0x1, SFUNC(IAudioRendererManager::GetAudioRendererWorkBufferSize)}
|
||||
}) {}
|
||||
|
||||
void IAudioRendererManager::OpenAudioRenderer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
AudioRendererParams params = request.Pop<AudioRendererParams>();
|
||||
|
||||
state.logger->Debug("IAudioRendererManager: Opening a rev {} IAudioRenderer with sample rate: {}, voice count: {}, effect count: {}",
|
||||
ExtractVersionFromRevision(params.revision), params.sampleRate, params.voiceCount, params.effectCount);
|
||||
|
||||
manager.RegisterService(std::make_shared<IAudioRenderer>(state, manager, params), session, response);
|
||||
|
||||
}
|
||||
|
||||
void IAudioRendererManager::GetAudioRendererWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
AudioRendererParams params = request.Pop<AudioRendererParams>();
|
||||
|
||||
RevisionInfo revisionInfo;
|
||||
revisionInfo.SetUserRevision(params.revision);
|
||||
|
||||
u32 totalMixCount = params.subMixCount + 1;
|
||||
|
||||
i64 size = utils::AlignUp(params.mixBufferCount * 4, constant::BufferAlignment) +
|
||||
params.subMixCount * 0x400 +
|
||||
totalMixCount * 0x940 +
|
||||
params.voiceCount * 0x3F0 +
|
||||
utils::AlignUp(totalMixCount * 8, 16) +
|
||||
utils::AlignUp(params.voiceCount * 8, 16) +
|
||||
utils::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 = utils::AlignUp(totalMixCount, constant::BufferAlignment);
|
||||
|
||||
if (nodeStateWorkSize < 0)
|
||||
nodeStateWorkSize |= 7;
|
||||
|
||||
nodeStateWorkSize = 4 * (totalMixCount * totalMixCount) + 12 * totalMixCount + 2 * (nodeStateWorkSize / 8);
|
||||
|
||||
i32 edgeMatrixWorkSize = utils::AlignUp(totalMixCount * totalMixCount, constant::BufferAlignment);
|
||||
|
||||
if (edgeMatrixWorkSize < 0)
|
||||
edgeMatrixWorkSize |= 7;
|
||||
|
||||
edgeMatrixWorkSize /= 8;
|
||||
size += utils::AlignUp(edgeMatrixWorkSize + nodeStateWorkSize, 16);
|
||||
}
|
||||
|
||||
i64 splitterWorkSize = 0;
|
||||
|
||||
if (revisionInfo.SplitterSupported()) {
|
||||
splitterWorkSize += params.splitterDestinationDataCount * 0xE0 +
|
||||
params.splitterCount * 0x20;
|
||||
|
||||
if (revisionInfo.SplitterBugFixed())
|
||||
splitterWorkSize += utils::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) & ~0x3Fl) +
|
||||
((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 += (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 = utils::AlignUp(size, 0x1000);
|
||||
|
||||
state.logger->Debug("IAudioRendererManager: Work buffer size: 0x{:X}", size);
|
||||
response.Push<i64>(size);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <audio.h>
|
||||
#include <services/base_service.h>
|
||||
#include <services/serviceman.h>
|
||||
#include <kernel/types/KEvent.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
/**
|
||||
* @brief audren:u or IAudioRendererManager is used to manage audio renderer outputs (https://switchbrew.org/wiki/Audio_services#audren:u)
|
||||
*/
|
||||
class IAudioRendererManager : public BaseService {
|
||||
public:
|
||||
IAudioRendererManager(const DeviceState &state, ServiceManager &manager);
|
||||
|
||||
/**
|
||||
* @brief Creates a new audrenU::IAudioRenderer object and returns a handle to it
|
||||
*/
|
||||
void OpenAudioRenderer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
/**
|
||||
* @brief Calculates the size of the buffer the guest needs to allocate for audren
|
||||
*/
|
||||
void GetAudioRendererWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
};
|
||||
}
|
45
app/src/main/cpp/skyline/services/audren/effect.h
Normal file
45
app/src/main/cpp/skyline/services/audren/effect.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
/**
|
||||
* @brief This enumerates various states an effect can be in
|
||||
*/
|
||||
enum class EffectState : u8 {
|
||||
None = 0, //!< The effect isn't being used
|
||||
New = 1 //!< The effect is new
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This is in 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 This is returned to inform the guest of the state of an effect
|
||||
*/
|
||||
struct EffectOut {
|
||||
EffectState state; //!< The state of the effect
|
||||
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{}; //!< The current output state
|
||||
|
||||
inline void ProcessInput(const EffectIn &input) {
|
||||
if (input.isNew)
|
||||
output.state = EffectState::New;
|
||||
}
|
||||
};
|
||||
}
|
11
app/src/main/cpp/skyline/services/audren/memoryPool.cpp
Normal file
11
app/src/main/cpp/skyline/services/audren/memoryPool.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include <common.h>
|
||||
#include "memoryPool.h"
|
||||
|
||||
namespace skyline::service::audren {
|
||||
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;
|
||||
}
|
||||
}
|
54
app/src/main/cpp/skyline/services/audren/memoryPool.h
Normal file
54
app/src/main/cpp/skyline/services/audren/memoryPool.h
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
/**
|
||||
* @brief This enumerates various states a memory pool can be in
|
||||
*/
|
||||
enum class MemoryPoolState : u32 {
|
||||
Invalid = 0, //!< The memory pool is invalid
|
||||
Unknown = 1, //!< The memory pool is in an unknown state
|
||||
RequestDetach = 2, //!< The memory pool should be detached from
|
||||
Detached = 3, //!< The memory pool has been detached from
|
||||
RequestAttach = 4, //!< The memory pool should be attached to
|
||||
Attached = 5, //!< The memory pool has been attached to
|
||||
Released = 6 //!< The memory pool has been released
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This is in input containing information about a memory pool for use by the dsp
|
||||
*/
|
||||
struct MemoryPoolIn {
|
||||
u64 address; //!< The address of the memory pool
|
||||
u64 size; //!< The size of the memory pool
|
||||
MemoryPoolState state; //!< The state that is requested for the memory pool
|
||||
u32 _unk0_;
|
||||
u64 _unk1_;
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolIn) == 0x20);
|
||||
|
||||
/**
|
||||
* @brief This is returned to inform the guest of the state of a memory pool
|
||||
*/
|
||||
struct MemoryPoolOut {
|
||||
MemoryPoolState state = MemoryPoolState::Detached; //!< The state of the memory pool
|
||||
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{}; //!< The current output state
|
||||
|
||||
/**
|
||||
* @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);
|
||||
};
|
||||
}
|
83
app/src/main/cpp/skyline/services/audren/revisionInfo.h
Normal file
83
app/src/main/cpp/skyline/services/audren/revisionInfo.h
Normal file
@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
namespace constant {
|
||||
constexpr u32 SupportedRevision = 7; //!< The audren revision our implementation supports
|
||||
constexpr u32 Rev0Magic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); //!< 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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
inline void SetUserRevision(u32 revision) {
|
||||
userRevision = ExtractVersionFromRevision(revision);
|
||||
|
||||
if (userRevision > constant::SupportedRevision)
|
||||
throw exception("Unsupported audren revision: {}", userRevision);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the splitter is supported
|
||||
*/
|
||||
inline bool SplitterSupported() {
|
||||
return userRevision >= constant::supportTags::Splitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the splitter is fixed
|
||||
*/
|
||||
inline bool SplitterBugFixed() {
|
||||
return userRevision >= constant::supportTags::SplitterBugFix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the new performance metrics format is used
|
||||
*/
|
||||
inline bool UsesPerformanceMetricDataFormatV2() {
|
||||
return userRevision >= constant::supportTags::PerformanceMetricsDataFormatV2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether varying command buffer sizes are supported
|
||||
*/
|
||||
inline bool VaradicCommandBufferSizeSupported() {
|
||||
return userRevision >= constant::supportTags::VaradicCommandBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether elapsed frame counting is supported
|
||||
*/
|
||||
inline bool ElapsedFrameCountSupported() {
|
||||
return userRevision >= constant::supportTags::ElapsedFrameCount;
|
||||
}
|
||||
};
|
||||
}
|
110
app/src/main/cpp/skyline/services/audren/voice.cpp
Normal file
110
app/src/main/cpp/skyline/services/audren/voice.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
#include <common.h>
|
||||
#include "voice.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
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.pcmFormat != audio::PcmFormat::Int16)
|
||||
throw exception("Unsupported voice PCM format: {}", input.pcmFormat);
|
||||
|
||||
pcmFormat = input.pcmFormat;
|
||||
sampleRate = input.sampleRate;
|
||||
|
||||
if (input.channelCount > 2)
|
||||
throw exception("Unsupported voice channel count: {}", input.channelCount);
|
||||
|
||||
channelCount = input.channelCount;
|
||||
SetWaveBufferIndex(input.baseWaveBufferIndex);
|
||||
}
|
||||
|
||||
waveBuffers = input.waveBuffers;
|
||||
volume = input.volume;
|
||||
playbackState = input.playbackState;
|
||||
}
|
||||
|
||||
void Voice::UpdateBuffers() {
|
||||
WaveBuffer ¤tBuffer = waveBuffers.at(bufferIndex);
|
||||
|
||||
if (currentBuffer.size == 0)
|
||||
return;
|
||||
|
||||
switch (pcmFormat) {
|
||||
case audio::PcmFormat::Int16:
|
||||
sampleBuffer.resize(currentBuffer.size / sizeof(i16));
|
||||
state.process->ReadMemory(sampleBuffer.data(), currentBuffer.position, currentBuffer.size);
|
||||
break;
|
||||
default:
|
||||
throw exception("Unsupported voice PCM format: {}", pcmFormat);
|
||||
}
|
||||
|
||||
if (sampleRate != audio::constant::SampleRate)
|
||||
sampleBuffer = resampler.ResampleBuffer(sampleBuffer, static_cast<double>(sampleRate) / audio::constant::SampleRate, channelCount);
|
||||
|
||||
if (channelCount == 1 && audio::constant::ChannelCount != channelCount) {
|
||||
size_t originalSize = sampleBuffer.size();
|
||||
sampleBuffer.resize((originalSize / channelCount) * audio::constant::ChannelCount);
|
||||
|
||||
for (size_t monoIndex = originalSize - 1, targetIndex = sampleBuffer.size(); monoIndex > 0; monoIndex--)
|
||||
for (uint i = 0; i < audio::constant::ChannelCount; i++)
|
||||
sampleBuffer[--targetIndex] = sampleBuffer[monoIndex];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<i16> &Voice::GetBufferData(int maxSamples, int &outOffset, int &outSize) {
|
||||
WaveBuffer ¤tBuffer = waveBuffers.at(bufferIndex);
|
||||
|
||||
if (!acquired || playbackState != audio::AudioOutState::Started) {
|
||||
outSize = 0;
|
||||
return sampleBuffer;
|
||||
}
|
||||
|
||||
if (bufferReload) {
|
||||
bufferReload = false;
|
||||
UpdateBuffers();
|
||||
}
|
||||
|
||||
outOffset = sampleOffset;
|
||||
outSize = std::min(maxSamples * audio::constant::ChannelCount, static_cast<int>(sampleBuffer.size() - sampleOffset));
|
||||
|
||||
output.playedSamplesCount += outSize / audio::constant::ChannelCount;
|
||||
sampleOffset += outSize;
|
||||
|
||||
if (sampleOffset == sampleBuffer.size()) {
|
||||
sampleOffset = 0;
|
||||
|
||||
if (currentBuffer.lastBuffer)
|
||||
playbackState = audio::AudioOutState::Paused;
|
||||
|
||||
if (!currentBuffer.looping)
|
||||
SetWaveBufferIndex(bufferIndex + 1);
|
||||
|
||||
output.playedWaveBuffersCount++;
|
||||
}
|
||||
|
||||
return sampleBuffer;
|
||||
}
|
||||
|
||||
void Voice::SetWaveBufferIndex(uint index) {
|
||||
bufferIndex = index & 3;
|
||||
bufferReload = true;
|
||||
}
|
||||
}
|
138
app/src/main/cpp/skyline/services/audren/voice.h
Normal file
138
app/src/main/cpp/skyline/services/audren/voice.h
Normal file
@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
#include <array>
|
||||
#include <audio.h>
|
||||
#include <audio/resampler.h>
|
||||
|
||||
namespace skyline::service::audren {
|
||||
/**
|
||||
* @brief This stores data for configuring a biquadratic filter
|
||||
*/
|
||||
struct BiquadFilter {
|
||||
u8 enable; //!< Whether to enable the filter
|
||||
u8 _pad0_;
|
||||
u16 b0; //!< The b0 constant
|
||||
u16 b1; //!< The b1 constant
|
||||
u16 b2; //!< The b2 constant
|
||||
u16 a1; //!< The a1 constant
|
||||
u16 a2; //!< The a2 constant
|
||||
};
|
||||
static_assert(sizeof(BiquadFilter) == 0xc);
|
||||
|
||||
/**
|
||||
* @brief This stores information of a wave buffer of samples
|
||||
*/
|
||||
struct WaveBuffer {
|
||||
u64 position; //!< The position of the wave buffer in guest memory
|
||||
u64 size; //!< The size of the wave buffer
|
||||
u32 firstSampleOffset; //!< The offset of the first sample in the buffer
|
||||
u32 lastSampleOffset; //!< The offset of the last sample in the buffer
|
||||
u8 looping; //!< Whether to loop the buffer
|
||||
u8 lastBuffer; //!< Whether this is the last populated buffer
|
||||
u16 _unk0_;
|
||||
u32 _unk1_;
|
||||
u64 adpcmLoopContextPosition; //!< The position of the ADPCM loop context data
|
||||
u64 adpcmLoopContextSize; //!< The size of the ADPCM loop context data
|
||||
u64 _unk2_;
|
||||
};
|
||||
static_assert(sizeof(WaveBuffer) == 0x38);
|
||||
|
||||
/**
|
||||
* @brief This is in input containing the configuration of a voice
|
||||
*/
|
||||
struct VoiceIn {
|
||||
u32 slot; //!< The slot of the voice
|
||||
u32 nodeId; //!< The node ID of the voice
|
||||
u8 firstUpdate; //!< Whether this voice is new
|
||||
u8 acquired; //!< Whether the sample is in use
|
||||
audio::AudioOutState playbackState; //!< The playback state
|
||||
audio::PcmFormat pcmFormat; //!< The sample format
|
||||
u32 sampleRate; //!< The sample rate
|
||||
u32 priority; //!< The priority for this voice
|
||||
u32 _unk0_;
|
||||
u32 channelCount; //!< The amount of channels the wave buffers contain
|
||||
float pitch; //!< The pitch to play wave data at
|
||||
float volume; //!< The volume to play wave data at
|
||||
std::array<BiquadFilter, 2> biquadFilters; //!< Biquadratic filter configurations
|
||||
u32 appendedWaveBuffersCount; //!< The amount of buffers appended
|
||||
u32 baseWaveBufferIndex; //!< The start index of the buffer to use
|
||||
u32 _unk1_;
|
||||
u64 adpcmCoeffsPosition; //!< The ADPCM coefficient position in wave data
|
||||
u64 adpcmCoeffsSize; //!< The size of the ADPCM coefficient configuration data
|
||||
u32 destination; //!< The voice destination address
|
||||
u32 _pad0_;
|
||||
std::array<WaveBuffer, 4> waveBuffers; //!< The wave buffers for the voice
|
||||
std::array<u32, 6> voiceChannelResourceIds; //!< A list of IDs corresponding to each channel
|
||||
u32 _pad1_[6];
|
||||
};
|
||||
static_assert(sizeof(VoiceIn) == 0x170);
|
||||
|
||||
|
||||
/**
|
||||
* @brief This is returned to inform the guest of the state of a voice
|
||||
*/
|
||||
struct VoiceOut {
|
||||
u64 playedSamplesCount; //!< The amount of samples played
|
||||
u32 playedWaveBuffersCount; //!< The amount of wave buffers fully played
|
||||
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; //!< The emulator state object
|
||||
std::array<WaveBuffer, 4> waveBuffers; //!< An array containing the state of all four wave buffers
|
||||
std::vector<i16> sampleBuffer; //!< A buffer containing processed sample data
|
||||
audio::Resampler resampler; //!< The resampler object used for changing the sample rate of a stream
|
||||
|
||||
bool acquired{false}; //!< If the voice is in use
|
||||
bool bufferReload{true}; //!< If the buffer needs to be updated
|
||||
int bufferIndex{}; //!< The index of the wave buffer currently in use
|
||||
int sampleOffset{}; //!< The offset in the sample data of the current wave buffer
|
||||
int sampleRate{}; //!< The sample rate of the sample data
|
||||
int channelCount{}; //!< The amount of channels in the sample data
|
||||
audio::AudioOutState playbackState{audio::AudioOutState::Stopped}; //!< The playback state of the voice
|
||||
audio::PcmFormat pcmFormat{audio::PcmFormat::Invalid}; //!< The PCM format used for guest audio data
|
||||
|
||||
/**
|
||||
* @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(uint index);
|
||||
|
||||
public:
|
||||
VoiceOut output{}; //!< The current output state
|
||||
float volume{}; //!< The volume of the voice
|
||||
|
||||
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(int maxSamples, int &outOffset, int &outSize);
|
||||
|
||||
/**
|
||||
* @return Whether the voice is currently playable
|
||||
*/
|
||||
inline bool Playable() {
|
||||
return acquired && playbackState == audio::AudioOutState::Started && waveBuffers[bufferIndex].size != 0;
|
||||
}
|
||||
};
|
||||
}
|
@ -38,6 +38,8 @@ namespace skyline::service {
|
||||
am_IAppletCommonFunctions,
|
||||
audout_u,
|
||||
audout_IAudioOut,
|
||||
IAudioRendererManager,
|
||||
IAudioRenderer,
|
||||
hid,
|
||||
hid_IAppletResource,
|
||||
time,
|
||||
@ -79,6 +81,7 @@ namespace skyline::service {
|
||||
{"am:IAppletCommonFunctions", Service::am_IAppletCommonFunctions},
|
||||
{"audout:u", Service::audout_u},
|
||||
{"audout:IAudioOut", Service::audout_IAudioOut},
|
||||
{"audren:u", Service::IAudioRendererManager},
|
||||
{"hid", Service::hid},
|
||||
{"hid:IAppletResource", Service::hid_IAppletResource},
|
||||
{"time:s", Service::time},
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "serviceman.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include <services/audren/IAudioRendererManager.h>
|
||||
#include "sm/sm.h"
|
||||
#include "set/sys.h"
|
||||
#include "apm/apm.h"
|
||||
@ -86,6 +87,9 @@ namespace skyline::service {
|
||||
case Service::audout_u:
|
||||
serviceObj = std::make_shared<audout::audoutU>(state, *this);
|
||||
break;
|
||||
case Service::IAudioRendererManager:
|
||||
serviceObj = std::make_shared<audren::IAudioRendererManager>(state, *this);
|
||||
break;
|
||||
case Service::hid:
|
||||
serviceObj = std::make_shared<hid::hid>(state, *this);
|
||||
break;
|
||||
|
@ -35,4 +35,7 @@
|
||||
<string name="docked_enabled">The system will emulate being in docked mode</string>
|
||||
<string name="theme">Theme</string>
|
||||
<string name="searching_roms">Searching for ROMs</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="audren_buffer_size">Audio Buffer Size</string>
|
||||
<string name="audren_buffer_desc">The size of the buffer used to store audio samples for ROMs using audren. A lower value will result in less latency but potentially increased audio stutter depending on the performance of the device</string>
|
||||
</resources>
|
||||
|
@ -54,4 +54,16 @@
|
||||
app:key="operation_mode"
|
||||
app:title="@string/use_docked" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="category_audio"
|
||||
android:title="@string/audio">
|
||||
<SeekBarPreference
|
||||
app:key="audren_buffer_size"
|
||||
app:title="@string/audren_buffer_size"
|
||||
app:summary="@string/audren_buffer_desc"
|
||||
app:showSeekBarValue="true"
|
||||
app:min="320"
|
||||
android:defaultValue="960"
|
||||
android:max="2880"/>
|
||||
</PreferenceCategory>
|
||||
</androidx.preference.PreferenceScreen>
|
||||
|
Loading…
Reference in New Issue
Block a user