AudioStream: Backport changes
This commit is contained in:
parent
921f5119b2
commit
4139bf63d8
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, ¶ms, &min_latency);
|
||||
ret[0].minimum_latency_frames = min_latency;
|
||||
|
||||
for (size_t i = 0; i < devices.count; i++)
|
||||
{
|
||||
const cubeb_device_info& di = devices.device[i];
|
||||
if (!di.device_id)
|
||||
continue;
|
||||
|
||||
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
|
||||
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, min_latency);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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