forked from ShuriZma/suyu
audio_core: Implement audren_u audio playback.
This commit is contained in:
parent
1dee8ceda1
commit
b46df98e93
|
@ -1,6 +1,8 @@
|
||||||
add_library(audio_core STATIC
|
add_library(audio_core STATIC
|
||||||
audio_out.cpp
|
audio_out.cpp
|
||||||
audio_out.h
|
audio_out.h
|
||||||
|
audio_renderer.cpp
|
||||||
|
audio_renderer.h
|
||||||
buffer.h
|
buffer.h
|
||||||
cubeb_sink.cpp
|
cubeb_sink.cpp
|
||||||
cubeb_sink.h
|
cubeb_sink.h
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
// Copyright 2018 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "audio_core/audio_renderer.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace AudioCore {
|
||||||
|
|
||||||
|
constexpr u32 STREAM_SAMPLE_RATE{48000};
|
||||||
|
constexpr u32 STREAM_NUM_CHANNELS{2};
|
||||||
|
|
||||||
|
AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||||
|
Kernel::SharedPtr<Kernel::Event> buffer_event)
|
||||||
|
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
|
||||||
|
|
||||||
|
audio_core = std::make_unique<AudioCore::AudioOut>();
|
||||||
|
stream = audio_core->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||||
|
[=]() { buffer_event->Signal(); });
|
||||||
|
audio_core->StartStream(stream);
|
||||||
|
|
||||||
|
QueueMixedBuffer(0);
|
||||||
|
QueueMixedBuffer(1);
|
||||||
|
QueueMixedBuffer(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
|
||||||
|
// Copy UpdateDataHeader struct
|
||||||
|
UpdateDataHeader config{};
|
||||||
|
std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
|
||||||
|
u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
|
||||||
|
|
||||||
|
// Copy MemoryPoolInfo structs
|
||||||
|
std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
|
||||||
|
std::memcpy(mem_pool_info.data(),
|
||||||
|
input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
|
||||||
|
memory_pool_count * sizeof(MemoryPoolInfo));
|
||||||
|
|
||||||
|
// Copy VoiceInfo structs
|
||||||
|
size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size +
|
||||||
|
config.voice_resource_size};
|
||||||
|
for (auto& voice : voices) {
|
||||||
|
std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo));
|
||||||
|
offset += sizeof(VoiceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update voices
|
||||||
|
for (auto& voice : voices) {
|
||||||
|
voice.UpdateState();
|
||||||
|
if (!voice.GetInfo().is_in_use) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (voice.GetInfo().is_new) {
|
||||||
|
voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update memory pool state
|
||||||
|
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||||
|
for (size_t index = 0; index < memory_pool.size(); ++index) {
|
||||||
|
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||||
|
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||||
|
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||||
|
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release previous buffers and queue next ones for playback
|
||||||
|
ReleaseAndQueueBuffers();
|
||||||
|
|
||||||
|
// Copy output header
|
||||||
|
UpdateDataHeader response_data{worker_params};
|
||||||
|
std::vector<u8> output_params(response_data.total_size);
|
||||||
|
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
|
||||||
|
|
||||||
|
// Copy output memory pool entries
|
||||||
|
std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
|
||||||
|
response_data.memory_pools_size);
|
||||||
|
|
||||||
|
// Copy output voice status
|
||||||
|
size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
|
||||||
|
for (const auto& voice : voices) {
|
||||||
|
std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
|
||||||
|
sizeof(VoiceOutStatus));
|
||||||
|
voice_out_status_offset += sizeof(VoiceOutStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioRenderer::VoiceState::SetWaveIndex(size_t index) {
|
||||||
|
wave_index = index & 3;
|
||||||
|
is_refresh_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(size_t sample_count) {
|
||||||
|
if (!IsPlaying()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_refresh_pending) {
|
||||||
|
RefreshBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t max_size{samples.size() - offset};
|
||||||
|
const size_t dequeue_offset{offset};
|
||||||
|
size_t size{sample_count * STREAM_NUM_CHANNELS};
|
||||||
|
if (size > max_size) {
|
||||||
|
size = max_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
|
||||||
|
offset += size;
|
||||||
|
|
||||||
|
const auto& wave_buffer{info.wave_buffer[wave_index]};
|
||||||
|
if (offset == samples.size()) {
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
if (!wave_buffer.is_looping) {
|
||||||
|
SetWaveIndex(wave_index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
out_status.wave_buffer_consumed++;
|
||||||
|
|
||||||
|
if (wave_buffer.end_of_stream) {
|
||||||
|
info.play_state = PlayState::Paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioRenderer::VoiceState::UpdateState() {
|
||||||
|
if (is_in_use && !info.is_in_use) {
|
||||||
|
// No longer in use, reset state
|
||||||
|
is_refresh_pending = true;
|
||||||
|
wave_index = 0;
|
||||||
|
offset = 0;
|
||||||
|
out_status = {};
|
||||||
|
}
|
||||||
|
is_in_use = info.is_in_use;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioRenderer::VoiceState::RefreshBuffer() {
|
||||||
|
std::vector<s16> new_samples(info.wave_buffer[wave_index].buffer_sz / sizeof(s16));
|
||||||
|
Memory::ReadBlock(info.wave_buffer[wave_index].buffer_addr, new_samples.data(),
|
||||||
|
info.wave_buffer[wave_index].buffer_sz);
|
||||||
|
|
||||||
|
switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
|
||||||
|
case Codec::PcmFormat::Int16: {
|
||||||
|
// PCM16 is played as-is
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Codec::PcmFormat::Adpcm: {
|
||||||
|
// Decode ADPCM to PCM16
|
||||||
|
Codec::ADPCM_Coeff coeffs;
|
||||||
|
Memory::ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
|
||||||
|
new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
|
||||||
|
new_samples.size() * sizeof(s16), coeffs, adpcm_state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_CRITICAL(Audio, "Unimplemented sample_format={}", info.sample_format);
|
||||||
|
UNREACHABLE();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (info.channel_count) {
|
||||||
|
case 1:
|
||||||
|
// 1 channel is upsampled to 2 channel
|
||||||
|
samples.resize(new_samples.size() * 2);
|
||||||
|
for (size_t index = 0; index < new_samples.size(); ++index) {
|
||||||
|
samples[index * 2] = new_samples[index];
|
||||||
|
samples[index * 2 + 1] = new_samples[index];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: {
|
||||||
|
// 2 channel is played as is
|
||||||
|
samples = std::move(new_samples);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_CRITICAL(Audio, "Unimplemented channel_count={}", info.channel_count);
|
||||||
|
UNREACHABLE();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_refresh_pending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr s16 ClampToS16(s32 value) {
|
||||||
|
return static_cast<s16>(std::clamp(value, -32768, 32767));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||||
|
constexpr size_t BUFFER_SIZE{512};
|
||||||
|
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
|
||||||
|
|
||||||
|
for (auto& voice : voices) {
|
||||||
|
if (!voice.IsPlaying()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset{};
|
||||||
|
s64 samples_remaining{BUFFER_SIZE};
|
||||||
|
while (samples_remaining > 0) {
|
||||||
|
const std::vector<s16> samples{voice.DequeueSamples(samples_remaining)};
|
||||||
|
|
||||||
|
if (samples.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
samples_remaining -= samples.size();
|
||||||
|
|
||||||
|
for (const auto& sample : samples) {
|
||||||
|
const s32 buffer_sample{buffer[offset]};
|
||||||
|
buffer[offset++] =
|
||||||
|
ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audio_core->QueueBuffer(stream, tag, std::move(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||||
|
const auto released_buffers{audio_core->GetTagsAndReleaseBuffers(stream, 2)};
|
||||||
|
for (const auto& tag : released_buffers) {
|
||||||
|
QueueMixedBuffer(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioCore
|
|
@ -0,0 +1,206 @@
|
||||||
|
// Copyright 2018 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "audio_core/audio_out.h"
|
||||||
|
#include "audio_core/codec.h"
|
||||||
|
#include "audio_core/stream.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/swap.h"
|
||||||
|
#include "core/hle/kernel/event.h"
|
||||||
|
|
||||||
|
namespace AudioCore {
|
||||||
|
|
||||||
|
enum class PlayState : u8 {
|
||||||
|
Started = 0,
|
||||||
|
Stopped = 1,
|
||||||
|
Paused = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioRendererParameter {
|
||||||
|
u32_le sample_rate;
|
||||||
|
u32_le sample_count;
|
||||||
|
u32_le unknown_8;
|
||||||
|
u32_le unknown_c;
|
||||||
|
u32_le voice_count;
|
||||||
|
u32_le sink_count;
|
||||||
|
u32_le effect_count;
|
||||||
|
u32_le unknown_1c;
|
||||||
|
u8 unknown_20;
|
||||||
|
INSERT_PADDING_BYTES(3);
|
||||||
|
u32_le splitter_count;
|
||||||
|
u32_le unknown_2c;
|
||||||
|
INSERT_PADDING_WORDS(1);
|
||||||
|
u32_le revision;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||||
|
|
||||||
|
enum class MemoryPoolStates : u32 { // Should be LE
|
||||||
|
Invalid = 0x0,
|
||||||
|
Unknown = 0x1,
|
||||||
|
RequestDetach = 0x2,
|
||||||
|
Detached = 0x3,
|
||||||
|
RequestAttach = 0x4,
|
||||||
|
Attached = 0x5,
|
||||||
|
Released = 0x6,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MemoryPoolEntry {
|
||||||
|
MemoryPoolStates state;
|
||||||
|
u32_le unknown_4;
|
||||||
|
u32_le unknown_8;
|
||||||
|
u32_le unknown_c;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
|
||||||
|
|
||||||
|
struct MemoryPoolInfo {
|
||||||
|
u64_le pool_address;
|
||||||
|
u64_le pool_size;
|
||||||
|
MemoryPoolStates pool_state;
|
||||||
|
INSERT_PADDING_WORDS(3); // Unknown
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
|
||||||
|
struct BiquadFilter {
|
||||||
|
u8 enable;
|
||||||
|
INSERT_PADDING_BYTES(1);
|
||||||
|
std::array<s16_le, 3> numerator;
|
||||||
|
std::array<s16_le, 2> denominator;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
|
||||||
|
|
||||||
|
struct WaveBuffer {
|
||||||
|
u64_le buffer_addr;
|
||||||
|
u64_le buffer_sz;
|
||||||
|
s32_le start_sample_offset;
|
||||||
|
s32_le end_sample_offset;
|
||||||
|
u8 is_looping;
|
||||||
|
u8 end_of_stream;
|
||||||
|
u8 sent_to_server;
|
||||||
|
INSERT_PADDING_BYTES(5);
|
||||||
|
u64 context_addr;
|
||||||
|
u64 context_sz;
|
||||||
|
INSERT_PADDING_BYTES(8);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
|
||||||
|
|
||||||
|
struct VoiceInfo {
|
||||||
|
u32_le id;
|
||||||
|
u32_le node_id;
|
||||||
|
u8 is_new;
|
||||||
|
u8 is_in_use;
|
||||||
|
PlayState play_state;
|
||||||
|
u8 sample_format;
|
||||||
|
u32_le sample_rate;
|
||||||
|
u32_le priority;
|
||||||
|
u32_le sorting_order;
|
||||||
|
u32_le channel_count;
|
||||||
|
float_le pitch;
|
||||||
|
float_le volume;
|
||||||
|
std::array<BiquadFilter, 2> biquad_filter;
|
||||||
|
u32_le wave_buffer_count;
|
||||||
|
u32_le wave_buffer_head;
|
||||||
|
INSERT_PADDING_WORDS(1);
|
||||||
|
u64_le additional_params_addr;
|
||||||
|
u64_le additional_params_sz;
|
||||||
|
u32_le mix_id;
|
||||||
|
u32_le splitter_info_id;
|
||||||
|
std::array<WaveBuffer, 4> wave_buffer;
|
||||||
|
std::array<u32_le, 6> voice_channel_resource_ids;
|
||||||
|
INSERT_PADDING_BYTES(24);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
|
||||||
|
|
||||||
|
struct VoiceOutStatus {
|
||||||
|
u64_le played_sample_count;
|
||||||
|
u32_le wave_buffer_consumed;
|
||||||
|
u32_le voice_drops_count;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
|
||||||
|
|
||||||
|
struct UpdateDataHeader {
|
||||||
|
UpdateDataHeader() {}
|
||||||
|
|
||||||
|
explicit UpdateDataHeader(const AudioRendererParameter& config) {
|
||||||
|
revision = Common::MakeMagic('R', 'E', 'V', '4'); // 5.1.0 Revision
|
||||||
|
behavior_size = 0xb0;
|
||||||
|
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
|
||||||
|
voices_size = config.voice_count * 0x10;
|
||||||
|
voice_resource_size = 0x0;
|
||||||
|
effects_size = config.effect_count * 0x10;
|
||||||
|
mixes_size = 0x0;
|
||||||
|
sinks_size = config.sink_count * 0x20;
|
||||||
|
performance_manager_size = 0x10;
|
||||||
|
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
|
||||||
|
effects_size + sinks_size + performance_manager_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32_le revision;
|
||||||
|
u32_le behavior_size;
|
||||||
|
u32_le memory_pools_size;
|
||||||
|
u32_le voices_size;
|
||||||
|
u32_le voice_resource_size;
|
||||||
|
u32_le effects_size;
|
||||||
|
u32_le mixes_size;
|
||||||
|
u32_le sinks_size;
|
||||||
|
u32_le performance_manager_size;
|
||||||
|
INSERT_PADDING_WORDS(6);
|
||||||
|
u32_le total_size;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
|
||||||
|
|
||||||
|
class AudioRenderer {
|
||||||
|
public:
|
||||||
|
AudioRenderer(AudioRendererParameter params, Kernel::SharedPtr<Kernel::Event> buffer_event);
|
||||||
|
std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params);
|
||||||
|
void QueueMixedBuffer(Buffer::Tag tag);
|
||||||
|
void ReleaseAndQueueBuffers();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class VoiceState {
|
||||||
|
public:
|
||||||
|
bool IsPlaying() const {
|
||||||
|
return is_in_use && info.play_state == PlayState::Started;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VoiceOutStatus& GetOutStatus() const {
|
||||||
|
return out_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VoiceInfo& GetInfo() const {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoiceInfo& Info() {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetWaveIndex(size_t index);
|
||||||
|
std::vector<s16> DequeueSamples(size_t sample_count);
|
||||||
|
void UpdateState();
|
||||||
|
void RefreshBuffer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool is_in_use{};
|
||||||
|
bool is_refresh_pending{};
|
||||||
|
size_t wave_index{};
|
||||||
|
size_t offset{};
|
||||||
|
Codec::ADPCMState adpcm_state{};
|
||||||
|
std::vector<s16> samples;
|
||||||
|
VoiceOutStatus out_status{};
|
||||||
|
VoiceInfo info{};
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioRendererParameter worker_params;
|
||||||
|
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||||
|
std::vector<VoiceState> voices;
|
||||||
|
std::unique_ptr<AudioCore::AudioOut> audio_core;
|
||||||
|
AudioCore::StreamPtr stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace AudioCore
|
|
@ -15,13 +15,10 @@
|
||||||
|
|
||||||
namespace Service::Audio {
|
namespace Service::Audio {
|
||||||
|
|
||||||
/// TODO(bunnei): Find a proper value for the audio_ticks
|
|
||||||
constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 200)};
|
|
||||||
|
|
||||||
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
|
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
|
||||||
public:
|
public:
|
||||||
explicit IAudioRenderer(AudioRendererParameter audren_params)
|
explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params)
|
||||||
: ServiceFramework("IAudioRenderer"), worker_params(audren_params) {
|
: ServiceFramework("IAudioRenderer") {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, nullptr, "GetAudioRendererSampleRate"},
|
{0, nullptr, "GetAudioRendererSampleRate"},
|
||||||
{1, nullptr, "GetAudioRendererSampleCount"},
|
{1, nullptr, "GetAudioRendererSampleCount"},
|
||||||
|
@ -39,21 +36,8 @@ public:
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
|
|
||||||
system_event =
|
system_event =
|
||||||
Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioRenderer:SystemEvent");
|
Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent");
|
||||||
|
renderer = std::make_unique<AudioCore::AudioRenderer>(audren_params, system_event);
|
||||||
// Register event callback to update the Audio Buffer
|
|
||||||
audio_event = CoreTiming::RegisterEvent(
|
|
||||||
"IAudioRenderer::UpdateAudioCallback", [this](u64 userdata, int cycles_late) {
|
|
||||||
UpdateAudioCallback();
|
|
||||||
CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start the audio event
|
|
||||||
CoreTiming::ScheduleEvent(audio_ticks, audio_event);
|
|
||||||
voice_status_list.resize(worker_params.voice_count);
|
|
||||||
}
|
|
||||||
~IAudioRenderer() {
|
|
||||||
CoreTiming::UnscheduleEvent(audio_event, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -62,60 +46,9 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
|
void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
|
||||||
UpdateDataHeader config{};
|
ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer()));
|
||||||
auto buf = ctx.ReadBuffer();
|
|
||||||
std::memcpy(&config, buf.data(), sizeof(UpdateDataHeader));
|
|
||||||
u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
|
|
||||||
|
|
||||||
std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
|
|
||||||
std::memcpy(mem_pool_info.data(),
|
|
||||||
buf.data() + sizeof(UpdateDataHeader) + config.behavior_size,
|
|
||||||
memory_pool_count * sizeof(MemoryPoolInfo));
|
|
||||||
|
|
||||||
std::vector<VoiceInfo> voice_info(worker_params.voice_count);
|
|
||||||
std::memcpy(voice_info.data(),
|
|
||||||
buf.data() + sizeof(UpdateDataHeader) + config.behavior_size +
|
|
||||||
config.memory_pools_size + config.voice_resource_size,
|
|
||||||
worker_params.voice_count * sizeof(VoiceInfo));
|
|
||||||
|
|
||||||
UpdateDataHeader response_data{worker_params};
|
|
||||||
|
|
||||||
ASSERT(ctx.GetWriteBufferSize() == response_data.total_size);
|
|
||||||
|
|
||||||
std::vector<u8> output(response_data.total_size);
|
|
||||||
std::memcpy(output.data(), &response_data, sizeof(UpdateDataHeader));
|
|
||||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
|
||||||
for (unsigned i = 0; i < memory_pool.size(); i++) {
|
|
||||||
if (mem_pool_info[i].pool_state == MemoryPoolStates::RequestAttach)
|
|
||||||
memory_pool[i].state = MemoryPoolStates::Attached;
|
|
||||||
else if (mem_pool_info[i].pool_state == MemoryPoolStates::RequestDetach)
|
|
||||||
memory_pool[i].state = MemoryPoolStates::Detached;
|
|
||||||
}
|
|
||||||
std::memcpy(output.data() + sizeof(UpdateDataHeader), memory_pool.data(),
|
|
||||||
response_data.memory_pools_size);
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < voice_info.size(); i++) {
|
|
||||||
if (voice_info[i].is_new) {
|
|
||||||
voice_status_list[i].played_sample_count = 0;
|
|
||||||
voice_status_list[i].wave_buffer_consumed = 0;
|
|
||||||
} else if (voice_info[i].play_state == (u8)PlayStates::Started) {
|
|
||||||
for (u32 buff_idx = 0; buff_idx < voice_info[i].wave_buffer_count; buff_idx++) {
|
|
||||||
voice_status_list[i].played_sample_count +=
|
|
||||||
(voice_info[i].wave_buffer[buff_idx].end_sample_offset -
|
|
||||||
voice_info[i].wave_buffer[buff_idx].start_sample_offset) /
|
|
||||||
2;
|
|
||||||
voice_status_list[i].wave_buffer_consumed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::memcpy(output.data() + sizeof(UpdateDataHeader) + response_data.memory_pools_size,
|
|
||||||
voice_status_list.data(), response_data.voices_size);
|
|
||||||
|
|
||||||
ctx.WriteBuffer(output);
|
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
|
||||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,8 +69,6 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
|
void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
|
||||||
// system_event->Signal();
|
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.PushCopyObjects(system_event);
|
rb.PushCopyObjects(system_event);
|
||||||
|
@ -145,131 +76,8 @@ private:
|
||||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MemoryPoolStates : u32 { // Should be LE
|
|
||||||
Invalid = 0x0,
|
|
||||||
Unknown = 0x1,
|
|
||||||
RequestDetach = 0x2,
|
|
||||||
Detached = 0x3,
|
|
||||||
RequestAttach = 0x4,
|
|
||||||
Attached = 0x5,
|
|
||||||
Released = 0x6,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class PlayStates : u8 {
|
|
||||||
Started = 0,
|
|
||||||
Stopped = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MemoryPoolEntry {
|
|
||||||
MemoryPoolStates state;
|
|
||||||
u32_le unknown_4;
|
|
||||||
u32_le unknown_8;
|
|
||||||
u32_le unknown_c;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
|
|
||||||
|
|
||||||
struct MemoryPoolInfo {
|
|
||||||
u64_le pool_address;
|
|
||||||
u64_le pool_size;
|
|
||||||
MemoryPoolStates pool_state;
|
|
||||||
INSERT_PADDING_WORDS(3); // Unknown
|
|
||||||
};
|
|
||||||
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
|
|
||||||
|
|
||||||
struct UpdateDataHeader {
|
|
||||||
UpdateDataHeader() {}
|
|
||||||
|
|
||||||
explicit UpdateDataHeader(const AudioRendererParameter& config) {
|
|
||||||
revision = Common::MakeMagic('R', 'E', 'V', '4'); // 5.1.0 Revision
|
|
||||||
behavior_size = 0xb0;
|
|
||||||
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
|
|
||||||
voices_size = config.voice_count * 0x10;
|
|
||||||
voice_resource_size = 0x0;
|
|
||||||
effects_size = config.effect_count * 0x10;
|
|
||||||
mixes_size = 0x0;
|
|
||||||
sinks_size = config.sink_count * 0x20;
|
|
||||||
performance_manager_size = 0x10;
|
|
||||||
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size +
|
|
||||||
voices_size + effects_size + sinks_size + performance_manager_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32_le revision;
|
|
||||||
u32_le behavior_size;
|
|
||||||
u32_le memory_pools_size;
|
|
||||||
u32_le voices_size;
|
|
||||||
u32_le voice_resource_size;
|
|
||||||
u32_le effects_size;
|
|
||||||
u32_le mixes_size;
|
|
||||||
u32_le sinks_size;
|
|
||||||
u32_le performance_manager_size;
|
|
||||||
INSERT_PADDING_WORDS(6);
|
|
||||||
u32_le total_size;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
|
|
||||||
|
|
||||||
struct BiquadFilter {
|
|
||||||
u8 enable;
|
|
||||||
INSERT_PADDING_BYTES(1);
|
|
||||||
s16_le numerator[3];
|
|
||||||
s16_le denominator[2];
|
|
||||||
};
|
|
||||||
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
|
|
||||||
|
|
||||||
struct WaveBuffer {
|
|
||||||
u64_le buffer_addr;
|
|
||||||
u64_le buffer_sz;
|
|
||||||
s32_le start_sample_offset;
|
|
||||||
s32_le end_sample_offset;
|
|
||||||
u8 loop;
|
|
||||||
u8 end_of_stream;
|
|
||||||
u8 sent_to_server;
|
|
||||||
INSERT_PADDING_BYTES(5);
|
|
||||||
u64 context_addr;
|
|
||||||
u64 context_sz;
|
|
||||||
INSERT_PADDING_BYTES(8);
|
|
||||||
};
|
|
||||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
|
|
||||||
|
|
||||||
struct VoiceInfo {
|
|
||||||
u32_le id;
|
|
||||||
u32_le node_id;
|
|
||||||
u8 is_new;
|
|
||||||
u8 is_in_use;
|
|
||||||
u8 play_state;
|
|
||||||
u8 sample_format;
|
|
||||||
u32_le sample_rate;
|
|
||||||
u32_le priority;
|
|
||||||
u32_le sorting_order;
|
|
||||||
u32_le channel_count;
|
|
||||||
float_le pitch;
|
|
||||||
float_le volume;
|
|
||||||
BiquadFilter biquad_filter[2];
|
|
||||||
u32_le wave_buffer_count;
|
|
||||||
u16_le wave_buffer_head;
|
|
||||||
INSERT_PADDING_BYTES(6);
|
|
||||||
u64_le additional_params_addr;
|
|
||||||
u64_le additional_params_sz;
|
|
||||||
u32_le mix_id;
|
|
||||||
u32_le splitter_info_id;
|
|
||||||
WaveBuffer wave_buffer[4];
|
|
||||||
u32_le voice_channel_resource_ids[6];
|
|
||||||
INSERT_PADDING_BYTES(24);
|
|
||||||
};
|
|
||||||
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
|
|
||||||
|
|
||||||
struct VoiceOutStatus {
|
|
||||||
u64_le played_sample_count;
|
|
||||||
u32_le wave_buffer_consumed;
|
|
||||||
INSERT_PADDING_WORDS(1);
|
|
||||||
};
|
|
||||||
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
|
|
||||||
|
|
||||||
/// This is used to trigger the audio event callback.
|
|
||||||
CoreTiming::EventType* audio_event;
|
|
||||||
|
|
||||||
Kernel::SharedPtr<Kernel::Event> system_event;
|
Kernel::SharedPtr<Kernel::Event> system_event;
|
||||||
AudioRendererParameter worker_params;
|
std::unique_ptr<AudioCore::AudioRenderer> renderer;
|
||||||
std::vector<VoiceOutStatus> voice_status_list;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
|
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
|
||||||
|
@ -368,7 +176,7 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") {
|
||||||
|
|
||||||
void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
|
void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
auto params = rp.PopRaw<AudioRendererParameter>();
|
auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
@ -379,7 +187,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
||||||
void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
auto params = rp.PopRaw<AudioRendererParameter>();
|
auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
|
||||||
|
|
||||||
u64 buffer_sz = Common::AlignUp(4 * params.unknown_8, 0x40);
|
u64 buffer_sz = Common::AlignUp(4 * params.unknown_8, 0x40);
|
||||||
buffer_sz += params.unknown_c * 1024;
|
buffer_sz += params.unknown_c * 1024;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "audio_core/audio_renderer.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
@ -12,24 +13,6 @@ class HLERequestContext;
|
||||||
|
|
||||||
namespace Service::Audio {
|
namespace Service::Audio {
|
||||||
|
|
||||||
struct AudioRendererParameter {
|
|
||||||
u32_le sample_rate;
|
|
||||||
u32_le sample_count;
|
|
||||||
u32_le unknown_8;
|
|
||||||
u32_le unknown_c;
|
|
||||||
u32_le voice_count;
|
|
||||||
u32_le sink_count;
|
|
||||||
u32_le effect_count;
|
|
||||||
u32_le unknown_1c;
|
|
||||||
u8 unknown_20;
|
|
||||||
INSERT_PADDING_BYTES(3);
|
|
||||||
u32_le splitter_count;
|
|
||||||
u32_le unknown_2c;
|
|
||||||
INSERT_PADDING_WORDS(1);
|
|
||||||
u32_le revision;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
|
||||||
|
|
||||||
class AudRenU final : public ServiceFramework<AudRenU> {
|
class AudRenU final : public ServiceFramework<AudRenU> {
|
||||||
public:
|
public:
|
||||||
explicit AudRenU();
|
explicit AudRenU();
|
||||||
|
|
Loading…
Reference in New Issue