From 0c8beedb94c25e5b41a8cc6f30eb9aa388f7a560 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 30 Dec 2022 17:39:41 +1000 Subject: [PATCH] SPU2: Split output latency and target buffer size --- pcsx2-qt/Settings/AudioSettingsWidget.cpp | 60 ++- pcsx2-qt/Settings/AudioSettingsWidget.h | 4 +- pcsx2-qt/Settings/AudioSettingsWidget.ui | 512 +++++++++++++--------- pcsx2/Config.h | 3 + pcsx2/Pcsx2Config.cpp | 2 + pcsx2/SPU2/SndOut_Cubeb.cpp | 26 +- pcsx2/SPU2/spu2.cpp | 2 + 7 files changed, 363 insertions(+), 246 deletions(-) diff --git a/pcsx2-qt/Settings/AudioSettingsWidget.cpp b/pcsx2-qt/Settings/AudioSettingsWidget.cpp index b03adfb87f..023f0fef06 100644 --- a/pcsx2-qt/Settings/AudioSettingsWidget.cpp +++ b/pcsx2-qt/Settings/AudioSettingsWidget.cpp @@ -33,7 +33,8 @@ static constexpr s32 DEFAULT_SYNCHRONIZATION_MODE = 0; static constexpr s32 DEFAULT_EXPANSION_MODE = 0; static constexpr s32 DEFAULT_DPL_DECODING_LEVEL = 0; static const char* DEFAULT_OUTPUT_MODULE = "cubeb"; -static constexpr s32 DEFAULT_OUTPUT_LATENCY = 100; +static constexpr s32 DEFAULT_TARGET_LATENCY = 100; +static constexpr s32 DEFAULT_OUTPUT_LATENCY = 20; static constexpr s32 DEFAULT_VOLUME = 100; static constexpr s32 DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH = 30; static constexpr s32 DEFAULT_SOUNDTOUCH_SEEK_WINDOW = 20; @@ -63,15 +64,21 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.syncMode, "SPU2/Output", "SynchMode", DEFAULT_SYNCHRONIZATION_MODE); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.expansionMode, "SPU2/Output", "SpeakerConfiguration", DEFAULT_EXPANSION_MODE); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.dplLevel, "SPU2/Output", "DplDecodingLevel", DEFAULT_DPL_DECODING_LEVEL); + connect(m_ui.syncMode, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioSettingsWidget::updateTargetLatencyRange); connect(m_ui.expansionMode, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioSettingsWidget::expansionModeChanged); + updateTargetLatencyRange(); expansionModeChanged(); SettingWidgetBinder::BindWidgetToEnumSetting( sif, m_ui.outputModule, "SPU2/Output", "OutputModule", s_output_module_entries, s_output_module_values, DEFAULT_OUTPUT_MODULE); - SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.latency, "SPU2/Output", "Latency", DEFAULT_OUTPUT_LATENCY); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.targetLatency, "SPU2/Output", "Latency", DEFAULT_TARGET_LATENCY); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.outputLatency, "SPU2/Output", "OutputLatency", DEFAULT_OUTPUT_LATENCY); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.outputLatencyMinimal, "SPU2/Output", "OutputLatencyMinimal", false); connect(m_ui.outputModule, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::outputModuleChanged); connect(m_ui.backend, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::outputBackendChanged); - connect(m_ui.latency, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel); + connect(m_ui.targetLatency, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabels); + connect(m_ui.outputLatency, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabels); + connect(m_ui.outputLatencyMinimal, &QCheckBox::stateChanged, this, &AudioSettingsWidget::updateLatencyLabels); outputModuleChanged(); m_ui.volume->setValue(m_dialog->getEffectiveIntValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME)); @@ -90,7 +97,8 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent m_ui.dplLevel->setVisible(false); volumeChanged(m_ui.volume->value()); - updateLatencyLabel(); + onMinimalOutputLatencyStateChanged(); + updateLatencyLabels(); updateTimestretchSequenceLengthLabel(); updateTimestretchSeekwindowLengthLabel(); updateTimestretchOverlapLabel(); @@ -105,7 +113,12 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent dialog->registerWidgetHelp(m_ui.backend, tr("Output Backend"), tr("Default"), tr("")); - dialog->registerWidgetHelp(m_ui.latency, tr("Latency"), tr("100 ms"), tr("")); + dialog->registerWidgetHelp(m_ui.targetLatency, tr("Target Latency"), tr("100 ms"), + tr("Determines the buffer size which the time stretcher will try to keep filled. It effectively selects the average latency, as " + "audio will be stretched/shrunk to keep the buffer size within check.")); + dialog->registerWidgetHelp(m_ui.outputLatency, tr("Output Latency"), tr("20 ms"), + tr("Determines the latency from the buffer to the host audio output. This can be set lower than the target latency to reduce audio " + "delay.")); dialog->registerWidgetHelp(m_ui.sequenceLength, tr("Sequence Length"), tr("30 ms"), tr("")); @@ -206,9 +219,42 @@ void AudioSettingsWidget::volumeChanged(int value) } } -void AudioSettingsWidget::updateLatencyLabel() +void AudioSettingsWidget::updateTargetLatencyRange() { - m_ui.latencyLabel->setText(tr("%1 ms (avg)").arg(m_ui.latency->value())); + const Pcsx2Config::SPU2Options::SynchronizationMode sync_mode = static_cast( + m_dialog->getIntValue("SPU2/Output", "SynchMode", DEFAULT_SYNCHRONIZATION_MODE).value_or(DEFAULT_SYNCHRONIZATION_MODE)); + + m_ui.targetLatency->setMinimum((sync_mode == Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch) ? + Pcsx2Config::SPU2Options::MIN_LATENCY_TIMESTRETCH : + Pcsx2Config::SPU2Options::MIN_LATENCY); + m_ui.targetLatency->setMaximum(Pcsx2Config::SPU2Options::MAX_LATENCY); +} + +void AudioSettingsWidget::updateLatencyLabels() +{ + const bool minimal_output = m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false); + + m_ui.targetLatencyLabel->setText(tr("%1 ms").arg(m_ui.targetLatency->value())); + m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(m_ui.outputLatency->value())); + + const u32 output_latency_ms = minimal_output ? 0 : static_cast(m_ui.outputLatency->value()); + const u32 buffer_ms = static_cast(m_ui.targetLatency->value()); + if (output_latency_ms > 0) + { + m_ui.latencySummary->setText(tr("Average Latency: %1 ms (%2 ms buffer + %3 ms output)") + .arg(buffer_ms + output_latency_ms) + .arg(buffer_ms) + .arg(output_latency_ms)); + } + else + { + m_ui.latencySummary->setText(tr("Average Latency: %1 ms (plus minimum output)").arg(buffer_ms)); + } +} + +void AudioSettingsWidget::onMinimalOutputLatencyStateChanged() +{ + m_ui.outputLatency->setEnabled(!m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false)); } void AudioSettingsWidget::updateTimestretchSequenceLengthLabel() diff --git a/pcsx2-qt/Settings/AudioSettingsWidget.h b/pcsx2-qt/Settings/AudioSettingsWidget.h index 8269f87a7f..5b12c8e5a5 100644 --- a/pcsx2-qt/Settings/AudioSettingsWidget.h +++ b/pcsx2-qt/Settings/AudioSettingsWidget.h @@ -34,7 +34,9 @@ private Q_SLOTS: void outputModuleChanged(); void outputBackendChanged(); void volumeChanged(int value); - void updateLatencyLabel(); + void updateTargetLatencyRange(); + void updateLatencyLabels(); + void onMinimalOutputLatencyStateChanged(); void updateTimestretchSequenceLengthLabel(); void updateTimestretchSeekwindowLengthLabel(); void updateTimestretchOverlapLabel(); diff --git a/pcsx2-qt/Settings/AudioSettingsWidget.ui b/pcsx2-qt/Settings/AudioSettingsWidget.ui index 67dfc5a5c4..e3e9a99d0e 100644 --- a/pcsx2-qt/Settings/AudioSettingsWidget.ui +++ b/pcsx2-qt/Settings/AudioSettingsWidget.ui @@ -6,8 +6,8 @@ 0 0 - 752 - 448 + 754 + 464 @@ -26,208 +26,6 @@ 0 - - - - Mixing Settings - - - - - - Interpolation: - - - - - - - - Nearest (Fastest / worst quality) - - - - - Linear (Simple / okay sound) - - - - - Cubic (Fake highs / okay sound) - - - - - Hermite (Better highs / okay sound) - - - - - Catmull-Rom (PS2-like / good sound) - - - - - Gaussian (PS2-like / great sound) - - - - - - - - Synchronization: - - - - - - - - TimeStretch (Recommended) - - - - - Async Mix (Breaks some games!) - - - - - None (Audio can skip.) - - - - - - - - Expansion: - - - - - - - - Stereo (None, Default) - - - - - Quadrafonic - - - - - Surround 5.1 - - - - - Surround 7.1 - - - - - - - - ProLogic Level: - - - - - - - - None (Default) - - - - - ProLogic Decoding (basic) - - - - - ProLogic II Decoding (gigaherz) - - - - - - - - - - - Output Settings - - - - - - Output Module: - - - - - - - - - - Latency: - - - - - - - - - 15 - - - 200 - - - 100 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 200 - - - - - - - 100 ms (avg) - - - - - - - - - Output Backend: - - - - - - - - - @@ -378,19 +176,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -401,6 +186,18 @@ + + + 0 + 0 + + + + + 40 + 0 + + 200 @@ -417,9 +214,18 @@ + + + 40 + 0 + + 100% + + Qt::AlignCenter + @@ -440,6 +246,278 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Mixing Settings + + + + + + Interpolation: + + + + + + + + Nearest (Fastest / worst quality) + + + + + Linear (Simple / okay sound) + + + + + Cubic (Fake highs / okay sound) + + + + + Hermite (Better highs / okay sound) + + + + + Catmull-Rom (PS2-like / good sound) + + + + + Gaussian (PS2-like / great sound) + + + + + + + + Synchronization: + + + + + + + + TimeStretch (Recommended) + + + + + Async Mix (Breaks some games!) + + + + + None (Audio can skip.) + + + + + + + + Expansion: + + + + + + + + Stereo (None, Default) + + + + + Quadrafonic + + + + + Surround 5.1 + + + + + Surround 7.1 + + + + + + + + ProLogic Level: + + + + + + + + None (Default) + + + + + ProLogic Decoding (basic) + + + + + ProLogic II Decoding (gigaherz) + + + + + + + + Target Latency: + + + + + + + + + 15 + + + 200 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 200 + + + + + + + 100 ms + + + + + + + + + + + + Output Settings + + + + + + Output Module: + + + + + + + + + + Output Latency: + + + + + + + + + 10 + + + 200 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 200 + + + + + + + 100 ms + + + + + + + Minimal + + + + + + + + + Output Backend: + + + + + + + + + + Maximum Latency: + + + Qt::AlignCenter + + + + + + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 3f5a5690e6..e0c38ad4a9 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -808,6 +808,7 @@ struct Pcsx2Config static constexpr s32 MAX_OVERLAP = 15; BITFIELD32() + bool OutputLatencyMinimal : 1; bool DebugEnabled : 1, MsgToConsole : 1, @@ -831,6 +832,7 @@ struct Pcsx2Config s32 FinalVolume = 100; s32 Latency = 100; + s32 OutputLatency = 20; s32 SpeakerConfiguration = 0; s32 DplDecodingLevel = 0; @@ -854,6 +856,7 @@ struct Pcsx2Config OpEqu(FinalVolume) && OpEqu(Latency) && + OpEqu(OutputLatency) && OpEqu(SpeakerConfiguration) && OpEqu(DplDecodingLevel) && diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 884c36e703..fbc99546f9 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -827,6 +827,8 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap) SettingsWrapEntry(OutputModule); SettingsWrapEntry(BackendName); SettingsWrapEntry(Latency); + SettingsWrapEntry(OutputLatency); + SettingsWrapBitBool(OutputLatencyMinimal); SynchMode = static_cast(wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, "SynchMode", static_cast(SynchMode), static_cast(SynchMode))); SettingsWrapEntry(SpeakerConfiguration); SettingsWrapEntry(DplDecodingLevel); diff --git a/pcsx2/SPU2/SndOut_Cubeb.cpp b/pcsx2/SPU2/SndOut_Cubeb.cpp index 69b0802f5c..b084759bea 100644 --- a/pcsx2/SPU2/SndOut_Cubeb.cpp +++ b/pcsx2/SPU2/SndOut_Cubeb.cpp @@ -34,9 +34,6 @@ class Cubeb : public SndOutModule { private: - static constexpr int MINIMUM_LATENCY_MS = 20; - static constexpr int MAXIMUM_LATENCY_MS = 200; - ////////////////////////////////////////////////////////////////////////////////////////// // Stuff necessary for speaker expansion class SampleReader @@ -115,9 +112,6 @@ private: #ifdef _WIN32 bool m_COMInitializedByUs = false; #endif - bool m_SuggestedLatencyMinimal = false; - int m_SuggestedLatencyMS = 20; - std::string m_Backend; ////////////////////////////////////////////////////////////////////////////////////////// // Instance vars @@ -142,10 +136,6 @@ public: bool Init() override { - ReadSettings(); - - // TODO(Stenzek): Migrate the errors to Host::ReportErrorAsync() once more Qt stuff is merged. - #ifdef _WIN32 HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); m_COMInitializedByUs = SUCCEEDED(hr); @@ -160,7 +150,8 @@ public: cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback); #endif - int rv = cubeb_init(&m_context, "PCSX2", m_Backend.empty() ? nullptr : m_Backend.c_str()); + const std::string backend(Host::GetStringSettingValue("SPU2/Output", "BackendName", "")); + int rv = cubeb_init(&m_context, "PCSX2", backend.empty() ? nullptr : backend.c_str()); if (rv != CUBEB_OK) { Host::ReportFormattedErrorAsync("Cubeb Error", "Could not initialize cubeb context: %d", rv); @@ -248,13 +239,13 @@ public: params.layout = layout; params.prefs = CUBEB_STREAM_PREF_NONE; - const u32 requested_latency_frames = static_cast((m_SuggestedLatencyMS * static_cast(SampleRate)) / 1000u); + const u32 requested_latency_frames = static_cast((EmuConfig.SPU2.OutputLatency * SampleRate) / 1000u); u32 latency_frames = 0; rv = cubeb_get_min_latency(m_context, ¶ms, &latency_frames); if (rv == CUBEB_ERROR_NOT_SUPPORTED) { Console.WriteLn("(Cubeb) Cubeb backend does not support latency queries, using latency of %d ms (%u frames).", - m_SuggestedLatencyMS, requested_latency_frames); + EmuConfig.SPU2.OutputLatency, requested_latency_frames); latency_frames = requested_latency_frames; } else @@ -268,7 +259,7 @@ public: const float minimum_latency_ms = static_cast(latency_frames * 1000u) / static_cast(SampleRate); Console.WriteLn("(Cubeb) Minimum latency: %.2f ms (%u audio frames)", minimum_latency_ms, latency_frames); - if (!m_SuggestedLatencyMinimal) + if (!EmuConfig.SPU2.OutputLatencyMinimal) { if (latency_frames > requested_latency_frames) { @@ -362,13 +353,6 @@ public: { return cubeb_get_backend_names(); } - - void ReadSettings() - { - m_SuggestedLatencyMinimal = Host::GetBoolSettingValue("Cubeb", "MinimalSuggestedLatency", false); - m_SuggestedLatencyMS = std::clamp(Host::GetIntSettingValue("Cubeb", "ManualSuggestedLatencyMS", MINIMUM_LATENCY_MS), MINIMUM_LATENCY_MS, MAXIMUM_LATENCY_MS); - m_Backend = Host::GetStringSettingValue("SPU2/Output", "BackendName", ""); - } }; static Cubeb s_Cubeb; diff --git a/pcsx2/SPU2/spu2.cpp b/pcsx2/SPU2/spu2.cpp index bb80a32af4..566c1febda 100644 --- a/pcsx2/SPU2/spu2.cpp +++ b/pcsx2/SPU2/spu2.cpp @@ -395,6 +395,8 @@ void SPU2::CheckForConfigChanges(const Pcsx2Config& old_config) // Things which require re-initialzing the output. if (opts.Latency != oldopts.Latency || + opts.OutputLatency != oldopts.OutputLatency || + opts.OutputLatencyMinimal != oldopts.OutputLatencyMinimal || opts.SpeakerConfiguration != oldopts.SpeakerConfiguration || opts.DplDecodingLevel != oldopts.DplDecodingLevel || opts.SequenceLenMS != oldopts.SequenceLenMS ||