AudioStream: Backport changes

This commit is contained in:
Stenzek 2024-04-25 01:13:51 +10:00
parent 921f5119b2
commit 4139bf63d8
No known key found for this signature in database
13 changed files with 256 additions and 443 deletions

View File

@ -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(

View File

@ -12,6 +12,7 @@
#include "util/audio_stream.h"
#include <bit>
#include <cmath>
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<std::string> names;
std::vector<std::pair<std::string, std::string>> 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<std::string> 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<AudioStream::DeviceInfo> 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<u32>(m_ui.outputLatencyMS->value());
const u32 output_latency_frames = AudioStream::GetBufferSizeForMS(SPU::SAMPLE_RATE, output_latency_ms);
const u32 buffer_ms = static_cast<u32>(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();
}
}

View File

@ -1,11 +1,16 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "ui_audiosettingswidget.h"
#include "common/types.h"
#include <QtWidgets/QWidget>
#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;
};

View File

@ -91,6 +91,13 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="outputLatencyLabel">
<property name="text">
<string>0 ms</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="outputLatencyMinimal">
<property name="text">
@ -207,7 +214,7 @@
<item>
<widget class="QSlider" name="volume">
<property name="maximum">
<number>100</number>
<number>200</number>
</property>
<property name="value">
<number>100</number>
@ -246,7 +253,7 @@
<item>
<widget class="QSlider" name="fastForwardVolume">
<property name="maximum">
<number>100</number>
<number>200</number>
</property>
<property name="value">
<number>100</number>

View File

@ -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)

View File

@ -38,6 +38,13 @@ static constexpr const std::array<std::pair<u8, u8>, static_cast<size_t>(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<size_t>(parameters.expansion_mode)].first),
@ -65,22 +72,50 @@ std::unique_ptr<AudioStream> AudioStream::CreateNullStream(u32 sample_rate, u32
#ifndef __ANDROID__
std::vector<std::string> AudioStream::GetDriverNames(AudioBackend backend)
{
std::vector<std::string> ret;
switch (backend)
{
case AudioBackend::Cubeb:
ret = GetCubebDriverNames();
break;
default:
break;
}
return ret;
}
std::vector<AudioStream::DeviceInfo> AudioStream::GetOutputDevices(AudioBackend backend, const char* driver)
{
std::vector<AudioStream::DeviceInfo> ret;
switch (backend)
{
case AudioBackend::Cubeb:
ret = GetCubebOutputDevices(driver);
break;
default:
break;
}
return ret;
}
std::unique_ptr<AudioStream> 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<AudioBackend> 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<s32>((static_cast<float>(m_volume) / 100.0f) * 32768.0f);
u32 num_samples = num_frames * m_output_channels;
while (num_samples > 0)
{
*samples = static_cast<s16>((static_cast<s32>(*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<s32>((static_cast<float>(m_volume) / 100.0f) * 32768.0f);
while (num_samples > 0)
{
*samples = static_cast<s16>((static_cast<s32>(*samples) * volume_mult) >> 15);
samples++;
num_samples--;
}
}
void AudioStream::InternalWriteFrames(s16* data, u32 num_frames)
{
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();

View File

@ -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<std::string> GetDriverNames(AudioBackend backend);
static std::vector<DeviceInfo> GetOutputDevices(AudioBackend backend, const char* driver);
static std::unique_ptr<AudioStream> 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<AudioStream> CreateNullStream(u32 sample_rate, u32 buffer_ms);
#ifndef __ANDROID__
static std::vector<std::string> GetCubebDriverNames();
static std::vector<std::pair<std::string, std::string>> 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<std::string> GetCubebDriverNames();
static std::vector<DeviceInfo> GetCubebOutputDevices(const char* driver);
static std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
const char* driver_name, const char* device_name,
Error* error);
static std::unique_ptr<AudioStream> 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<AudioStream> CreateCubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
Error* error);
static std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
Error* error);
#endif
#ifdef _WIN32
static std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters,
Error* error);
#endif
};
template<AudioExpansionMode mode, AudioStream::ReadChannel c0, AudioStream::ReadChannel c1, AudioStream::ReadChannel c2,

View File

@ -31,9 +31,8 @@ public:
~CubebAudioStream();
void SetPaused(bool paused) override;
void SetOutputVolume(u32 volume) override;
bool Initialize(Error* error);
bool Initialize(const char* driver_name, const char* device_name, Error* error);
private:
static void LogCallback(const char* fmt, ...);
@ -122,7 +121,7 @@ void CubebAudioStream::DestroyContextAndStream()
#endif
}
bool CubebAudioStream::Initialize(Error* error)
bool CubebAudioStream::Initialize(const char* driver_name, const char* device_name, Error* error)
{
#ifdef _WIN32
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
@ -245,8 +244,6 @@ bool CubebAudioStream::Initialize(Error* error)
}
BaseInitialize(channel_setups[static_cast<size_t>(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<float>(volume) / 100.0f);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("cubeb_stream_set_volume() failed: %d", rv);
return;
}
m_volume = volume;
}
std::unique_ptr<AudioStream> 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<CubebAudioStream> stream = std::make_unique<CubebAudioStream>(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<std::string> AudioStream::GetCubebDriverNames()
return names;
}
std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebOutputDevices(const char* driver)
std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const char* driver)
{
std::vector<std::pair<std::string, std::string>> ret;
ret.emplace_back(std::string(), TRANSLATE_STR("CommonHost", "Default Output Device"));
std::vector<AudioStream::DeviceInfo> 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<std::pair<std::string, std::string>> 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, &params, &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;

View File

@ -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<size_t>(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<SampleType*>(stream), num_frames);
this_ptr->ApplyVolume(reinterpret_cast<SampleType*>(stream), num_frames);
}
void SDLAudioStream::SetOutputVolume(u32 volume)
{
m_volume = volume;
}

View File

@ -14,7 +14,7 @@
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib;xaudio2.lib</AdditionalDependencies>
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>

View File

@ -230,7 +230,6 @@
<ClCompile Include="wav_writer.cpp" />
<ClCompile Include="win32_raw_input_source.cpp" />
<ClCompile Include="window_info.cpp" />
<ClCompile Include="xaudio2_audio_stream.cpp" />
<ClCompile Include="xinput_source.cpp" />
<ClCompile Include="zstd_byte_stream.cpp" />
</ItemGroup>

View File

@ -105,7 +105,6 @@
<ClCompile Include="platform_misc_win32.cpp" />
<ClCompile Include="sdl_input_source.cpp" />
<ClCompile Include="win32_raw_input_source.cpp" />
<ClCompile Include="xaudio2_audio_stream.cpp" />
<ClCompile Include="xinput_source.cpp" />
<ClCompile Include="dinput_source.cpp" />
<ClCompile Include="input_manager.cpp" />

View File

@ -1,291 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// 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 <array>
#include <cstdint>
#include <memory>
#include <wrl/client.h>
#include <xaudio2.h>
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<bool>(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<IXAudio2> m_xaudio;
IXAudio2MasteringVoice* m_mastering_voice = nullptr;
IXAudio2SourceVoice* m_source_voice = nullptr;
std::array<std::unique_ptr<SampleType[]>, 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> AudioStream::CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters,
Error* error)
{
std::unique_ptr<XAudio2AudioStream> stream(std::make_unique<XAudio2AudioStream>(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<SampleReader, static_cast<size_t>(AudioExpansionMode::Count)> sample_readers = {{
// Disabled
&StereoSampleReaderImpl,
// StereoLFE
&SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
READ_CHANNEL_LFE>,
// Quadraphonic
&SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
// QuadraphonicLFE
nullptr,
// Surround51
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
// Surround71
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT,
READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT>,
}};
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<WORD>(sizeof(s16) * m_output_channels);
wf.nChannels = static_cast<WORD>(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<u32>(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<SampleType[]>(m_enqueue_buffer_size * m_output_channels);
BaseInitialize(sample_readers[static_cast<size_t>(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<UINT32>(0), // flags
static_cast<UINT32>(sizeof(s16) * m_output_channels * m_enqueue_buffer_size), // bytes
reinterpret_cast<const BYTE*>(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<float>(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)
{
}