diff --git a/src/core/spu.cpp b/src/core/spu.cpp index b0d9e0726..51b157519 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -439,7 +439,8 @@ void SPU::CreateOutputStream() Error error; s_audio_stream = - AudioStream::CreateStream(g_settings.audio_backend, SAMPLE_RATE, g_settings.audio_stream_parameters, &error); + AudioStream::CreateStream(g_settings.audio_backend, SAMPLE_RATE, g_settings.audio_stream_parameters, + g_settings.audio_driver.c_str(), g_settings.audio_output_device.c_str(), &error); if (!s_audio_stream) { Host::ReportErrorAsync( diff --git a/src/duckstation-qt/audiosettingswidget.cpp b/src/duckstation-qt/audiosettingswidget.cpp index bce1c1e8b..d292d8cd6 100644 --- a/src/duckstation-qt/audiosettingswidget.cpp +++ b/src/duckstation-qt/audiosettingswidget.cpp @@ -12,6 +12,7 @@ #include "util/audio_stream.h" +#include #include AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog) @@ -58,12 +59,10 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent onStretchModeChanged(); updateDriverNames(); - m_ui.outputLatencyMinimal->setChecked(m_ui.outputLatencyMS->value() == 0); - m_ui.outputLatencyMS->setEnabled(m_ui.outputLatencyMinimal->isChecked()); - connect(m_ui.bufferMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel); connect(m_ui.outputLatencyMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel); - connect(m_ui.outputLatencyMinimal, &QCheckBox::toggled, this, &AudioSettingsWidget::onMinimalOutputLatencyChecked); + connect(m_ui.outputLatencyMinimal, &QCheckBox::checkStateChanged, this, + &AudioSettingsWidget::onMinimalOutputLatencyChecked); updateLatencyLabel(); // for per-game, just use the normal path, since it needs to re-read/apply @@ -79,8 +78,10 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent } else { - SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.volume, "Audio", "OutputVolume", 100); - SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.fastForwardVolume, "Audio", "FastForwardVolume", 100); + SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.volume, m_ui.volumeLabel, tr("%"), "Audio", + "OutputVolume", 100); + SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.fastForwardVolume, m_ui.fastForwardVolumeLabel, + tr("%"), "Audio", "FastForwardVolume", 100); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muted, "Audio", "OutputMuted", false); } @@ -90,7 +91,7 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent "lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio " "output.")); dialog->registerWidgetHelp( - m_ui.outputLatencyMS, tr("Output Latency"), tr("50 ms"), + m_ui.outputLatencyMS, tr("Output Latency"), tr("%1 ms").arg(AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS), tr("The buffer size determines the size of the chunks of audio which will be pulled by the " "host. Smaller values reduce the output latency, but may cause hitches if the emulation " "speed is inconsistent. Note that the Cubeb backend uses smaller chunks regardless of " @@ -120,16 +121,32 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent AudioSettingsWidget::~AudioSettingsWidget() = default; +AudioExpansionMode AudioSettingsWidget::getEffectiveExpansionMode() const +{ + return AudioStream::ParseExpansionMode( + m_dialog + ->getEffectiveStringValue("Audio", "ExpansionMode", + AudioStream::GetExpansionModeName(AudioStreamParameters::DEFAULT_EXPANSION_MODE)) + .c_str()) + .value_or(AudioStreamParameters::DEFAULT_EXPANSION_MODE); +} + +u32 AudioSettingsWidget::getEffectiveExpansionBlockSize() const +{ + const AudioExpansionMode expansion_mode = getEffectiveExpansionMode(); + if (expansion_mode == AudioExpansionMode::Disabled) + return 0; + + const u32 config_block_size = + m_dialog->getEffectiveIntValue("Audio", "ExpandBlockSize", AudioStreamParameters::DEFAULT_EXPAND_BLOCK_SIZE); + return std::has_single_bit(config_block_size) ? config_block_size : std::bit_ceil(config_block_size); +} + void AudioSettingsWidget::onExpansionModeChanged() { - const AudioExpansionMode expansion_mode = - AudioStream::ParseExpansionMode( - m_dialog - ->getEffectiveStringValue("Audio", "ExpansionMode", - AudioStream::GetExpansionModeName(AudioStreamParameters::DEFAULT_EXPANSION_MODE)) - .c_str()) - .value_or(AudioStreamParameters::DEFAULT_EXPANSION_MODE); + const AudioExpansionMode expansion_mode = getEffectiveExpansionMode(); m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled); + updateLatencyLabel(); } void AudioSettingsWidget::onStretchModeChanged() @@ -144,22 +161,19 @@ void AudioSettingsWidget::onStretchModeChanged() m_ui.stretchSettings->setEnabled(stretch_mode != AudioStretchMode::Off); } +AudioBackend AudioSettingsWidget::getEffectiveBackend() const +{ + return AudioStream::ParseBackendName( + m_dialog + ->getEffectiveStringValue("Audio", "Backend", AudioStream::GetBackendName(AudioStream::DEFAULT_BACKEND)) + .c_str()) + .value_or(AudioStream::DEFAULT_BACKEND); +} + void AudioSettingsWidget::updateDriverNames() { - const AudioBackend backend = - AudioStream::ParseBackendName( - m_dialog->getEffectiveStringValue("Audio", "Backend", AudioStream::GetBackendName(AudioStream::DEFAULT_BACKEND)) - .c_str()) - .value_or(AudioStream::DEFAULT_BACKEND); - - std::vector names; - std::vector> devices; - - if (backend == AudioBackend::Cubeb) - { - names = AudioStream::GetCubebDriverNames(); - devices = AudioStream::GetCubebOutputDevices(m_dialog->getEffectiveStringValue("Audio", "Driver", "").c_str()); - } + const AudioBackend backend = getEffectiveBackend(); + const std::vector names = AudioStream::GetDriverNames(backend); m_ui.driver->disconnect(); m_ui.driver->clear(); @@ -176,12 +190,24 @@ void AudioSettingsWidget::updateDriverNames() SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.driver, "Audio", "Driver", std::move(names.front())); - connect(m_ui.driver, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::updateDriverNames); + connect(m_ui.driver, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::updateDeviceNames); } + updateDeviceNames(); +} + +void AudioSettingsWidget::updateDeviceNames() +{ + const AudioBackend backend = getEffectiveBackend(); + const std::string driver_name = m_dialog->getEffectiveStringValue("Audio", "Driver", ""); + const std::string current_device = m_dialog->getEffectiveStringValue("Audio", "Device", ""); + const std::vector devices = AudioStream::GetOutputDevices(backend, driver_name.c_str()); + m_ui.outputDevice->disconnect(); m_ui.outputDevice->clear(); - if (names.empty()) + m_output_device_latency = 0; + + if (devices.empty()) { m_ui.outputDevice->addItem(tr("Default")); m_ui.outputDevice->setEnabled(false); @@ -189,31 +215,79 @@ void AudioSettingsWidget::updateDriverNames() else { m_ui.outputDevice->setEnabled(true); - for (const auto& [id, name] : devices) - m_ui.outputDevice->addItem(QString::fromStdString(name), QString::fromStdString(id)); + for (const AudioStream::DeviceInfo& di : devices) + { + m_ui.outputDevice->addItem(QString::fromStdString(di.display_name), QString::fromStdString(di.name)); + if (di.name == current_device) + m_output_device_latency = di.minimum_latency_frames; + } SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.outputDevice, "Audio", - "OutputDevice", std::move(devices.front().first)); + "OutputDevice", std::move(devices.front().name)); } + + updateLatencyLabel(); } void AudioSettingsWidget::updateLatencyLabel() { - const u32 output_latency_ms = static_cast(m_ui.outputLatencyMS->value()); - const u32 output_latency_frames = AudioStream::GetBufferSizeForMS(SPU::SAMPLE_RATE, output_latency_ms); - const u32 buffer_ms = static_cast(m_ui.bufferMS->value()); - const u32 buffer_frames = AudioStream::GetBufferSizeForMS(SPU::SAMPLE_RATE, buffer_ms); + const u32 expand_buffer_ms = AudioStream::GetMSForBufferSize(SPU::SAMPLE_RATE, getEffectiveExpansionBlockSize()); + const u32 config_buffer_ms = + m_dialog->getEffectiveIntValue("Audio", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS); + const u32 config_output_latency_ms = + m_dialog->getEffectiveIntValue("Audio", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS); + const bool minimal_output = (config_output_latency_ms == 0); + + //: Preserve the %1 variable, adapt the latter ms (and/or any possible spaces in between) to your language's ruleset. + m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(config_output_latency_ms)); + + const u32 output_latency_ms = minimal_output ? + AudioStream::GetMSForBufferSize(SPU::SAMPLE_RATE, m_output_device_latency) : + config_output_latency_ms; if (output_latency_ms > 0) { - m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 frames / %2 ms (%3ms buffer + %5ms output)") - .arg(buffer_frames + output_latency_frames) - .arg(buffer_ms + output_latency_ms) - .arg(buffer_ms) - .arg(output_latency_ms)); + if (expand_buffer_ms > 0) + { + m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms expand + %4 ms output)") + .arg(config_buffer_ms + expand_buffer_ms + output_latency_ms) + .arg(config_buffer_ms) + .arg(expand_buffer_ms) + .arg(output_latency_ms)); + } + else + { + m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms output)") + .arg(config_buffer_ms + output_latency_ms) + .arg(config_buffer_ms) + .arg(output_latency_ms)); + } } else { - m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 frames / %2 ms").arg(buffer_frames).arg(buffer_ms)); + if (expand_buffer_ms > 0) + { + m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms expand, minimum output latency unknown)") + .arg(expand_buffer_ms + config_buffer_ms) + .arg(expand_buffer_ms)); + } + else + { + m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (minimum output latency unknown)").arg(config_buffer_ms)); + } + } + + const u32 value = + m_dialog->getEffectiveIntValue("Audio", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS); + + { + QSignalBlocker sb(m_ui.outputLatencyMS); + m_ui.outputLatencyMS->setValue(value); + m_ui.outputLatencyMS->setEnabled(value != 0); + } + + { + QSignalBlocker sb(m_ui.outputLatencyMinimal); + m_ui.outputLatencyMinimal->setChecked(value == 0); } } @@ -223,13 +297,11 @@ void AudioSettingsWidget::updateVolumeLabel() m_ui.fastForwardVolumeLabel->setText(tr("%1%").arg(m_ui.fastForwardVolume->value())); } -void AudioSettingsWidget::onMinimalOutputLatencyChecked(bool new_value) +void AudioSettingsWidget::onMinimalOutputLatencyChecked(Qt::CheckState state) { - const u32 value = new_value ? 0u : AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS; + const u32 value = (state == Qt::Checked) ? 0u : AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS; m_dialog->setIntSettingValue("Audio", "OutputLatencyMS", value); - QSignalBlocker sb(m_ui.outputLatencyMS); - m_ui.outputLatencyMS->setValue(value); - m_ui.outputLatencyMS->setEnabled(!new_value); + updateLatencyLabel(); } @@ -352,6 +424,7 @@ void AudioSettingsWidget::onExpansionSettingsClicked() }); dlg.exec(); + updateLatencyLabel(); } void AudioSettingsWidget::onStretchSettingsClicked() @@ -405,4 +478,4 @@ void AudioSettingsWidget::onStretchSettingsClicked() }); dlg.exec(); -} +} \ No newline at end of file diff --git a/src/duckstation-qt/audiosettingswidget.h b/src/duckstation-qt/audiosettingswidget.h index b6b75a647..61a94e615 100644 --- a/src/duckstation-qt/audiosettingswidget.h +++ b/src/duckstation-qt/audiosettingswidget.h @@ -1,11 +1,16 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once +#include "ui_audiosettingswidget.h" + +#include "common/types.h" + #include -#include "ui_audiosettingswidget.h" +enum class AudioBackend : u8; +enum class AudioExpansionMode : u8; class SettingsWindow; @@ -14,7 +19,7 @@ class AudioSettingsWidget : public QWidget Q_OBJECT public: - explicit AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent); + AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent); ~AudioSettingsWidget(); private Q_SLOTS: @@ -22,9 +27,10 @@ private Q_SLOTS: void onStretchModeChanged(); void updateDriverNames(); + void updateDeviceNames(); void updateLatencyLabel(); void updateVolumeLabel(); - void onMinimalOutputLatencyChecked(bool new_value); + void onMinimalOutputLatencyChecked(Qt::CheckState state); void onOutputVolumeChanged(int new_value); void onFastForwardVolumeChanged(int new_value); void onOutputMutedChanged(int new_state); @@ -33,7 +39,11 @@ private Q_SLOTS: void onStretchSettingsClicked(); private: - Ui::AudioSettingsWidget m_ui; + AudioBackend getEffectiveBackend() const; + AudioExpansionMode getEffectiveExpansionMode() const; + u32 getEffectiveExpansionBlockSize() const; + Ui::AudioSettingsWidget m_ui; SettingsWindow* m_dialog; + u32 m_output_device_latency = 0; }; diff --git a/src/duckstation-qt/audiosettingswidget.ui b/src/duckstation-qt/audiosettingswidget.ui index 6f7f9d3c1..2038a5d78 100644 --- a/src/duckstation-qt/audiosettingswidget.ui +++ b/src/duckstation-qt/audiosettingswidget.ui @@ -91,6 +91,13 @@ + + + + 0 ms + + + @@ -207,7 +214,7 @@ - 100 + 200 100 @@ -246,7 +253,7 @@ - 100 + 200 100 diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index cadcc4bcd..b9e84e8d6 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -217,12 +217,11 @@ if(WIN32) platform_misc_win32.cpp win32_raw_input_source.cpp win32_raw_input_source.h - xaudio2_audio_stream.cpp xinput_source.cpp xinput_source.h ) target_link_libraries(util PRIVATE d3d12ma) - target_link_libraries(util PRIVATE d3d11.lib d3d12.lib d3dcompiler.lib dxgi.lib winmm.lib Dwmapi.lib winhttp.lib xaudio2.lib) + target_link_libraries(util PRIVATE d3d11.lib d3d12.lib d3dcompiler.lib dxgi.lib winmm.lib Dwmapi.lib winhttp.lib) if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime) diff --git a/src/util/audio_stream.cpp b/src/util/audio_stream.cpp index 71196d004..6987484ef 100644 --- a/src/util/audio_stream.cpp +++ b/src/util/audio_stream.cpp @@ -38,6 +38,13 @@ static constexpr const std::array, static_cast(AudioEx {u8(8), u8(8)}, // Surround71 }}; +AudioStream::DeviceInfo::DeviceInfo(std::string name_, std::string display_name_, u32 minimum_latency_) + : name(std::move(name_)), display_name(std::move(display_name_)), minimum_latency_frames(minimum_latency_) +{ +} + +AudioStream::DeviceInfo::~DeviceInfo() = default; + AudioStream::AudioStream(u32 sample_rate, const AudioStreamParameters& parameters) : m_sample_rate(sample_rate), m_parameters(parameters), m_internal_channels(s_expansion_channel_count[static_cast(parameters.expansion_mode)].first), @@ -65,22 +72,50 @@ std::unique_ptr AudioStream::CreateNullStream(u32 sample_rate, u32 #ifndef __ANDROID__ +std::vector AudioStream::GetDriverNames(AudioBackend backend) +{ + std::vector ret; + switch (backend) + { + case AudioBackend::Cubeb: + ret = GetCubebDriverNames(); + break; + + default: + break; + } + + return ret; +} + +std::vector AudioStream::GetOutputDevices(AudioBackend backend, const char* driver) +{ + std::vector ret; + switch (backend) + { + case AudioBackend::Cubeb: + ret = GetCubebOutputDevices(driver); + break; + + default: + break; + } + + return ret; +} + std::unique_ptr AudioStream::CreateStream(AudioBackend backend, u32 sample_rate, - const AudioStreamParameters& parameters, Error* error) + const AudioStreamParameters& parameters, const char* driver_name, + const char* device_name, Error* error /* = nullptr */) { switch (backend) { case AudioBackend::Cubeb: - return CreateCubebAudioStream(sample_rate, parameters, error); + return CreateCubebAudioStream(sample_rate, parameters, driver_name, device_name, error); case AudioBackend::SDL: return CreateSDLAudioStream(sample_rate, parameters, error); -#ifdef _WIN32 - case AudioBackend::XAudio2: - return CreateXAudio2Stream(sample_rate, parameters, error); -#endif - case AudioBackend::Null: return CreateNullStream(sample_rate, parameters.buffer_ms); @@ -118,9 +153,6 @@ static constexpr const std::array s_backend_names = { "AAudio", "OpenSLES", #endif -#ifdef _WIN32 - "XAudio2", -#endif }; static constexpr const std::array s_backend_display_names = { TRANSLATE_NOOP("AudioStream", "Null (No Output)"), @@ -131,9 +163,6 @@ static constexpr const std::array s_backend_display_names = { "AAudio", "OpenSL ES", #endif -#ifdef _WIN32 - TRANSLATE_NOOP("AudioStream", "XAudio2"), -#endif }; std::optional AudioStream::ParseBackendName(const char* str) @@ -327,6 +356,19 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames) std::memset(samples + (frames_to_read * m_output_channels), 0, silence_frames * m_output_channels * sizeof(s16)); } } + + if (m_volume != 100) + { + const s32 volume_mult = static_cast((static_cast(m_volume) / 100.0f) * 32768.0f); + + u32 num_samples = num_frames * m_output_channels; + while (num_samples > 0) + { + *samples = static_cast((static_cast(*samples) * volume_mult) >> 15); + samples++; + num_samples--; + } + } } void AudioStream::StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames) @@ -334,21 +376,6 @@ void AudioStream::StereoSampleReaderImpl(SampleType* dest, const SampleType* src std::memcpy(dest, src, num_frames * 2 * sizeof(SampleType)); } -void AudioStream::ApplyVolume(s16* samples, u32 num_samples) -{ - if (m_volume == 100) - return; - - const s32 volume_mult = static_cast((static_cast(m_volume) / 100.0f) * 32768.0f); - - while (num_samples > 0) - { - *samples = static_cast((static_cast(*samples) * volume_mult) >> 15); - samples++; - num_samples--; - } -} - void AudioStream::InternalWriteFrames(s16* data, u32 num_frames) { const u32 free = m_buffer_size - GetBufferedFramesRelaxed(); diff --git a/src/util/audio_stream.h b/src/util/audio_stream.h index aee948b72..c9f6dc99e 100644 --- a/src/util/audio_stream.h +++ b/src/util/audio_stream.h @@ -34,9 +34,6 @@ enum class AudioBackend : u8 #else AAudio, OpenSLES, -#endif -#ifdef _WIN32 - XAudio2, #endif Count }; @@ -93,7 +90,7 @@ struct AudioStreamParameters static constexpr u16 DEFAULT_BUFFER_MS = 100; static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20; #endif - static constexpr u16 DEFAULT_EXPAND_BLOCK_SIZE = 1024; + static constexpr u16 DEFAULT_EXPAND_BLOCK_SIZE = 2048; static constexpr float DEFAULT_EXPAND_CIRCULAR_WRAP = 90.0f; static constexpr float DEFAULT_EXPAND_SHIFT = 0.0f; static constexpr float DEFAULT_EXPAND_DEPTH = 1.0f; @@ -136,6 +133,16 @@ public: static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::AAudio; #endif + struct DeviceInfo + { + std::string name; + std::string display_name; + u32 minimum_latency_frames; + + DeviceInfo(std::string name_, std::string display_name_, u32 minimum_latency_); + ~DeviceInfo(); + }; + public: virtual ~AudioStream(); @@ -169,7 +176,7 @@ public: /// Temporarily pauses the stream, preventing it from requesting data. virtual void SetPaused(bool paused); - virtual void SetOutputVolume(u32 volume); + void SetOutputVolume(u32 volume); void BeginWrite(SampleType** buffer_ptr, u32* num_frames); void WriteFrames(const SampleType* frames, u32 num_frames); @@ -184,15 +191,13 @@ public: void SetStretchMode(AudioStretchMode mode); + static std::vector GetDriverNames(AudioBackend backend); + static std::vector GetOutputDevices(AudioBackend backend, const char* driver); static std::unique_ptr CreateStream(AudioBackend backend, u32 sample_rate, - const AudioStreamParameters& parameters, Error* error = nullptr); + const AudioStreamParameters& parameters, const char* driver_name, + const char* device_name, Error* error = nullptr); static std::unique_ptr CreateNullStream(u32 sample_rate, u32 buffer_ms); -#ifndef __ANDROID__ - static std::vector GetCubebDriverNames(); - static std::vector> GetCubebOutputDevices(const char* driver); -#endif - protected: enum ReadChannel : u8 { @@ -220,10 +225,8 @@ protected: static void SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames); static void StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames); - void ApplyVolume(SampleType* samples, u32 num_samples); - u32 m_sample_rate = 0; - u32 m_volume = 0; + u32 m_volume = 100; AudioStreamParameters m_parameters; u8 m_internal_channels = 0; u8 m_output_channels = 0; @@ -237,6 +240,16 @@ private: static constexpr u32 STRETCH_RESET_THRESHOLD = 5; static constexpr u32 TARGET_IPS = 691; +#ifndef __ANDROID__ + static std::vector GetCubebDriverNames(); + static std::vector GetCubebOutputDevices(const char* driver); + static std::unique_ptr CreateCubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters, + const char* driver_name, const char* device_name, + Error* error); + static std::unique_ptr CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters, + Error* error); +#endif + ALWAYS_INLINE bool IsExpansionEnabled() const { return m_parameters.expansion_mode != AudioExpansionMode::Disabled; } ALWAYS_INLINE bool IsStretchEnabled() const { return m_parameters.stretch_mode != AudioStretchMode::Off; } @@ -294,17 +307,6 @@ private: float* m_expand_output_buffer = nullptr; u32 m_expand_buffer_pos = 0; #endif - -#ifndef __ANDROID__ - static std::unique_ptr CreateCubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters, - Error* error); - static std::unique_ptr CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters, - Error* error); -#endif -#ifdef _WIN32 - static std::unique_ptr CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters, - Error* error); -#endif }; template(m_parameters.expansion_mode)].second); - m_volume = 100; - m_paused = false; char stream_name[32]; std::snprintf(stream_name, sizeof(stream_name), "%p", this); @@ -302,26 +299,13 @@ void CubebAudioStream::SetPaused(bool paused) m_paused = paused; } -void CubebAudioStream::SetOutputVolume(u32 volume) -{ - if (volume == m_volume) - return; - - int rv = cubeb_stream_set_volume(stream, static_cast(volume) / 100.0f); - if (rv != CUBEB_OK) - { - Log_ErrorPrintf("cubeb_stream_set_volume() failed: %d", rv); - return; - } - - m_volume = volume; -} - std::unique_ptr AudioStream::CreateCubebAudioStream(u32 sample_rate, - const AudioStreamParameters& parameters, Error* error) + const AudioStreamParameters& parameters, + const char* driver_name, const char* device_name, + Error* error) { std::unique_ptr stream = std::make_unique(sample_rate, parameters); - if (!stream->Initialize(error)) + if (!stream->Initialize(driver_name, device_name, error)) stream.reset(); return stream; } @@ -335,16 +319,16 @@ std::vector AudioStream::GetCubebDriverNames() return names; } -std::vector> AudioStream::GetCubebOutputDevices(const char* driver) +std::vector AudioStream::GetCubebOutputDevices(const char* driver) { - std::vector> ret; - ret.emplace_back(std::string(), TRANSLATE_STR("CommonHost", "Default Output Device")); + std::vector ret; + ret.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default Output Device"), 0); cubeb* context; int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr); if (rv != CUBEB_OK) { - Log_ErrorPrintf("cubeb_init() failed: %d", rv); + Log_ErrorFmt("cubeb_init() failed: {}", GetCubebErrorString(rv)); return ret; } @@ -354,19 +338,31 @@ std::vector> AudioStream::GetCubebOutputDevi rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices); if (rv != CUBEB_OK) { - Log_ErrorPrintf("cubeb_enumerate_devices() failed: %d", rv); + Log_ErrorFmt("cubeb_enumerate_devices() failed: {}", GetCubebErrorString(rv)); return ret; } ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); }); + // we need stream parameters to query latency + cubeb_stream_params params = {}; + params.format = CUBEB_SAMPLE_S16LE; + params.rate = 48000; + params.channels = 2; + params.layout = CUBEB_LAYOUT_UNDEFINED; + params.prefs = CUBEB_STREAM_PREF_NONE; + + u32 min_latency = 0; + cubeb_get_min_latency(context, ¶ms, &min_latency); + ret[0].minimum_latency_frames = min_latency; + for (size_t i = 0; i < devices.count; i++) { const cubeb_device_info& di = devices.device[i]; if (!di.device_id) continue; - ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id); + ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, min_latency); } return ret; diff --git a/src/util/sdl_audio_stream.cpp b/src/util/sdl_audio_stream.cpp index 75c8b3f21..43ea953c8 100644 --- a/src/util/sdl_audio_stream.cpp +++ b/src/util/sdl_audio_stream.cpp @@ -19,7 +19,6 @@ public: ~SDLAudioStream(); void SetPaused(bool paused) override; - void SetOutputVolume(u32 volume) override; bool OpenDevice(Error* error); void CloseDevice(); @@ -120,8 +119,6 @@ bool SDLAudioStream::OpenDevice(Error* error) Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples); BaseInitialize(sample_readers[static_cast(m_parameters.expansion_mode)]); - m_volume = 100; - m_paused = false; SDL_PauseAudioDevice(m_device_id, 0); return true; @@ -148,10 +145,4 @@ void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len) const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_output_channels; this_ptr->ReadFrames(reinterpret_cast(stream), num_frames); - this_ptr->ApplyVolume(reinterpret_cast(stream), num_frames); -} - -void SDLAudioStream::SetOutputVolume(u32 volume) -{ - m_volume = volume; } diff --git a/src/util/util.props b/src/util/util.props index 033b23ba8..7855c2811 100644 --- a/src/util/util.props +++ b/src/util/util.props @@ -14,7 +14,7 @@ - %(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib;xaudio2.lib + %(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib %(AdditionalDependencies);opengl32.lib diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index 43238b6df..a5b344708 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -230,7 +230,6 @@ - diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 5a8a32282..344d85c2c 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -105,7 +105,6 @@ - diff --git a/src/util/xaudio2_audio_stream.cpp b/src/util/xaudio2_audio_stream.cpp deleted file mode 100644 index ae077c39b..000000000 --- a/src/util/xaudio2_audio_stream.cpp +++ /dev/null @@ -1,291 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#include "util/audio_stream.h" - -#include "common/assert.h" -#include "common/error.h" -#include "common/log.h" -#include "common/windows_headers.h" - -#include -#include -#include -#include -#include - -Log_SetChannel(XAudio2AudioStream); - -namespace { - -class XAudio2AudioStream final : public AudioStream, private IXAudio2VoiceCallback -{ -public: - XAudio2AudioStream(u32 sample_rate, const AudioStreamParameters& parameters); - ~XAudio2AudioStream(); - - void SetPaused(bool paused) override; - void SetOutputVolume(u32 volume) override; - - bool OpenDevice(Error* error); - void CloseDevice(); - void EnqueueBuffer(); - -private: - enum : u32 - { - NUM_BUFFERS = 2, - INTERNAL_BUFFER_SIZE = 512, - }; - - ALWAYS_INLINE bool IsOpen() const { return static_cast(m_xaudio); } - - // Inherited via IXAudio2VoiceCallback - void __stdcall OnVoiceProcessingPassStart(UINT32 BytesRequired) override; - void __stdcall OnVoiceProcessingPassEnd(void) override; - void __stdcall OnStreamEnd(void) override; - void __stdcall OnBufferStart(void* pBufferContext) override; - void __stdcall OnBufferEnd(void* pBufferContext) override; - void __stdcall OnLoopEnd(void* pBufferContext) override; - void __stdcall OnVoiceError(void* pBufferContext, HRESULT Error) override; - - Microsoft::WRL::ComPtr m_xaudio; - IXAudio2MasteringVoice* m_mastering_voice = nullptr; - IXAudio2SourceVoice* m_source_voice = nullptr; - - std::array, NUM_BUFFERS> m_enqueue_buffers; - u32 m_enqueue_buffer_size = 0; - u32 m_current_buffer = 0; - bool m_buffer_enqueued = false; - bool m_com_initialized_by_us = false; -}; - -} // namespace - -XAudio2AudioStream::XAudio2AudioStream(u32 sample_rate, const AudioStreamParameters& parameters) - : AudioStream(sample_rate, parameters) -{ -} - -XAudio2AudioStream::~XAudio2AudioStream() -{ - if (IsOpen()) - CloseDevice(); - - if (m_com_initialized_by_us) - CoUninitialize(); -} - -std::unique_ptr AudioStream::CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters, - Error* error) -{ - std::unique_ptr stream(std::make_unique(sample_rate, parameters)); - if (!stream->OpenDevice(error)) - stream.reset(); - return stream; -} - -bool XAudio2AudioStream::OpenDevice(Error* error) -{ - DebugAssert(!IsOpen()); - - if (m_parameters.expansion_mode == AudioExpansionMode::QuadraphonicLFE) - { - Log_ErrorPrint("QuadraphonicLFE is not supported by XAudio2."); - return false; - } - - static constexpr const std::array(AudioExpansionMode::Count)> sample_readers = {{ - // Disabled - &StereoSampleReaderImpl, - // StereoLFE - &SampleReaderImpl, - // Quadraphonic - &SampleReaderImpl, - // QuadraphonicLFE - nullptr, - // Surround51 - &SampleReaderImpl, - // Surround71 - &SampleReaderImpl, - }}; - - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - m_com_initialized_by_us = SUCCEEDED(hr); - if (FAILED(hr) && hr != RPC_E_CHANGED_MODE && hr != S_FALSE) - { - Error::SetHResult(error, "CoInitializeEx() failed: ", hr); - return false; - } - - hr = XAudio2Create(m_xaudio.ReleaseAndGetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR); - if (FAILED(hr)) - { - Error::SetHResult(error, "XAudio2Create() failed: ", hr); - return false; - } - - hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_output_channels, m_sample_rate, 0, nullptr); - if (FAILED(hr)) - { - Error::SetHResult(error, "CreateMasteringVoice() failed: ", hr); - return false; - } - - // TODO: CHANNEL LAYOUT - WAVEFORMATEX wf = {}; - wf.cbSize = sizeof(wf); - wf.nAvgBytesPerSec = m_sample_rate * m_output_channels * sizeof(s16); - wf.nBlockAlign = static_cast(sizeof(s16) * m_output_channels); - wf.nChannels = static_cast(m_output_channels); - wf.nSamplesPerSec = m_sample_rate; - wf.wBitsPerSample = sizeof(s16) * 8; - wf.wFormatTag = WAVE_FORMAT_PCM; - hr = m_xaudio->CreateSourceVoice(&m_source_voice, &wf, 0, 1.0f, this); - if (FAILED(hr)) - { - Error::SetHResult(error, "CreateMasteringVoice() failed: ", hr); - return false; - } - - hr = m_source_voice->SetFrequencyRatio(1.0f); - if (FAILED(hr)) - { - Error::SetHResult(error, "SetFrequencyRatio() failed: ", hr); - return false; - } - - m_enqueue_buffer_size = - std::max(INTERNAL_BUFFER_SIZE, GetBufferSizeForMS(m_sample_rate, (m_parameters.output_latency_ms == 0) ? - m_parameters.buffer_ms : - m_parameters.output_latency_ms)); - Log_DevPrintf("Allocating %u buffers of %u frames", NUM_BUFFERS, m_enqueue_buffer_size); - for (u32 i = 0; i < NUM_BUFFERS; i++) - m_enqueue_buffers[i] = std::make_unique(m_enqueue_buffer_size * m_output_channels); - - BaseInitialize(sample_readers[static_cast(m_parameters.expansion_mode)]); - m_volume = 100; - m_paused = false; - - hr = m_source_voice->Start(0, 0); - if (FAILED(hr)) - { - Error::SetHResult(error, "Start() failed: ", hr); - return false; - } - - EnqueueBuffer(); - return true; -} - -void XAudio2AudioStream::SetPaused(bool paused) -{ - if (m_paused == paused) - return; - - if (paused) - { - HRESULT hr = m_source_voice->Stop(0, 0); - if (FAILED(hr)) - Log_ErrorPrintf("Stop() failed: %08X", hr); - } - else - { - HRESULT hr = m_source_voice->Start(0, 0); - if (FAILED(hr)) - Log_ErrorPrintf("Start() failed: %08X", hr); - } - - m_paused = paused; - - if (!m_buffer_enqueued) - EnqueueBuffer(); -} - -void XAudio2AudioStream::CloseDevice() -{ - HRESULT hr; - if (!m_paused) - { - hr = m_source_voice->Stop(0, 0); - if (FAILED(hr)) - Log_ErrorPrintf("Stop() failed: %08X", hr); - } - - m_source_voice = nullptr; - m_mastering_voice = nullptr; - m_xaudio.Reset(); - m_enqueue_buffers = {}; - m_current_buffer = 0; - m_paused = true; -} - -void XAudio2AudioStream::EnqueueBuffer() -{ - SampleType* samples = m_enqueue_buffers[m_current_buffer].get(); - ReadFrames(samples, m_enqueue_buffer_size); - - const XAUDIO2_BUFFER buf = { - static_cast(0), // flags - static_cast(sizeof(s16) * m_output_channels * m_enqueue_buffer_size), // bytes - reinterpret_cast(samples), // data - 0u, - 0u, - 0u, - 0u, - 0u, - nullptr, - }; - - HRESULT hr = m_source_voice->SubmitSourceBuffer(&buf, nullptr); - if (FAILED(hr)) - Log_ErrorPrintf("SubmitSourceBuffer() failed: %08X", hr); - - m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS; -} - -void XAudio2AudioStream::SetOutputVolume(u32 volume) -{ - HRESULT hr = m_mastering_voice->SetVolume(static_cast(m_volume) / 100.0f); - if (FAILED(hr)) - { - Log_ErrorPrintf("SetVolume() failed: %08X", hr); - return; - } - - m_volume = volume; -} - -void __stdcall XAudio2AudioStream::OnVoiceProcessingPassStart(UINT32 BytesRequired) -{ -} - -void __stdcall XAudio2AudioStream::OnVoiceProcessingPassEnd(void) -{ -} - -void __stdcall XAudio2AudioStream::OnStreamEnd(void) -{ -} - -void __stdcall XAudio2AudioStream::OnBufferStart(void* pBufferContext) -{ -} - -void __stdcall XAudio2AudioStream::OnBufferEnd(void* pBufferContext) -{ - EnqueueBuffer(); -} - -void __stdcall XAudio2AudioStream::OnLoopEnd(void* pBufferContext) -{ -} - -void __stdcall XAudio2AudioStream::OnVoiceError(void* pBufferContext, HRESULT Error) -{ -}