diff options
Diffstat (limited to 'src/audio_core')
-rw-r--r-- | src/audio_core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/audio_core/audio_renderer.cpp | 234 | ||||
-rw-r--r-- | src/audio_core/audio_renderer.h | 206 |
3 files changed, 442 insertions, 0 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 05a61b5cd..827ab0ac7 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(audio_core STATIC audio_out.cpp audio_out.h + audio_renderer.cpp + audio_renderer.h buffer.h cubeb_sink.cpp cubeb_sink.h diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp new file mode 100644 index 000000000..282f345c5 --- /dev/null +++ b/src/audio_core/audio_renderer.cpp @@ -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 diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h new file mode 100644 index 000000000..6950a4681 --- /dev/null +++ b/src/audio_core/audio_renderer.h @@ -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 |