5 changed files with 451 additions and 218 deletions
-
2src/audio_core/CMakeLists.txt
-
234src/audio_core/audio_renderer.cpp
-
206src/audio_core/audio_renderer.h
-
208src/core/hle/service/audio/audren_u.cpp
-
19src/core/hle/service/audio/audren_u.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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue