mirror of https://github.com/PCSX2/pcsx2.git
SPU2: Add output device selection
This commit is contained in:
parent
0c8beedb94
commit
9bca1946ed
|
@ -79,6 +79,7 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent
|
|||
connect(m_ui.targetLatency, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabels);
|
||||
connect(m_ui.outputLatency, &QSlider::valueChanged, this, &AudioSettingsWidget::updateLatencyLabels);
|
||||
connect(m_ui.outputLatencyMinimal, &QCheckBox::stateChanged, this, &AudioSettingsWidget::updateLatencyLabels);
|
||||
connect(m_ui.outputLatencyMinimal, &QCheckBox::stateChanged, this, &AudioSettingsWidget::onMinimalOutputLatencyStateChanged);
|
||||
outputModuleChanged();
|
||||
|
||||
m_ui.volume->setValue(m_dialog->getEffectiveIntValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME));
|
||||
|
@ -168,6 +169,8 @@ void AudioSettingsWidget::outputModuleChanged()
|
|||
m_ui.backend->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
updateDevices();
|
||||
}
|
||||
|
||||
void AudioSettingsWidget::outputBackendChanged()
|
||||
|
@ -188,6 +191,40 @@ void AudioSettingsWidget::outputBackendChanged()
|
|||
m_dialog->setStringSettingValue("SPU2/Output", "BackendName", "");
|
||||
else
|
||||
m_dialog->setStringSettingValue("SPU2/Output", "BackendName", m_ui.backend->currentText().toUtf8().constData());
|
||||
|
||||
updateDevices();
|
||||
}
|
||||
|
||||
void AudioSettingsWidget::updateDevices()
|
||||
{
|
||||
const std::string module_name(m_dialog->getEffectiveStringValue("SPU2/Output", "OutputModule", DEFAULT_OUTPUT_MODULE));
|
||||
const std::string backend_name(m_dialog->getEffectiveStringValue("SPU2/Output", "BackendName", ""));
|
||||
|
||||
m_ui.outputDevice->disconnect();
|
||||
m_ui.outputDevice->clear();
|
||||
m_output_device_latency = 0;
|
||||
|
||||
std::vector<SndOutDeviceInfo> devices(GetOutputDeviceList(module_name.c_str(), backend_name.c_str()));
|
||||
if (devices.empty())
|
||||
{
|
||||
m_ui.outputDevice->addItem(tr("Default"));
|
||||
m_ui.outputDevice->setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string current_device(m_dialog->getEffectiveStringValue("SPU2/Output", "DeviceName", ""));
|
||||
|
||||
m_ui.outputDevice->setEnabled(true);
|
||||
for (const SndOutDeviceInfo& devi : devices)
|
||||
{
|
||||
m_ui.outputDevice->addItem(QString::fromStdString(devi.display_name), QString::fromStdString(devi.name));
|
||||
if (devi.name == current_device)
|
||||
m_output_device_latency = devi.minimum_latency_frames;
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(
|
||||
m_dialog->getSettingsInterface(), m_ui.outputDevice, "SPU2/Output", "DeviceName", std::move(devices.front().name));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSettingsWidget::volumeChanged(int value)
|
||||
|
@ -237,7 +274,8 @@ void AudioSettingsWidget::updateLatencyLabels()
|
|||
m_ui.targetLatencyLabel->setText(tr("%1 ms").arg(m_ui.targetLatency->value()));
|
||||
m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(m_ui.outputLatency->value()));
|
||||
|
||||
const u32 output_latency_ms = minimal_output ? 0 : static_cast<u32>(m_ui.outputLatency->value());
|
||||
const u32 output_latency_ms =
|
||||
minimal_output ? (((m_output_device_latency * 1000u) + 47999u) / 48000u) : static_cast<u32>(m_ui.outputLatency->value());
|
||||
const u32 buffer_ms = static_cast<u32>(m_ui.targetLatency->value());
|
||||
if (output_latency_ms > 0)
|
||||
{
|
||||
|
@ -248,7 +286,7 @@ void AudioSettingsWidget::updateLatencyLabels()
|
|||
}
|
||||
else
|
||||
{
|
||||
m_ui.latencySummary->setText(tr("Average Latency: %1 ms (plus minimum output)").arg(buffer_ms));
|
||||
m_ui.latencySummary->setText(tr("Average Latency: %1 ms (minimum output latency unknown)").arg(buffer_ms));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ private Q_SLOTS:
|
|||
void expansionModeChanged();
|
||||
void outputModuleChanged();
|
||||
void outputBackendChanged();
|
||||
void updateDevices();
|
||||
void volumeChanged(int value);
|
||||
void updateTargetLatencyRange();
|
||||
void updateLatencyLabels();
|
||||
|
@ -45,4 +46,5 @@ private Q_SLOTS:
|
|||
private:
|
||||
SettingsDialog* m_dialog;
|
||||
Ui::AudioSettingsWidget m_ui;
|
||||
u32 m_output_device_latency = 0;
|
||||
};
|
||||
|
|
|
@ -448,14 +448,14 @@
|
|||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="outputModule"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Output Latency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="outputLatency">
|
||||
|
@ -505,7 +505,7 @@
|
|||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="backend"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="latencySummary">
|
||||
<property name="text">
|
||||
<string>Maximum Latency:</string>
|
||||
|
@ -515,6 +515,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Output Device:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="outputDevice"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -842,6 +842,7 @@ struct Pcsx2Config
|
|||
|
||||
std::string OutputModule;
|
||||
std::string BackendName;
|
||||
std::string DeviceName;
|
||||
|
||||
SPU2Options();
|
||||
|
||||
|
@ -865,7 +866,8 @@ struct Pcsx2Config
|
|||
OpEqu(OverlapMS) &&
|
||||
|
||||
OpEqu(OutputModule) &&
|
||||
OpEqu(BackendName);
|
||||
OpEqu(BackendName) &&
|
||||
OpEqu(DeviceName);
|
||||
}
|
||||
|
||||
bool operator!=(const SPU2Options& right) const
|
||||
|
|
|
@ -826,6 +826,7 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
|
|||
|
||||
SettingsWrapEntry(OutputModule);
|
||||
SettingsWrapEntry(BackendName);
|
||||
SettingsWrapEntry(DeviceName);
|
||||
SettingsWrapEntry(Latency);
|
||||
SettingsWrapEntry(OutputLatency);
|
||||
SettingsWrapBitBool(OutputLatencyMinimal);
|
||||
|
|
|
@ -74,6 +74,11 @@ public:
|
|||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* driver) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -113,17 +118,27 @@ static SndOutModule* FindOutputModule(const char* name)
|
|||
|
||||
const char* const* GetOutputModuleBackends(const char* omodid)
|
||||
{
|
||||
for (SndOutModule* mod : mods)
|
||||
{
|
||||
if (mod && std::strcmp(mod->GetIdent(), omodid) == 0)
|
||||
{
|
||||
if (SndOutModule* mod = FindOutputModule(omodid))
|
||||
return mod->GetBackendNames();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SndOutDeviceInfo::SndOutDeviceInfo(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_)
|
||||
{
|
||||
}
|
||||
|
||||
SndOutDeviceInfo::~SndOutDeviceInfo() = default;
|
||||
|
||||
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* omodid, const char* driver)
|
||||
{
|
||||
std::vector<SndOutDeviceInfo> ret;
|
||||
if (SndOutModule* mod = FindOutputModule(omodid))
|
||||
ret = mod->GetOutputDeviceList(driver);
|
||||
return ret;
|
||||
}
|
||||
|
||||
StereoOut32* SndBuffer::m_buffer;
|
||||
s32 SndBuffer::m_size;
|
||||
alignas(4) volatile s32 SndBuffer::m_rpos;
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
// Number of stereo samples per SndOut block.
|
||||
// All drivers must work in units of this size when communicating with
|
||||
// SndOut.
|
||||
|
@ -36,6 +38,18 @@ extern int SampleRate;
|
|||
// nullptr is returned if the specified module does not have multiple backends.
|
||||
extern const char* const* GetOutputModuleBackends(const char* omodid);
|
||||
|
||||
// Returns a list of output devices and their associated minimum latency.
|
||||
struct SndOutDeviceInfo
|
||||
{
|
||||
std::string name;
|
||||
std::string display_name;
|
||||
u32 minimum_latency_frames;
|
||||
|
||||
SndOutDeviceInfo(std::string name_, std::string display_name_, u32 minimum_latency_);
|
||||
~SndOutDeviceInfo();
|
||||
};
|
||||
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* omodid, const char* driver);
|
||||
|
||||
struct Stereo51Out16DplII;
|
||||
struct Stereo51Out32DplII;
|
||||
|
||||
|
@ -460,6 +474,9 @@ public:
|
|||
// Returns a null-terminated list of backends, or nullptr.
|
||||
virtual const char* const* GetBackendNames() const = 0;
|
||||
|
||||
// Returns a list of output devices and their associated minimum latency.
|
||||
virtual std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* driver) const = 0;
|
||||
|
||||
virtual bool Init() = 0;
|
||||
virtual void Close() = 0;
|
||||
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
#include "SPU2/Global.h"
|
||||
#include "SPU2/SndOut.h"
|
||||
#include "Host.h"
|
||||
#include "IconsFontAwesome5.h"
|
||||
|
||||
#include "common/Console.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include "common/ScopedGuard.h"
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
|
@ -273,10 +275,44 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
cubeb_devid selected_device = nullptr;
|
||||
const std::string& selected_device_name = EmuConfig.SPU2.DeviceName;
|
||||
cubeb_device_collection devices;
|
||||
bool devices_valid = false;
|
||||
if (!selected_device_name.empty())
|
||||
{
|
||||
rv = cubeb_enumerate_devices(m_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
|
||||
devices_valid = (rv == CUBEB_OK);
|
||||
if (rv == CUBEB_OK)
|
||||
{
|
||||
for (size_t i = 0; i < devices.count; i++)
|
||||
{
|
||||
const cubeb_device_info& di = devices.device[i];
|
||||
if (di.device_id && selected_device_name == di.device_id)
|
||||
{
|
||||
Console.WriteLn("Using output device '%s' (%s).", di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
|
||||
selected_device = di.devid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selected_device)
|
||||
{
|
||||
Host::AddIconOSDMessage("CubebDeviceNotFound", ICON_FA_VOLUME_MUTE,
|
||||
fmt::format("Requested audio output device '{}' not found, using default.", selected_device_name),
|
||||
Host::OSD_WARNING_DURATION);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error("cubeb_enumerate_devices() returned %d, using default device.", rv);
|
||||
}
|
||||
}
|
||||
|
||||
char stream_name[32];
|
||||
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
|
||||
|
||||
rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, nullptr, ¶ms,
|
||||
rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, ¶ms,
|
||||
latency_frames, &Cubeb::DataCallback, &Cubeb::StateCallback, this);
|
||||
if (rv != CUBEB_OK)
|
||||
{
|
||||
|
@ -353,6 +389,55 @@ public:
|
|||
{
|
||||
return cubeb_get_backend_names();
|
||||
}
|
||||
|
||||
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* driver) const override
|
||||
{
|
||||
std::vector<SndOutDeviceInfo> ret;
|
||||
ret.emplace_back(std::string(), "Default", 0u);
|
||||
|
||||
cubeb* context;
|
||||
int rv = cubeb_init(&context, "PCSX2", (driver && *driver) ? driver : nullptr);
|
||||
if (rv != CUBEB_OK)
|
||||
{
|
||||
Console.Error("(GetOutputDeviceList) cubeb_init() failed: %d", rv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ScopedGuard context_cleanup([context]() { cubeb_destroy(context); });
|
||||
|
||||
cubeb_device_collection devices;
|
||||
rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
|
||||
if (rv != CUBEB_OK)
|
||||
{
|
||||
Console.Error("(GetOutputDeviceList) cubeb_enumerate_devices() failed: %d", 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 = SampleRate;
|
||||
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, min_latency);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
static Cubeb s_Cubeb;
|
||||
|
|
|
@ -369,6 +369,10 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<SndOutDeviceInfo> GetOutputDeviceList(const char* driver) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
} static XA2;
|
||||
|
||||
SndOutModule* XAudio2Out = &XA2;
|
||||
|
|
|
@ -21,11 +21,11 @@
|
|||
|
||||
namespace SPU2
|
||||
{
|
||||
static int GetConsoleSampleRate();
|
||||
static void InitSndBuffer();
|
||||
static void UpdateSampleRate();
|
||||
static void InternalReset(bool psxmode);
|
||||
}
|
||||
static int GetConsoleSampleRate();
|
||||
static void InitSndBuffer();
|
||||
static void UpdateSampleRate();
|
||||
static void InternalReset(bool psxmode);
|
||||
} // namespace SPU2
|
||||
|
||||
static double s_device_sample_rate_multiplier = 1.0;
|
||||
static bool s_psxmode = false;
|
||||
|
@ -397,6 +397,9 @@ void SPU2::CheckForConfigChanges(const Pcsx2Config& old_config)
|
|||
if (opts.Latency != oldopts.Latency ||
|
||||
opts.OutputLatency != oldopts.OutputLatency ||
|
||||
opts.OutputLatencyMinimal != oldopts.OutputLatencyMinimal ||
|
||||
opts.OutputModule != oldopts.OutputModule ||
|
||||
opts.BackendName != oldopts.BackendName ||
|
||||
opts.DeviceName != oldopts.DeviceName ||
|
||||
opts.SpeakerConfiguration != oldopts.SpeakerConfiguration ||
|
||||
opts.DplDecodingLevel != oldopts.DplDecodingLevel ||
|
||||
opts.SequenceLenMS != oldopts.SequenceLenMS ||
|
||||
|
|
Loading…
Reference in New Issue