summaryrefslogtreecommitdiff
path: root/src/audio_core/audio_renderer.cpp
diff options
context:
space:
mode:
authorGravatar Kelebek12021-06-20 15:23:16 +0100
committerGravatar Kelebek12021-06-27 15:58:07 +0100
commit0857d6a3dbcaeb30b51326419fb56d9b543601f1 (patch)
tree26221ce096533b188aa1fe8d7e82290780edee1d /src/audio_core/audio_renderer.cpp
parentMerge pull request #6526 from bunnei/doom-update (diff)
downloadyuzu-0857d6a3dbcaeb30b51326419fb56d9b543601f1.tar.gz
yuzu-0857d6a3dbcaeb30b51326419fb56d9b543601f1.tar.xz
yuzu-0857d6a3dbcaeb30b51326419fb56d9b543601f1.zip
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.
Diffstat (limited to 'src/audio_core/audio_renderer.cpp')
-rw-r--r--src/audio_core/audio_renderer.cpp178
1 files changed, 103 insertions, 75 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index 80ffddb10..0757cd804 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -12,6 +12,7 @@
12#include "audio_core/voice_context.h" 12#include "audio_core/voice_context.h"
13#include "common/logging/log.h" 13#include "common/logging/log.h"
14#include "common/settings.h" 14#include "common/settings.h"
15#include "core/core_timing.h"
15#include "core/memory.h" 16#include "core/memory.h"
16 17
17namespace { 18namespace {
@@ -68,7 +69,9 @@ namespace {
68} // namespace 69} // namespace
69 70
70namespace AudioCore { 71namespace AudioCore {
71AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, 72constexpr s32 NUM_BUFFERS = 2;
73
74AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_,
72 AudioCommon::AudioRendererParameter params, 75 AudioCommon::AudioRendererParameter params,
73 Stream::ReleaseCallback&& release_callback, 76 Stream::ReleaseCallback&& release_callback,
74 std::size_t instance_number) 77 std::size_t instance_number)
@@ -77,7 +80,8 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
77 sink_context(params.sink_count), splitter_context(), 80 sink_context(params.sink_count), splitter_context(),
78 voices(params.voice_count), memory{memory_}, 81 voices(params.voice_count), memory{memory_},
79 command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, 82 command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
80 memory) { 83 memory),
84 core_timing{core_timing_} {
81 behavior_info.SetUserRevision(params.revision); 85 behavior_info.SetUserRevision(params.revision);
82 splitter_context.Initialize(behavior_info, params.splitter_count, 86 splitter_context.Initialize(behavior_info, params.splitter_count,
83 params.num_splitter_send_channels); 87 params.num_splitter_send_channels);
@@ -86,16 +90,27 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
86 stream = audio_out->OpenStream( 90 stream = audio_out->OpenStream(
87 core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, 91 core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
88 fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); 92 fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
89 audio_out->StartStream(stream); 93 process_event = Core::Timing::CreateEvent(
90 94 fmt::format("AudioRenderer-Instance{}-Process", instance_number),
91 QueueMixedBuffer(0); 95 [this](std::uintptr_t, std::chrono::nanoseconds) { ReleaseAndQueueBuffers(); });
92 QueueMixedBuffer(1); 96 for (s32 i = 0; i < NUM_BUFFERS; ++i) {
93 QueueMixedBuffer(2); 97 QueueMixedBuffer(i);
94 QueueMixedBuffer(3); 98 }
95} 99}
96 100
97AudioRenderer::~AudioRenderer() = default; 101AudioRenderer::~AudioRenderer() = default;
98 102
103ResultCode AudioRenderer::Start() {
104 audio_out->StartStream(stream);
105 ReleaseAndQueueBuffers();
106 return ResultSuccess;
107}
108
109ResultCode AudioRenderer::Stop() {
110 audio_out->StopStream(stream);
111 return ResultSuccess;
112}
113
99u32 AudioRenderer::GetSampleRate() const { 114u32 AudioRenderer::GetSampleRate() const {
100 return worker_params.sample_rate; 115 return worker_params.sample_rate;
101} 116}
@@ -114,89 +129,88 @@ Stream::State AudioRenderer::GetStreamState() const {
114 129
115ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, 130ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
116 std::vector<u8>& output_params) { 131 std::vector<u8>& output_params) {
132 {
133 std::scoped_lock lock{mutex};
134 InfoUpdater info_updater{input_params, output_params, behavior_info};
117 135
118 InfoUpdater info_updater{input_params, output_params, behavior_info}; 136 if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
119 137 LOG_ERROR(Audio, "Failed to update behavior info input parameters");
120 if (!info_updater.UpdateBehaviorInfo(behavior_info)) { 138 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
121 LOG_ERROR(Audio, "Failed to update behavior info input parameters"); 139 }
122 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
123 }
124
125 if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
126 LOG_ERROR(Audio, "Failed to update memory pool parameters");
127 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
128 }
129
130 if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
131 LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
132 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
133 }
134 140
135 if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { 141 if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
136 LOG_ERROR(Audio, "Failed to update voice parameters"); 142 LOG_ERROR(Audio, "Failed to update memory pool parameters");
137 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 143 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
138 } 144 }
139 145
140 // TODO(ogniK): Deal with stopped audio renderer but updates still taking place 146 if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
141 if (!info_updater.UpdateEffects(effect_context, true)) { 147 LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
142 LOG_ERROR(Audio, "Failed to update effect parameters"); 148 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
143 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 149 }
144 }
145 150
146 if (behavior_info.IsSplitterSupported()) { 151 if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
147 if (!info_updater.UpdateSplitterInfo(splitter_context)) { 152 LOG_ERROR(Audio, "Failed to update voice parameters");
148 LOG_ERROR(Audio, "Failed to update splitter parameters");
149 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 153 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
150 } 154 }
151 }
152 155
153 const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, 156 // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
154 splitter_context, effect_context); 157 if (!info_updater.UpdateEffects(effect_context, true)) {
158 LOG_ERROR(Audio, "Failed to update effect parameters");
159 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
160 }
155 161
156 if (mix_result.IsError()) { 162 if (behavior_info.IsSplitterSupported()) {
157 LOG_ERROR(Audio, "Failed to update mix parameters"); 163 if (!info_updater.UpdateSplitterInfo(splitter_context)) {
158 return mix_result; 164 LOG_ERROR(Audio, "Failed to update splitter parameters");
159 } 165 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
166 }
167 }
160 168
161 // TODO(ogniK): Sinks 169 const auto mix_result = info_updater.UpdateMixes(
162 if (!info_updater.UpdateSinks(sink_context)) { 170 mix_context, worker_params.mix_buffer_count, splitter_context, effect_context);
163 LOG_ERROR(Audio, "Failed to update sink parameters");
164 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
165 }
166 171
167 // TODO(ogniK): Performance buffer 172 if (mix_result.IsError()) {
168 if (!info_updater.UpdatePerformanceBuffer()) { 173 LOG_ERROR(Audio, "Failed to update mix parameters");
169 LOG_ERROR(Audio, "Failed to update performance buffer parameters"); 174 return mix_result;
170 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 175 }
171 }
172 176
173 if (!info_updater.UpdateErrorInfo(behavior_info)) { 177 // TODO(ogniK): Sinks
174 LOG_ERROR(Audio, "Failed to update error info"); 178 if (!info_updater.UpdateSinks(sink_context)) {
175 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 179 LOG_ERROR(Audio, "Failed to update sink parameters");
176 } 180 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
181 }
177 182
178 if (behavior_info.IsElapsedFrameCountSupported()) { 183 // TODO(ogniK): Performance buffer
179 if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { 184 if (!info_updater.UpdatePerformanceBuffer()) {
180 LOG_ERROR(Audio, "Failed to update renderer info"); 185 LOG_ERROR(Audio, "Failed to update performance buffer parameters");
181 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 186 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
182 } 187 }
183 }
184 // TODO(ogniK): Statistics
185 188
186 if (!info_updater.WriteOutputHeader()) { 189 if (!info_updater.UpdateErrorInfo(behavior_info)) {
187 LOG_ERROR(Audio, "Failed to write output header"); 190 LOG_ERROR(Audio, "Failed to update error info");
188 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 191 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
189 } 192 }
190 193
191 // TODO(ogniK): Check when all sections are implemented 194 if (behavior_info.IsElapsedFrameCountSupported()) {
195 if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
196 LOG_ERROR(Audio, "Failed to update renderer info");
197 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
198 }
199 }
200 // TODO(ogniK): Statistics
192 201
193 if (!info_updater.CheckConsumedSize()) { 202 if (!info_updater.WriteOutputHeader()) {
194 LOG_ERROR(Audio, "Audio buffers were not consumed!"); 203 LOG_ERROR(Audio, "Failed to write output header");
195 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 204 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
196 } 205 }
197 206
198 ReleaseAndQueueBuffers(); 207 // TODO(ogniK): Check when all sections are implemented
199 208
209 if (!info_updater.CheckConsumedSize()) {
210 LOG_ERROR(Audio, "Audio buffers were not consumed!");
211 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
212 }
213 }
200 return ResultSuccess; 214 return ResultSuccess;
201} 215}
202 216
@@ -315,10 +329,24 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
315} 329}
316 330
317void AudioRenderer::ReleaseAndQueueBuffers() { 331void AudioRenderer::ReleaseAndQueueBuffers() {
318 const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; 332 if (!stream->IsPlaying()) {
319 for (const auto& tag : released_buffers) { 333 return;
320 QueueMixedBuffer(tag); 334 }
335
336 {
337 std::scoped_lock lock{mutex};
338 const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
339 for (const auto& tag : released_buffers) {
340 QueueMixedBuffer(tag);
341 }
321 } 342 }
343
344 const f32 sample_rate = static_cast<f32>(GetSampleRate());
345 const f32 sample_count = static_cast<f32>(GetSampleCount());
346 const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240));
347 const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1;
348 const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1));
349 core_timing.ScheduleEvent(next_event_time, process_event, {});
322} 350}
323 351
324} // namespace AudioCore 352} // namespace AudioCore