diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index d42249ebd..8e19ec0c4 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -71,6 +71,10 @@ void SelectSink(std::string sink_id) { DSP::HLE::SetSink(iter->factory()); } +void EnableStretching(bool enable) { + DSP::HLE::EnableStretching(enable); +} + void Shutdown() { CoreTiming::UnscheduleEvent(tick_event, 0); DSP::HLE::Shutdown(); diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index f618361f3..7e678aba5 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -23,6 +23,9 @@ void AddAddressSpace(Kernel::VMManager& vm_manager); /// Select the sink to use based on sink id. void SelectSink(std::string sink_id); +/// Enable/Disable stretching. +void EnableStretching(bool enable); + /// Shutdown Audio Core void Shutdown(); diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp index 0640e1eff..0cddeb82a 100644 --- a/src/audio_core/hle/dsp.cpp +++ b/src/audio_core/hle/dsp.cpp @@ -85,12 +85,45 @@ static StereoFrame16 GenerateCurrentFrame() { // Audio output +static bool perform_time_stretching = true; static std::unique_ptr sink; static AudioCore::TimeStretcher time_stretcher; +static void FlushResidualStretcherAudio() { + time_stretcher.Flush(); + while (true) { + std::vector residual_audio = time_stretcher.Process(sink->SamplesInQueue()); + if (residual_audio.empty()) + break; + sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2); + } +} + static void OutputCurrentFrame(const StereoFrame16& frame) { - time_stretcher.AddSamples(&frame[0][0], frame.size()); - sink->EnqueueSamples(time_stretcher.Process(sink->SamplesInQueue())); + if (perform_time_stretching) { + time_stretcher.AddSamples(&frame[0][0], frame.size()); + std::vector stretched_samples = time_stretcher.Process(sink->SamplesInQueue()); + sink->EnqueueSamples(stretched_samples.data(), stretched_samples.size() / 2); + } else { + constexpr size_t maximum_sample_latency = 1024; // about 32 miliseconds + if (sink->SamplesInQueue() > maximum_sample_latency) { + // This can occur if we're running too fast and samples are starting to back up. + // Just drop the samples. + return; + } + + sink->EnqueueSamples(&frame[0][0], frame.size()); + } +} + +void EnableStretching(bool enable) { + if (perform_time_stretching == enable) + return; + + if (!enable) { + FlushResidualStretcherAudio(); + } + perform_time_stretching = enable; } // Public Interface @@ -111,12 +144,8 @@ void Init() { } void Shutdown() { - time_stretcher.Flush(); - while (true) { - std::vector residual_audio = time_stretcher.Process(sink->SamplesInQueue()); - if (residual_audio.empty()) - break; - sink->EnqueueSamples(residual_audio); + if (perform_time_stretching) { + FlushResidualStretcherAudio(); } } diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h index 9275cd7de..565f20b6f 100644 --- a/src/audio_core/hle/dsp.h +++ b/src/audio_core/hle/dsp.h @@ -544,5 +544,13 @@ bool Tick(); */ void SetSink(std::unique_ptr sink); +/** + * Enables/Disables audio-stretching. + * Audio stretching is an enhancement that stretches audio to match emulation + * speed to prevent stuttering at the cost of some audio latency. + * @param enable true to enable, false to disable. + */ +void EnableStretching(bool enable); + } // namespace HLE } // namespace DSP diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index faf0ee4e1..9931c4778 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -19,7 +19,7 @@ public: return native_sample_rate; } - void EnqueueSamples(const std::vector&) override {} + void EnqueueSamples(const s16*, size_t) override {} size_t SamplesInQueue() const override { return 0; diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp index dc75c04ee..311dd5b59 100644 --- a/src/audio_core/sdl2_sink.cpp +++ b/src/audio_core/sdl2_sink.cpp @@ -71,14 +71,12 @@ unsigned int SDL2Sink::GetNativeSampleRate() const { return impl->sample_rate; } -void SDL2Sink::EnqueueSamples(const std::vector& samples) { +void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) { if (impl->audio_device_id <= 0) return; - ASSERT_MSG(samples.size() % 2 == 0, "Samples must be in interleaved stereo PCM16 format (size must be a multiple of two)"); - SDL_LockAudioDevice(impl->audio_device_id); - impl->queue.emplace_back(samples); + impl->queue.emplace_back(samples, samples + sample_count * 2); SDL_UnlockAudioDevice(impl->audio_device_id); } diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h index 0f296b673..b13827214 100644 --- a/src/audio_core/sdl2_sink.h +++ b/src/audio_core/sdl2_sink.h @@ -18,7 +18,7 @@ public: unsigned int GetNativeSampleRate() const override; - void EnqueueSamples(const std::vector& samples) override; + void EnqueueSamples(const s16* samples, size_t sample_count) override; size_t SamplesInQueue() const override; diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 1c881c3d2..a06fc3dcc 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -23,9 +23,10 @@ public: /** * Feed stereo samples to sink. - * @param samples Samples in interleaved stereo PCM16 format. Size of vector must be multiple of two. + * @param samples Samples in interleaved stereo PCM16 format. + * @param sample_count Number of samples. */ - virtual void EnqueueSamples(const std::vector& samples) = 0; + virtual void EnqueueSamples(const s16* samples, size_t sample_count) = 0; /// Samples enqueued that have not been played yet. virtual std::size_t SamplesInQueue() const = 0; diff --git a/src/citra/config.cpp b/src/citra/config.cpp index e832ec58d..f48386b9c 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -78,6 +78,7 @@ void Config::ReadValues() { // Audio Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); + Settings::values.enable_audio_stretching = sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 6249ef9e2..5b8ca12a0 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -66,6 +66,12 @@ bg_green = # auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) output_engine = +# Whether or not to enable the audio-stretching post-processing effect. +# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, +# at the cost of increasing audio latency. +# 0: No, 1 (default): Yes +enable_audio_stretching = + [Data Storage] # Whether to create a virtual SD card. # 1 (default): Yes, 0: No diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 93c6a6e41..ff3bfe1bd 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -56,6 +56,7 @@ void Config::ReadValues() { qt_config->beginGroup("Audio"); Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); + Settings::values.enable_audio_stretching = qt_config->value("enable_audio_stretching", true).toBool(); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); @@ -148,6 +149,7 @@ void Config::SaveValues() { qt_config->beginGroup("Audio"); qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); + qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configure_audio.cpp index cedfa2f2a..7100be158 100644 --- a/src/citra_qt/configure_audio.cpp +++ b/src/citra_qt/configure_audio.cpp @@ -36,9 +36,12 @@ void ConfigureAudio::setConfiguration() { } } ui->output_sink_combo_box->setCurrentIndex(new_sink_index); + + ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); } void ConfigureAudio::applyConfiguration() { Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString(); + Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); Settings::Apply(); } diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui index d7f6946ca..3e2b4635f 100644 --- a/src/citra_qt/configure_audio.ui +++ b/src/citra_qt/configure_audio.ui @@ -25,6 +25,16 @@ + + + + Enable audio stretching + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + + + diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 77261eafe..1b6733a79 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -24,6 +24,7 @@ void Apply() { VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution; AudioCore::SelectSink(values.sink_id); + AudioCore::EnableStretching(values.enable_audio_stretching); } diff --git a/src/core/settings.h b/src/core/settings.h index f95e62390..0962a4ecf 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -81,6 +81,7 @@ struct Values { // Audio std::string sink_id; + bool enable_audio_stretching; // Debugging bool use_gdbstub;