From 0857d6a3dbcaeb30b51326419fb56d9b543601f1 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sun, 20 Jun 2021 15:23:16 +0100 Subject: [PATCH 1/2] Decouple audio processing and run at variable rate Currently, processing of audio samples is called from AudioRenderer's Update method, using a fixed 4 buffers to process the given samples. Games call Update at variable rates, depending on framerate and/or sample count, which causes inconsistency in audio processing. From what I've seen, 60 FPS games update every ~0.004s, but 30 FPS/160 sample games update somewhere between 0.02 and 0.04, 5-10x slower. Not enough samples get fed to the backend, leading to a lot of audio skipping. This PR seeks to address this by de-coupling the audio consumption and the audio update. Update remains the same without calling for buffer queuing, and the consume now schedules itself to run based on the sample rate and count. --- src/audio_core/audio_renderer.cpp | 196 ++++++++++++++---------- src/audio_core/audio_renderer.h | 6 + src/core/hle/service/audio/audren_u.cpp | 10 +- 3 files changed, 124 insertions(+), 88 deletions(-) diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 80ffddb104..0757cd8040 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -12,6 +12,7 @@ #include "audio_core/voice_context.h" #include "common/logging/log.h" #include "common/settings.h" +#include "core/core_timing.h" #include "core/memory.h" namespace { @@ -68,7 +69,9 @@ namespace { } // namespace namespace AudioCore { -AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, +constexpr s32 NUM_BUFFERS = 2; + +AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_, AudioCommon::AudioRendererParameter params, Stream::ReleaseCallback&& release_callback, std::size_t instance_number) @@ -77,7 +80,8 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory sink_context(params.sink_count), splitter_context(), voices(params.voice_count), memory{memory_}, command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, - memory) { + memory), + core_timing{core_timing_} { behavior_info.SetUserRevision(params.revision); splitter_context.Initialize(behavior_info, params.splitter_count, params.num_splitter_send_channels); @@ -86,16 +90,27 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory stream = audio_out->OpenStream( core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); - audio_out->StartStream(stream); - - QueueMixedBuffer(0); - QueueMixedBuffer(1); - QueueMixedBuffer(2); - QueueMixedBuffer(3); + process_event = Core::Timing::CreateEvent( + fmt::format("AudioRenderer-Instance{}-Process", instance_number), + [this](std::uintptr_t, std::chrono::nanoseconds) { ReleaseAndQueueBuffers(); }); + for (s32 i = 0; i < NUM_BUFFERS; ++i) { + QueueMixedBuffer(i); + } } AudioRenderer::~AudioRenderer() = default; +ResultCode AudioRenderer::Start() { + audio_out->StartStream(stream); + ReleaseAndQueueBuffers(); + return ResultSuccess; +} + +ResultCode AudioRenderer::Stop() { + audio_out->StopStream(stream); + return ResultSuccess; +} + u32 AudioRenderer::GetSampleRate() const { return worker_params.sample_rate; } @@ -114,89 +129,88 @@ Stream::State AudioRenderer::GetStreamState() const { ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector& input_params, std::vector& output_params) { + { + std::scoped_lock lock{mutex}; + InfoUpdater info_updater{input_params, output_params, behavior_info}; - InfoUpdater info_updater{input_params, output_params, behavior_info}; + if (!info_updater.UpdateBehaviorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update behavior info input parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } - if (!info_updater.UpdateBehaviorInfo(behavior_info)) { - LOG_ERROR(Audio, "Failed to update behavior info input parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + if (!info_updater.UpdateMemoryPools(memory_pool_info)) { + LOG_ERROR(Audio, "Failed to update memory pool parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } - if (!info_updater.UpdateMemoryPools(memory_pool_info)) { - LOG_ERROR(Audio, "Failed to update memory pool parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + if (!info_updater.UpdateVoiceChannelResources(voice_context)) { + LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } - if (!info_updater.UpdateVoiceChannelResources(voice_context)) { - LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { + LOG_ERROR(Audio, "Failed to update voice parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } - if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { - LOG_ERROR(Audio, "Failed to update voice parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + // TODO(ogniK): Deal with stopped audio renderer but updates still taking place + if (!info_updater.UpdateEffects(effect_context, true)) { + LOG_ERROR(Audio, "Failed to update effect parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } - // TODO(ogniK): Deal with stopped audio renderer but updates still taking place - if (!info_updater.UpdateEffects(effect_context, true)) { - LOG_ERROR(Audio, "Failed to update effect parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + if (behavior_info.IsSplitterSupported()) { + if (!info_updater.UpdateSplitterInfo(splitter_context)) { + LOG_ERROR(Audio, "Failed to update splitter parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } - if (behavior_info.IsSplitterSupported()) { - if (!info_updater.UpdateSplitterInfo(splitter_context)) { - LOG_ERROR(Audio, "Failed to update splitter parameters"); + const auto mix_result = info_updater.UpdateMixes( + mix_context, worker_params.mix_buffer_count, splitter_context, effect_context); + + if (mix_result.IsError()) { + LOG_ERROR(Audio, "Failed to update mix parameters"); + return mix_result; + } + + // TODO(ogniK): Sinks + if (!info_updater.UpdateSinks(sink_context)) { + LOG_ERROR(Audio, "Failed to update sink parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + // TODO(ogniK): Performance buffer + if (!info_updater.UpdatePerformanceBuffer()) { + LOG_ERROR(Audio, "Failed to update performance buffer parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateErrorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update error info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (behavior_info.IsElapsedFrameCountSupported()) { + if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { + LOG_ERROR(Audio, "Failed to update renderer info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } + // TODO(ogniK): Statistics + + if (!info_updater.WriteOutputHeader()) { + LOG_ERROR(Audio, "Failed to write output header"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + // TODO(ogniK): Check when all sections are implemented + + if (!info_updater.CheckConsumedSize()) { + LOG_ERROR(Audio, "Audio buffers were not consumed!"); return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } } - - const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, - splitter_context, effect_context); - - if (mix_result.IsError()) { - LOG_ERROR(Audio, "Failed to update mix parameters"); - return mix_result; - } - - // TODO(ogniK): Sinks - if (!info_updater.UpdateSinks(sink_context)) { - LOG_ERROR(Audio, "Failed to update sink parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - // TODO(ogniK): Performance buffer - if (!info_updater.UpdatePerformanceBuffer()) { - LOG_ERROR(Audio, "Failed to update performance buffer parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - if (!info_updater.UpdateErrorInfo(behavior_info)) { - LOG_ERROR(Audio, "Failed to update error info"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - if (behavior_info.IsElapsedFrameCountSupported()) { - if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { - LOG_ERROR(Audio, "Failed to update renderer info"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - } - // TODO(ogniK): Statistics - - if (!info_updater.WriteOutputHeader()) { - LOG_ERROR(Audio, "Failed to write output header"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - // TODO(ogniK): Check when all sections are implemented - - if (!info_updater.CheckConsumedSize()) { - LOG_ERROR(Audio, "Audio buffers were not consumed!"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - ReleaseAndQueueBuffers(); - return ResultSuccess; } @@ -315,10 +329,24 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { } void AudioRenderer::ReleaseAndQueueBuffers() { - const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; - for (const auto& tag : released_buffers) { - QueueMixedBuffer(tag); + if (!stream->IsPlaying()) { + return; } + + { + std::scoped_lock lock{mutex}; + const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; + for (const auto& tag : released_buffers) { + QueueMixedBuffer(tag); + } + } + + const f32 sample_rate = static_cast(GetSampleRate()); + const f32 sample_count = static_cast(GetSampleCount()); + const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240)); + const s32 ms = (1000 / static_cast(consume_rate)) - 1; + const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1)); + core_timing.ScheduleEvent(next_event_time, process_event, {}); } } // namespace AudioCore diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index 18567f6188..88fdd13dda 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "audio_core/behavior_info.h" @@ -45,6 +46,8 @@ public: [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector& input_params, std::vector& output_params); + [[nodiscard]] ResultCode Start(); + [[nodiscard]] ResultCode Stop(); void QueueMixedBuffer(Buffer::Tag tag); void ReleaseAndQueueBuffers(); [[nodiscard]] u32 GetSampleRate() const; @@ -68,6 +71,9 @@ private: Core::Memory::Memory& memory; CommandGenerator command_generator; std::size_t elapsed_frame_count{}; + Core::Timing::CoreTiming& core_timing; + std::shared_ptr process_event; + std::mutex mutex; }; } // namespace AudioCore diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 800feba6e8..feb5150e16 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -110,17 +110,19 @@ private: void Start(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; + const auto result = renderer->Start(); - rb.Push(ResultSuccess); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } void Stop(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; + const auto result = renderer->Stop(); - rb.Push(ResultSuccess); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } void QuerySystemEvent(Kernel::HLERequestContext& ctx) { From b455043e45737345fe73d118947db1684bd248ea Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sat, 26 Jun 2021 23:04:40 +0100 Subject: [PATCH 2/2] Fix XC2/VOEZ crashing, add audio looping and a few misc fixes --- src/audio_core/audio_renderer.cpp | 148 ++++++++++++------------ src/audio_core/command_generator.cpp | 76 ++++++------ src/audio_core/command_generator.h | 8 +- src/audio_core/info_updater.cpp | 3 - src/audio_core/voice_context.cpp | 88 ++++++++++---- src/audio_core/voice_context.h | 13 ++- src/core/hle/service/audio/audren_u.cpp | 2 +- 7 files changed, 197 insertions(+), 141 deletions(-) diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 0757cd8040..ccd5ca6cc9 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -129,87 +129,85 @@ Stream::State AudioRenderer::GetStreamState() const { ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector& input_params, std::vector& output_params) { - { - std::scoped_lock lock{mutex}; - InfoUpdater info_updater{input_params, output_params, behavior_info}; + std::scoped_lock lock{mutex}; + InfoUpdater info_updater{input_params, output_params, behavior_info}; - if (!info_updater.UpdateBehaviorInfo(behavior_info)) { - LOG_ERROR(Audio, "Failed to update behavior info input parameters"); + if (!info_updater.UpdateBehaviorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update behavior info input parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateMemoryPools(memory_pool_info)) { + LOG_ERROR(Audio, "Failed to update memory pool parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateVoiceChannelResources(voice_context)) { + LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { + LOG_ERROR(Audio, "Failed to update voice parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + // TODO(ogniK): Deal with stopped audio renderer but updates still taking place + if (!info_updater.UpdateEffects(effect_context, true)) { + LOG_ERROR(Audio, "Failed to update effect parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (behavior_info.IsSplitterSupported()) { + if (!info_updater.UpdateSplitterInfo(splitter_context)) { + LOG_ERROR(Audio, "Failed to update splitter parameters"); return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } + } - if (!info_updater.UpdateMemoryPools(memory_pool_info)) { - LOG_ERROR(Audio, "Failed to update memory pool parameters"); + const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, + splitter_context, effect_context); + + if (mix_result.IsError()) { + LOG_ERROR(Audio, "Failed to update mix parameters"); + return mix_result; + } + + // TODO(ogniK): Sinks + if (!info_updater.UpdateSinks(sink_context)) { + LOG_ERROR(Audio, "Failed to update sink parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + // TODO(ogniK): Performance buffer + if (!info_updater.UpdatePerformanceBuffer()) { + LOG_ERROR(Audio, "Failed to update performance buffer parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateErrorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update error info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (behavior_info.IsElapsedFrameCountSupported()) { + if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { + LOG_ERROR(Audio, "Failed to update renderer info"); return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } + } + // TODO(ogniK): Statistics - if (!info_updater.UpdateVoiceChannelResources(voice_context)) { - LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + if (!info_updater.WriteOutputHeader()) { + LOG_ERROR(Audio, "Failed to write output header"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } - if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { - LOG_ERROR(Audio, "Failed to update voice parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + // TODO(ogniK): Check when all sections are implemented - // TODO(ogniK): Deal with stopped audio renderer but updates still taking place - if (!info_updater.UpdateEffects(effect_context, true)) { - LOG_ERROR(Audio, "Failed to update effect parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - if (behavior_info.IsSplitterSupported()) { - if (!info_updater.UpdateSplitterInfo(splitter_context)) { - LOG_ERROR(Audio, "Failed to update splitter parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - } - - const auto mix_result = info_updater.UpdateMixes( - mix_context, worker_params.mix_buffer_count, splitter_context, effect_context); - - if (mix_result.IsError()) { - LOG_ERROR(Audio, "Failed to update mix parameters"); - return mix_result; - } - - // TODO(ogniK): Sinks - if (!info_updater.UpdateSinks(sink_context)) { - LOG_ERROR(Audio, "Failed to update sink parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - // TODO(ogniK): Performance buffer - if (!info_updater.UpdatePerformanceBuffer()) { - LOG_ERROR(Audio, "Failed to update performance buffer parameters"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - if (!info_updater.UpdateErrorInfo(behavior_info)) { - LOG_ERROR(Audio, "Failed to update error info"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - if (behavior_info.IsElapsedFrameCountSupported()) { - if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { - LOG_ERROR(Audio, "Failed to update renderer info"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - } - // TODO(ogniK): Statistics - - if (!info_updater.WriteOutputHeader()) { - LOG_ERROR(Audio, "Failed to write output header"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } - - // TODO(ogniK): Check when all sections are implemented - - if (!info_updater.CheckConsumedSize()) { - LOG_ERROR(Audio, "Audio buffers were not consumed!"); - return AudioCommon::Audren::ERR_INVALID_PARAMETERS; - } + if (!info_updater.CheckConsumedSize()) { + LOG_ERROR(Audio, "Audio buffers were not consumed!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } return ResultSuccess; } @@ -234,10 +232,8 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { command_generator.PostCommand(); // Base sample size std::size_t BUFFER_SIZE{worker_params.sample_count}; - // Samples - std::vector buffer(BUFFER_SIZE * stream->GetNumChannels()); - // Make sure to clear our samples - std::memset(buffer.data(), 0, buffer.size() * sizeof(s16)); + // Samples, making sure to clear + std::vector buffer(BUFFER_SIZE * stream->GetNumChannels(), 0); if (sink_context.InUse()) { const auto stream_channel_count = stream->GetNumChannels(); diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 437cc5ccd0..27437f1ea4 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -795,7 +795,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta state.lowpass_1 = 0.0f; } else { const auto a = 1.0f - hf_gain; - const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / + const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference / static_cast(info.sample_rate))); const auto c = std::sqrt(b * b - 4.0f * a * a); @@ -843,7 +843,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta } const auto max_early_delay = state.early_delay_line.GetMaxDelay(); - const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); + const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f); for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { const auto length = AudioCommon::CalculateDelaySamples( sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); @@ -1004,7 +1004,8 @@ void CommandGenerator::GenerateFinalMixCommand() { } s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 sample_count, s32 channel, std::size_t mix_offset) { + s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, + s32 channel, std::size_t mix_offset) { const auto& in_params = voice_info.GetInParams(); const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; if (wave_buffer.buffer_address == 0) { @@ -1013,14 +1014,12 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s if (wave_buffer.buffer_size == 0) { return 0; } - if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + if (sample_end_offset < sample_start_offset) { return 0; } - const auto samples_remaining = - (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; const auto start_offset = - ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) * - sizeof(s16); + ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(s16); const auto buffer_pos = wave_buffer.buffer_address + start_offset; const auto samples_processed = std::min(sample_count, samples_remaining); @@ -1044,8 +1043,8 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s } s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 sample_count, [[maybe_unused]] s32 channel, - std::size_t mix_offset) { + s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, + [[maybe_unused]] s32 channel, std::size_t mix_offset) { const auto& in_params = voice_info.GetInParams(); const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; if (wave_buffer.buffer_address == 0) { @@ -1054,7 +1053,7 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s if (wave_buffer.buffer_size == 0) { return 0; } - if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + if (sample_end_offset < sample_start_offset) { return 0; } @@ -1079,10 +1078,9 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s s32 coef1 = coeffs[idx * 2]; s32 coef2 = coeffs[idx * 2 + 1]; - const auto samples_remaining = - (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; const auto samples_processed = std::min(sample_count, samples_remaining); - const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset; + const auto sample_pos = dsp_state.offset + sample_start_offset; const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + @@ -1210,9 +1208,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o } std::size_t temp_mix_offset{}; - bool is_buffer_completed{false}; auto samples_remaining = sample_count; - while (samples_remaining > 0 && !is_buffer_completed) { + while (samples_remaining > 0) { const auto samples_to_output = std::min(samples_remaining, min_required_samples); const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; @@ -1229,24 +1226,38 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; // No more data can be read if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { - is_buffer_completed = true; break; } if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { - // TODO(ogniK): ADPCM loop context + memory.ReadBlock(wave_buffer.context_address, &dsp_state.context, + sizeof(ADPCMContext)); + } + + s32 samples_offset_start; + s32 samples_offset_end; + if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 && + wave_buffer.loop_end_sample != 0 && + wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) { + samples_offset_start = wave_buffer.loop_start_sample; + samples_offset_end = wave_buffer.loop_end_sample; + } else { + samples_offset_start = wave_buffer.start_sample_offset; + samples_offset_end = wave_buffer.end_sample_offset; } s32 samples_decoded{0}; switch (in_params.sample_format) { case SampleFormat::Pcm16: - samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read, - channel, temp_mix_offset); + samples_decoded = + DecodePcm16(voice_info, dsp_state, samples_offset_start, samples_offset_end, + samples_to_read - samples_read, channel, temp_mix_offset); break; case SampleFormat::Adpcm: - samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read, - channel, temp_mix_offset); + samples_decoded = + DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, + samples_to_read - samples_read, channel, temp_mix_offset); break; default: UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); @@ -1257,15 +1268,19 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o dsp_state.offset += samples_decoded; dsp_state.played_sample_count += samples_decoded; - if (dsp_state.offset >= - (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) || + if (dsp_state.offset >= (samples_offset_end - samples_offset_start) || samples_decoded == 0) { // Reset our sample offset dsp_state.offset = 0; if (wave_buffer.is_looping) { - if (samples_decoded == 0) { + dsp_state.loop_count++; + if (wave_buffer.loop_count > 0 && + (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) { // End of our buffer - is_buffer_completed = true; + voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); + } + + if (samples_decoded == 0) { break; } @@ -1273,15 +1288,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o dsp_state.played_sample_count = 0; } } else { - // Update our wave buffer states - dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; - dsp_state.wave_buffer_consumed++; - dsp_state.wave_buffer_index = - (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; - if (wave_buffer.end_of_stream) { - dsp_state.played_sample_count = 0; - } + voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); } } } diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 2ebb755b04..673e4fbef9 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -86,10 +86,10 @@ private: std::vector& work_buffer); void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); // DSP Code - s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, - s32 channel, std::size_t mix_offset); - s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, - s32 channel, std::size_t mix_offset); + s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, + s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); + s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, + s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state, s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id); diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp index 4a5b1b4abe..9b4ca1851a 100644 --- a/src/audio_core/info_updater.cpp +++ b/src/audio_core/info_updater.cpp @@ -189,9 +189,6 @@ bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, if (voice_in_params.is_new) { // Default our values for our voice voice_info.Initialize(); - if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) { - continue; - } // Zero out our voice states for (std::size_t channel = 0; channel < channel_count; channel++) { diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp index 867b8fc6ba..d8c954b603 100644 --- a/src/audio_core/voice_context.cpp +++ b/src/audio_core/voice_context.cpp @@ -66,7 +66,7 @@ void ServerVoiceInfo::Initialize() { in_params.last_volume = 0.0f; in_params.biquad_filter.fill({}); in_params.wave_buffer_count = 0; - in_params.wave_bufffer_head = 0; + in_params.wave_buffer_head = 0; in_params.mix_id = AudioCommon::NO_MIX; in_params.splitter_info_id = AudioCommon::NO_SPLITTER; in_params.additional_params_address = 0; @@ -75,7 +75,7 @@ void ServerVoiceInfo::Initialize() { out_params.played_sample_count = 0; out_params.wave_buffer_consumed = 0; in_params.voice_drop_flag = false; - in_params.buffer_mapped = false; + in_params.buffer_mapped = true; in_params.wave_buffer_flush_request_count = 0; in_params.was_biquad_filter_enabled.fill(false); @@ -126,7 +126,7 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, in_params.volume = voice_in.volume; in_params.biquad_filter = voice_in.biquad_filter; in_params.wave_buffer_count = voice_in.wave_buffer_count; - in_params.wave_bufffer_head = voice_in.wave_buffer_head; + in_params.wave_buffer_head = voice_in.wave_buffer_head; if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { const auto in_request_count = in_params.wave_buffer_flush_request_count; const auto voice_request_count = voice_in.wave_buffer_flush_request_count; @@ -185,14 +185,16 @@ void ServerVoiceInfo::UpdateWaveBuffers( wave_buffer.buffer_size = 0; wave_buffer.context_address = 0; wave_buffer.context_size = 0; + wave_buffer.loop_start_sample = 0; + wave_buffer.loop_end_sample = 0; wave_buffer.sent_to_dsp = true; } // Mark all our wave buffers as invalid for (std::size_t channel = 0; channel < static_cast(in_params.channel_count); channel++) { - for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) { - is_valid = false; + for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) { + voice_states[channel]->is_wave_buffer_valid[i] = false; } } } @@ -211,7 +213,7 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer, SampleFormat sample_format, bool is_buffer_valid, [[maybe_unused]] BehaviorInfo& behavior_info) { - if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) { + if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) { out_wavebuffer.buffer_address = 0; out_wavebuffer.buffer_size = 0; } @@ -219,11 +221,40 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { // Validate sample offset sizings if (sample_format == SampleFormat::Pcm16) { - const auto buffer_size = in_wave_buffer.buffer_size; - if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 || - (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) || - (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) { + const s64 buffer_size = static_cast(in_wave_buffer.buffer_size); + const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset; + const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset; + if (0 > start || start > buffer_size || 0 > end || end > buffer_size) { // TODO(ogniK): Write error info + LOG_ERROR(Audio, + "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " + "offsets were " + "{:08X} - 0x{:08X}", + buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset, + sizeof(s16) * in_wave_buffer.end_sample_offset); + return; + } + } else if (sample_format == SampleFormat::Adpcm) { + const s64 buffer_size = static_cast(in_wave_buffer.buffer_size); + const s64 start_frames = in_wave_buffer.start_sample_offset / 14; + const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0 + ? 0 + : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 + + (in_wave_buffer.start_sample_offset % 2); + const s64 start = start_frames * 8 + start_extra; + const s64 end_frames = in_wave_buffer.end_sample_offset / 14; + const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0 + ? 0 + : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 + + (in_wave_buffer.end_sample_offset % 2); + const s64 end = end_frames * 8 + end_extra; + if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size || + in_wave_buffer.end_sample_offset < 0 || end > buffer_size) { + LOG_ERROR(Audio, + "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " + "offsets were " + "{:08X} - 0x{:08X}", + in_wave_buffer.buffer_size, start, end); return; } } @@ -239,29 +270,34 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; out_wavebuffer.context_address = in_wave_buffer.context_address; out_wavebuffer.context_size = in_wave_buffer.context_size; + out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample; + out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample; in_params.buffer_mapped = in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; // TODO(ogniK): Pool mapper attachment // TODO(ogniK): IsAdpcmLoopContextBugFixed + if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 && + in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) { + } else { + out_wavebuffer.context_address = 0; + out_wavebuffer.context_size = 0; + } } } void ServerVoiceInfo::WriteOutStatus( VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, std::array& voice_states) { - if (voice_in.is_new) { + if (voice_in.is_new || in_params.is_new) { in_params.is_new = true; voice_out.wave_buffer_consumed = 0; voice_out.played_sample_count = 0; voice_out.voice_dropped = false; - } else if (!in_params.is_new) { - voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed; - voice_out.played_sample_count = voice_states[0]->played_sample_count; - voice_out.voice_dropped = in_params.voice_drop_flag; } else { - voice_out.wave_buffer_consumed = 0; - voice_out.played_sample_count = 0; - voice_out.voice_dropped = false; + const auto& state = voice_states[0]; + voice_out.wave_buffer_consumed = state->wave_buffer_consumed; + voice_out.played_sample_count = state->played_sample_count; + voice_out.voice_dropped = state->voice_dropped; } } @@ -283,7 +319,8 @@ ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { bool ServerVoiceInfo::ShouldSkip() const { // TODO(ogniK): Handle unmapped wave buffers or parameters - return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag; + return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped || + in_params.voice_drop_flag; } bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { @@ -381,7 +418,7 @@ bool ServerVoiceInfo::UpdateParametersForCommandGeneration( void ServerVoiceInfo::FlushWaveBuffers( u8 flush_count, std::array& dsp_voice_states, s32 channel_count) { - auto wave_head = in_params.wave_bufffer_head; + auto wave_head = in_params.wave_buffer_head; for (u8 i = 0; i < flush_count; i++) { in_params.wave_buffer[wave_head].sent_to_dsp = true; @@ -401,6 +438,17 @@ bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); } +void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state, + const ServerWaveBuffer& wave_buffer) { + dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; + dsp_state.wave_buffer_consumed++; + dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + dsp_state.loop_count = 0; + if (wave_buffer.end_of_stream) { + dsp_state.played_sample_count = 0; + } +} + VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { for (std::size_t i = 0; i < voice_count; i++) { voice_channel_resources.emplace_back(static_cast(i)); diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h index 70359cadb1..e1050897bb 100644 --- a/src/audio_core/voice_context.h +++ b/src/audio_core/voice_context.h @@ -60,10 +60,12 @@ struct WaveBuffer { u8 is_looping{}; u8 end_of_stream{}; u8 sent_to_server{}; - INSERT_PADDING_BYTES(5); + INSERT_PADDING_BYTES(1); + s32 loop_count{}; u64 context_address{}; u64 context_size{}; - INSERT_PADDING_BYTES(8); + u32 loop_start_sample{}; + u32 loop_end_sample{}; }; static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); @@ -76,6 +78,9 @@ struct ServerWaveBuffer { bool end_of_stream{}; VAddr context_address{}; std::size_t context_size{}; + s32 loop_count{}; + u32 loop_start_sample{}; + u32 loop_end_sample{}; bool sent_to_dsp{true}; }; @@ -108,6 +113,7 @@ struct VoiceState { u32 external_context_size; bool is_external_context_used; bool voice_dropped; + s32 loop_count; }; class VoiceChannelResource { @@ -206,7 +212,7 @@ public: float last_volume{}; std::array biquad_filter{}; s32 wave_buffer_count{}; - s16 wave_bufffer_head{}; + s16 wave_buffer_head{}; INSERT_PADDING_BYTES(2); BehaviorFlags behavior_flags{}; VAddr additional_params_address{}; @@ -252,6 +258,7 @@ public: void FlushWaveBuffers(u8 flush_count, std::array& dsp_voice_states, s32 channel_count); + void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer); private: std::vector stored_samples; diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index feb5150e16..7583d68b2e 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -96,7 +96,7 @@ private: void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "(STUBBED) called"); - std::vector output_params(ctx.GetWriteBufferSize()); + std::vector output_params(ctx.GetWriteBufferSize(), 0); auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); if (result.IsSuccess()) {