AudioStream: Backport changes
This commit is contained in:
parent
921f5119b2
commit
4139bf63d8
|
@ -439,7 +439,8 @@ void SPU::CreateOutputStream()
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
s_audio_stream =
|
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)
|
if (!s_audio_stream)
|
||||||
{
|
{
|
||||||
Host::ReportErrorAsync(
|
Host::ReportErrorAsync(
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "util/audio_stream.h"
|
#include "util/audio_stream.h"
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog)
|
AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog)
|
||||||
|
@ -58,12 +59,10 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent
|
||||||
onStretchModeChanged();
|
onStretchModeChanged();
|
||||||
updateDriverNames();
|
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.bufferMS, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabel);
|
||||||
connect(m_ui.outputLatencyMS, &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();
|
updateLatencyLabel();
|
||||||
|
|
||||||
// for per-game, just use the normal path, since it needs to re-read/apply
|
// 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
|
else
|
||||||
{
|
{
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.volume, "Audio", "OutputVolume", 100);
|
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.volume, m_ui.volumeLabel, tr("%"), "Audio",
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.fastForwardVolume, "Audio", "FastForwardVolume", 100);
|
"OutputVolume", 100);
|
||||||
|
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(sif, m_ui.fastForwardVolume, m_ui.fastForwardVolumeLabel,
|
||||||
|
tr("%"), "Audio", "FastForwardVolume", 100);
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muted, "Audio", "OutputMuted", false);
|
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 "
|
"lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio "
|
||||||
"output."));
|
"output."));
|
||||||
dialog->registerWidgetHelp(
|
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 "
|
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 "
|
"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 "
|
"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;
|
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()
|
void AudioSettingsWidget::onExpansionModeChanged()
|
||||||
{
|
{
|
||||||
const AudioExpansionMode expansion_mode =
|
const AudioExpansionMode expansion_mode = getEffectiveExpansionMode();
|
||||||
AudioStream::ParseExpansionMode(
|
|
||||||
m_dialog
|
|
||||||
->getEffectiveStringValue("Audio", "ExpansionMode",
|
|
||||||
AudioStream::GetExpansionModeName(AudioStreamParameters::DEFAULT_EXPANSION_MODE))
|
|
||||||
.c_str())
|
|
||||||
.value_or(AudioStreamParameters::DEFAULT_EXPANSION_MODE);
|
|
||||||
m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled);
|
m_ui.expansionSettings->setEnabled(expansion_mode != AudioExpansionMode::Disabled);
|
||||||
|
updateLatencyLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSettingsWidget::onStretchModeChanged()
|
void AudioSettingsWidget::onStretchModeChanged()
|
||||||
|
@ -144,22 +161,19 @@ void AudioSettingsWidget::onStretchModeChanged()
|
||||||
m_ui.stretchSettings->setEnabled(stretch_mode != AudioStretchMode::Off);
|
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()
|
void AudioSettingsWidget::updateDriverNames()
|
||||||
{
|
{
|
||||||
const AudioBackend backend =
|
const AudioBackend backend = getEffectiveBackend();
|
||||||
AudioStream::ParseBackendName(
|
const std::vector<std::string> names = AudioStream::GetDriverNames(backend);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ui.driver->disconnect();
|
m_ui.driver->disconnect();
|
||||||
m_ui.driver->clear();
|
m_ui.driver->clear();
|
||||||
|
@ -176,12 +190,24 @@ void AudioSettingsWidget::updateDriverNames()
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.driver, "Audio", "Driver",
|
SettingWidgetBinder::BindWidgetToStringSetting(m_dialog->getSettingsInterface(), m_ui.driver, "Audio", "Driver",
|
||||||
std::move(names.front()));
|
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->disconnect();
|
||||||
m_ui.outputDevice->clear();
|
m_ui.outputDevice->clear();
|
||||||
if (names.empty())
|
m_output_device_latency = 0;
|
||||||
|
|
||||||
|
if (devices.empty())
|
||||||
{
|
{
|
||||||
m_ui.outputDevice->addItem(tr("Default"));
|
m_ui.outputDevice->addItem(tr("Default"));
|
||||||
m_ui.outputDevice->setEnabled(false);
|
m_ui.outputDevice->setEnabled(false);
|
||||||
|
@ -189,31 +215,79 @@ void AudioSettingsWidget::updateDriverNames()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_ui.outputDevice->setEnabled(true);
|
m_ui.outputDevice->setEnabled(true);
|
||||||
for (const auto& [id, name] : devices)
|
for (const AudioStream::DeviceInfo& di : devices)
|
||||||
m_ui.outputDevice->addItem(QString::fromStdString(name), QString::fromStdString(id));
|
{
|
||||||
|
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",
|
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()
|
void AudioSettingsWidget::updateLatencyLabel()
|
||||||
{
|
{
|
||||||
const u32 output_latency_ms = static_cast<u32>(m_ui.outputLatencyMS->value());
|
const u32 expand_buffer_ms = AudioStream::GetMSForBufferSize(SPU::SAMPLE_RATE, getEffectiveExpansionBlockSize());
|
||||||
const u32 output_latency_frames = AudioStream::GetBufferSizeForMS(SPU::SAMPLE_RATE, output_latency_ms);
|
const u32 config_buffer_ms =
|
||||||
const u32 buffer_ms = static_cast<u32>(m_ui.bufferMS->value());
|
m_dialog->getEffectiveIntValue("Audio", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS);
|
||||||
const u32 buffer_frames = AudioStream::GetBufferSizeForMS(SPU::SAMPLE_RATE, 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)
|
if (output_latency_ms > 0)
|
||||||
{
|
{
|
||||||
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 frames / %2 ms (%3ms buffer + %5ms output)")
|
if (expand_buffer_ms > 0)
|
||||||
.arg(buffer_frames + output_latency_frames)
|
{
|
||||||
.arg(buffer_ms + output_latency_ms)
|
m_ui.bufferingLabel->setText(tr("Maximum Latency: %1 ms (%2 ms buffer + %3 ms expand + %4 ms output)")
|
||||||
.arg(buffer_ms)
|
.arg(config_buffer_ms + expand_buffer_ms + output_latency_ms)
|
||||||
.arg(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
|
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()));
|
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);
|
m_dialog->setIntSettingValue("Audio", "OutputLatencyMS", value);
|
||||||
QSignalBlocker sb(m_ui.outputLatencyMS);
|
|
||||||
m_ui.outputLatencyMS->setValue(value);
|
|
||||||
m_ui.outputLatencyMS->setEnabled(!new_value);
|
|
||||||
updateLatencyLabel();
|
updateLatencyLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,6 +424,7 @@ void AudioSettingsWidget::onExpansionSettingsClicked()
|
||||||
});
|
});
|
||||||
|
|
||||||
dlg.exec();
|
dlg.exec();
|
||||||
|
updateLatencyLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSettingsWidget::onStretchSettingsClicked()
|
void AudioSettingsWidget::onStretchSettingsClicked()
|
||||||
|
|
|
@ -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)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_audiosettingswidget.h"
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
#include <QtWidgets/QWidget>
|
#include <QtWidgets/QWidget>
|
||||||
|
|
||||||
#include "ui_audiosettingswidget.h"
|
enum class AudioBackend : u8;
|
||||||
|
enum class AudioExpansionMode : u8;
|
||||||
|
|
||||||
class SettingsWindow;
|
class SettingsWindow;
|
||||||
|
|
||||||
|
@ -14,7 +19,7 @@ class AudioSettingsWidget : public QWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent);
|
AudioSettingsWidget(SettingsWindow* dialog, QWidget* parent);
|
||||||
~AudioSettingsWidget();
|
~AudioSettingsWidget();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
@ -22,9 +27,10 @@ private Q_SLOTS:
|
||||||
void onStretchModeChanged();
|
void onStretchModeChanged();
|
||||||
|
|
||||||
void updateDriverNames();
|
void updateDriverNames();
|
||||||
|
void updateDeviceNames();
|
||||||
void updateLatencyLabel();
|
void updateLatencyLabel();
|
||||||
void updateVolumeLabel();
|
void updateVolumeLabel();
|
||||||
void onMinimalOutputLatencyChecked(bool new_value);
|
void onMinimalOutputLatencyChecked(Qt::CheckState state);
|
||||||
void onOutputVolumeChanged(int new_value);
|
void onOutputVolumeChanged(int new_value);
|
||||||
void onFastForwardVolumeChanged(int new_value);
|
void onFastForwardVolumeChanged(int new_value);
|
||||||
void onOutputMutedChanged(int new_state);
|
void onOutputMutedChanged(int new_state);
|
||||||
|
@ -33,7 +39,11 @@ private Q_SLOTS:
|
||||||
void onStretchSettingsClicked();
|
void onStretchSettingsClicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::AudioSettingsWidget m_ui;
|
AudioBackend getEffectiveBackend() const;
|
||||||
|
AudioExpansionMode getEffectiveExpansionMode() const;
|
||||||
|
u32 getEffectiveExpansionBlockSize() const;
|
||||||
|
|
||||||
|
Ui::AudioSettingsWidget m_ui;
|
||||||
SettingsWindow* m_dialog;
|
SettingsWindow* m_dialog;
|
||||||
|
u32 m_output_device_latency = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -91,6 +91,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="outputLatencyLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>0 ms</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="outputLatencyMinimal">
|
<widget class="QCheckBox" name="outputLatencyMinimal">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -207,7 +214,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSlider" name="volume">
|
<widget class="QSlider" name="volume">
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>100</number>
|
<number>200</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>100</number>
|
<number>100</number>
|
||||||
|
@ -246,7 +253,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSlider" name="fastForwardVolume">
|
<widget class="QSlider" name="fastForwardVolume">
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>100</number>
|
<number>200</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>100</number>
|
<number>100</number>
|
||||||
|
|
|
@ -217,12 +217,11 @@ if(WIN32)
|
||||||
platform_misc_win32.cpp
|
platform_misc_win32.cpp
|
||||||
win32_raw_input_source.cpp
|
win32_raw_input_source.cpp
|
||||||
win32_raw_input_source.h
|
win32_raw_input_source.h
|
||||||
xaudio2_audio_stream.cpp
|
|
||||||
xinput_source.cpp
|
xinput_source.cpp
|
||||||
xinput_source.h
|
xinput_source.h
|
||||||
)
|
)
|
||||||
target_link_libraries(util PRIVATE d3d12ma)
|
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")
|
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||||
target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime)
|
target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime)
|
||||||
|
|
|
@ -38,6 +38,13 @@ static constexpr const std::array<std::pair<u8, u8>, static_cast<size_t>(AudioEx
|
||||||
{u8(8), u8(8)}, // Surround71
|
{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)
|
AudioStream::AudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||||
: m_sample_rate(sample_rate), m_parameters(parameters),
|
: m_sample_rate(sample_rate), m_parameters(parameters),
|
||||||
m_internal_channels(s_expansion_channel_count[static_cast<size_t>(parameters.expansion_mode)].first),
|
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__
|
#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,
|
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)
|
switch (backend)
|
||||||
{
|
{
|
||||||
case AudioBackend::Cubeb:
|
case AudioBackend::Cubeb:
|
||||||
return CreateCubebAudioStream(sample_rate, parameters, error);
|
return CreateCubebAudioStream(sample_rate, parameters, driver_name, device_name, error);
|
||||||
|
|
||||||
case AudioBackend::SDL:
|
case AudioBackend::SDL:
|
||||||
return CreateSDLAudioStream(sample_rate, parameters, error);
|
return CreateSDLAudioStream(sample_rate, parameters, error);
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
case AudioBackend::XAudio2:
|
|
||||||
return CreateXAudio2Stream(sample_rate, parameters, error);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case AudioBackend::Null:
|
case AudioBackend::Null:
|
||||||
return CreateNullStream(sample_rate, parameters.buffer_ms);
|
return CreateNullStream(sample_rate, parameters.buffer_ms);
|
||||||
|
|
||||||
|
@ -118,9 +153,6 @@ static constexpr const std::array s_backend_names = {
|
||||||
"AAudio",
|
"AAudio",
|
||||||
"OpenSLES",
|
"OpenSLES",
|
||||||
#endif
|
#endif
|
||||||
#ifdef _WIN32
|
|
||||||
"XAudio2",
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
static constexpr const std::array s_backend_display_names = {
|
static constexpr const std::array s_backend_display_names = {
|
||||||
TRANSLATE_NOOP("AudioStream", "Null (No Output)"),
|
TRANSLATE_NOOP("AudioStream", "Null (No Output)"),
|
||||||
|
@ -131,9 +163,6 @@ static constexpr const std::array s_backend_display_names = {
|
||||||
"AAudio",
|
"AAudio",
|
||||||
"OpenSL ES",
|
"OpenSL ES",
|
||||||
#endif
|
#endif
|
||||||
#ifdef _WIN32
|
|
||||||
TRANSLATE_NOOP("AudioStream", "XAudio2"),
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<AudioBackend> AudioStream::ParseBackendName(const char* str)
|
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));
|
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)
|
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));
|
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)
|
void AudioStream::InternalWriteFrames(s16* data, u32 num_frames)
|
||||||
{
|
{
|
||||||
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
||||||
|
|
|
@ -34,9 +34,6 @@ enum class AudioBackend : u8
|
||||||
#else
|
#else
|
||||||
AAudio,
|
AAudio,
|
||||||
OpenSLES,
|
OpenSLES,
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
XAudio2,
|
|
||||||
#endif
|
#endif
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
@ -93,7 +90,7 @@ struct AudioStreamParameters
|
||||||
static constexpr u16 DEFAULT_BUFFER_MS = 100;
|
static constexpr u16 DEFAULT_BUFFER_MS = 100;
|
||||||
static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20;
|
static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20;
|
||||||
#endif
|
#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_CIRCULAR_WRAP = 90.0f;
|
||||||
static constexpr float DEFAULT_EXPAND_SHIFT = 0.0f;
|
static constexpr float DEFAULT_EXPAND_SHIFT = 0.0f;
|
||||||
static constexpr float DEFAULT_EXPAND_DEPTH = 1.0f;
|
static constexpr float DEFAULT_EXPAND_DEPTH = 1.0f;
|
||||||
|
@ -136,6 +133,16 @@ public:
|
||||||
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::AAudio;
|
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::AAudio;
|
||||||
#endif
|
#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:
|
public:
|
||||||
virtual ~AudioStream();
|
virtual ~AudioStream();
|
||||||
|
|
||||||
|
@ -169,7 +176,7 @@ public:
|
||||||
/// Temporarily pauses the stream, preventing it from requesting data.
|
/// Temporarily pauses the stream, preventing it from requesting data.
|
||||||
virtual void SetPaused(bool paused);
|
virtual void SetPaused(bool paused);
|
||||||
|
|
||||||
virtual void SetOutputVolume(u32 volume);
|
void SetOutputVolume(u32 volume);
|
||||||
|
|
||||||
void BeginWrite(SampleType** buffer_ptr, u32* num_frames);
|
void BeginWrite(SampleType** buffer_ptr, u32* num_frames);
|
||||||
void WriteFrames(const SampleType* frames, u32 num_frames);
|
void WriteFrames(const SampleType* frames, u32 num_frames);
|
||||||
|
@ -184,15 +191,13 @@ public:
|
||||||
|
|
||||||
void SetStretchMode(AudioStretchMode mode);
|
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,
|
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);
|
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:
|
protected:
|
||||||
enum ReadChannel : u8
|
enum ReadChannel : u8
|
||||||
{
|
{
|
||||||
|
@ -220,10 +225,8 @@ protected:
|
||||||
static void SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
static void SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||||
static void StereoSampleReaderImpl(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_sample_rate = 0;
|
||||||
u32 m_volume = 0;
|
u32 m_volume = 100;
|
||||||
AudioStreamParameters m_parameters;
|
AudioStreamParameters m_parameters;
|
||||||
u8 m_internal_channels = 0;
|
u8 m_internal_channels = 0;
|
||||||
u8 m_output_channels = 0;
|
u8 m_output_channels = 0;
|
||||||
|
@ -237,6 +240,16 @@ private:
|
||||||
static constexpr u32 STRETCH_RESET_THRESHOLD = 5;
|
static constexpr u32 STRETCH_RESET_THRESHOLD = 5;
|
||||||
static constexpr u32 TARGET_IPS = 691;
|
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 IsExpansionEnabled() const { return m_parameters.expansion_mode != AudioExpansionMode::Disabled; }
|
||||||
ALWAYS_INLINE bool IsStretchEnabled() const { return m_parameters.stretch_mode != AudioStretchMode::Off; }
|
ALWAYS_INLINE bool IsStretchEnabled() const { return m_parameters.stretch_mode != AudioStretchMode::Off; }
|
||||||
|
|
||||||
|
@ -294,17 +307,6 @@ private:
|
||||||
float* m_expand_output_buffer = nullptr;
|
float* m_expand_output_buffer = nullptr;
|
||||||
u32 m_expand_buffer_pos = 0;
|
u32 m_expand_buffer_pos = 0;
|
||||||
#endif
|
#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,
|
template<AudioExpansionMode mode, AudioStream::ReadChannel c0, AudioStream::ReadChannel c1, AudioStream::ReadChannel c2,
|
||||||
|
|
|
@ -31,9 +31,8 @@ public:
|
||||||
~CubebAudioStream();
|
~CubebAudioStream();
|
||||||
|
|
||||||
void SetPaused(bool paused) override;
|
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:
|
private:
|
||||||
static void LogCallback(const char* fmt, ...);
|
static void LogCallback(const char* fmt, ...);
|
||||||
|
@ -122,7 +121,7 @@ void CubebAudioStream::DestroyContextAndStream()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CubebAudioStream::Initialize(Error* error)
|
bool CubebAudioStream::Initialize(const char* driver_name, const char* device_name, Error* error)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
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);
|
BaseInitialize(channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].second);
|
||||||
m_volume = 100;
|
|
||||||
m_paused = false;
|
|
||||||
|
|
||||||
char stream_name[32];
|
char stream_name[32];
|
||||||
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
|
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
|
||||||
|
@ -302,26 +299,13 @@ void CubebAudioStream::SetPaused(bool paused)
|
||||||
m_paused = 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,
|
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);
|
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();
|
stream.reset();
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
@ -335,16 +319,16 @@ std::vector<std::string> AudioStream::GetCubebDriverNames()
|
||||||
return names;
|
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;
|
std::vector<AudioStream::DeviceInfo> ret;
|
||||||
ret.emplace_back(std::string(), TRANSLATE_STR("CommonHost", "Default Output Device"));
|
ret.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default Output Device"), 0);
|
||||||
|
|
||||||
cubeb* context;
|
cubeb* context;
|
||||||
int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
|
int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
|
||||||
if (rv != CUBEB_OK)
|
if (rv != CUBEB_OK)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("cubeb_init() failed: %d", rv);
|
Log_ErrorFmt("cubeb_init() failed: {}", GetCubebErrorString(rv));
|
||||||
return ret;
|
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);
|
rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
|
||||||
if (rv != CUBEB_OK)
|
if (rv != CUBEB_OK)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("cubeb_enumerate_devices() failed: %d", rv);
|
Log_ErrorFmt("cubeb_enumerate_devices() failed: {}", GetCubebErrorString(rv));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); });
|
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++)
|
for (size_t i = 0; i < devices.count; i++)
|
||||||
{
|
{
|
||||||
const cubeb_device_info& di = devices.device[i];
|
const cubeb_device_info& di = devices.device[i];
|
||||||
if (!di.device_id)
|
if (!di.device_id)
|
||||||
continue;
|
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;
|
return ret;
|
||||||
|
|
|
@ -19,7 +19,6 @@ public:
|
||||||
~SDLAudioStream();
|
~SDLAudioStream();
|
||||||
|
|
||||||
void SetPaused(bool paused) override;
|
void SetPaused(bool paused) override;
|
||||||
void SetOutputVolume(u32 volume) override;
|
|
||||||
|
|
||||||
bool OpenDevice(Error* error);
|
bool OpenDevice(Error* error);
|
||||||
void CloseDevice();
|
void CloseDevice();
|
||||||
|
@ -120,8 +119,6 @@ bool SDLAudioStream::OpenDevice(Error* error)
|
||||||
Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
|
Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
|
||||||
|
|
||||||
BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
|
BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
|
||||||
m_volume = 100;
|
|
||||||
m_paused = false;
|
|
||||||
SDL_PauseAudioDevice(m_device_id, 0);
|
SDL_PauseAudioDevice(m_device_id, 0);
|
||||||
|
|
||||||
return true;
|
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;
|
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_output_channels;
|
||||||
|
|
||||||
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames);
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<Link>
|
<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>
|
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
|
|
|
@ -230,7 +230,6 @@
|
||||||
<ClCompile Include="wav_writer.cpp" />
|
<ClCompile Include="wav_writer.cpp" />
|
||||||
<ClCompile Include="win32_raw_input_source.cpp" />
|
<ClCompile Include="win32_raw_input_source.cpp" />
|
||||||
<ClCompile Include="window_info.cpp" />
|
<ClCompile Include="window_info.cpp" />
|
||||||
<ClCompile Include="xaudio2_audio_stream.cpp" />
|
|
||||||
<ClCompile Include="xinput_source.cpp" />
|
<ClCompile Include="xinput_source.cpp" />
|
||||||
<ClCompile Include="zstd_byte_stream.cpp" />
|
<ClCompile Include="zstd_byte_stream.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -105,7 +105,6 @@
|
||||||
<ClCompile Include="platform_misc_win32.cpp" />
|
<ClCompile Include="platform_misc_win32.cpp" />
|
||||||
<ClCompile Include="sdl_input_source.cpp" />
|
<ClCompile Include="sdl_input_source.cpp" />
|
||||||
<ClCompile Include="win32_raw_input_source.cpp" />
|
<ClCompile Include="win32_raw_input_source.cpp" />
|
||||||
<ClCompile Include="xaudio2_audio_stream.cpp" />
|
|
||||||
<ClCompile Include="xinput_source.cpp" />
|
<ClCompile Include="xinput_source.cpp" />
|
||||||
<ClCompile Include="dinput_source.cpp" />
|
<ClCompile Include="dinput_source.cpp" />
|
||||||
<ClCompile Include="input_manager.cpp" />
|
<ClCompile Include="input_manager.cpp" />
|
||||||
|
|
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
Loading…
Reference in New Issue