SDL: Select audio device (#2403)
* Initial Commit Added Device logic to Sinks Started on UI for selecting devices Removed redundant import * Audio Core: Complete Device Switching Complete the device switching implementation by allowing the output device to be loaded, changed and saved through the configurations menu. Worked with the Sink abstraction and tuned the "Device Selection" configuration so that the Device List is automatically populated when the Sink is changed. This hopefully addresses the concerns and recommendations mentioned in the comments of the PR. * Clean original implementation. * Refactor GetSinkDetails
This commit is contained in:
parent
3feb3ce283
commit
f852369986
14 changed files with 129 additions and 18 deletions
|
@ -56,20 +56,8 @@ void AddAddressSpace(Kernel::VMManager& address_space) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectSink(std::string sink_id) {
|
void SelectSink(std::string sink_id) {
|
||||||
auto iter =
|
const SinkDetails& sink_details = GetSinkDetails(sink_id);
|
||||||
std::find_if(g_sink_details.begin(), g_sink_details.end(),
|
DSP::HLE::SetSink(sink_details.factory());
|
||||||
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
|
||||||
|
|
||||||
if (sink_id == "auto" || iter == g_sink_details.end()) {
|
|
||||||
if (sink_id != "auto") {
|
|
||||||
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id %s", sink_id.c_str());
|
|
||||||
}
|
|
||||||
// Auto-select.
|
|
||||||
// g_sink_details is ordered in terms of desirability, with the best choice at the front.
|
|
||||||
iter = g_sink_details.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
DSP::HLE::SetSink(iter->factory());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnableStretching(bool enable) {
|
void EnableStretching(bool enable) {
|
||||||
|
|
|
@ -23,6 +23,12 @@ public:
|
||||||
size_t SamplesInQueue() const override {
|
size_t SamplesInQueue() const override {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetDevice(int device_id) override {}
|
||||||
|
|
||||||
|
std::vector<std::string> GetDeviceList() const override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore
|
} // namespace AudioCore
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <vector>
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/sdl2_sink.h"
|
#include "audio_core/sdl2_sink.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
|
||||||
namespace AudioCore {
|
namespace AudioCore {
|
||||||
|
|
||||||
|
@ -42,10 +42,24 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
|
||||||
SDL_AudioSpec obtained_audiospec;
|
SDL_AudioSpec obtained_audiospec;
|
||||||
SDL_zero(obtained_audiospec);
|
SDL_zero(obtained_audiospec);
|
||||||
|
|
||||||
impl->audio_device_id =
|
int device_count = SDL_GetNumAudioDevices(0);
|
||||||
SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
|
device_list.clear();
|
||||||
|
for (int i = 0; i < device_count; ++i) {
|
||||||
|
device_list.push_back(SDL_GetAudioDeviceName(i, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* device = nullptr;
|
||||||
|
|
||||||
|
if (device_count >= 1 && Settings::values.audio_device_id != "auto" &&
|
||||||
|
!Settings::values.audio_device_id.empty()) {
|
||||||
|
device = Settings::values.audio_device_id.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->audio_device_id = SDL_OpenAudioDevice(device, false, &desired_audiospec,
|
||||||
|
&obtained_audiospec, SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||||
if (impl->audio_device_id <= 0) {
|
if (impl->audio_device_id <= 0) {
|
||||||
LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with: %s", SDL_GetError());
|
LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with code %d for device \"%s\"",
|
||||||
|
impl->audio_device_id, Settings::values.audio_device_id.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +83,10 @@ unsigned int SDL2Sink::GetNativeSampleRate() const {
|
||||||
return impl->sample_rate;
|
return impl->sample_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SDL2Sink::GetDeviceList() const {
|
||||||
|
return device_list;
|
||||||
|
}
|
||||||
|
|
||||||
void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) {
|
void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) {
|
||||||
if (impl->audio_device_id <= 0)
|
if (impl->audio_device_id <= 0)
|
||||||
return;
|
return;
|
||||||
|
@ -96,6 +114,10 @@ size_t SDL2Sink::SamplesInQueue() const {
|
||||||
return total_size;
|
return total_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SDL2Sink::SetDevice(int device_id) {
|
||||||
|
this->device_id = device_id;
|
||||||
|
}
|
||||||
|
|
||||||
void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) {
|
void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) {
|
||||||
Impl* impl = reinterpret_cast<Impl*>(impl_);
|
Impl* impl = reinterpret_cast<Impl*>(impl_);
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,14 @@ public:
|
||||||
|
|
||||||
size_t SamplesInQueue() const override;
|
size_t SamplesInQueue() const override;
|
||||||
|
|
||||||
|
std::vector<std::string> GetDeviceList() const override;
|
||||||
|
void SetDevice(int device_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl;
|
std::unique_ptr<Impl> impl;
|
||||||
|
int device_id;
|
||||||
|
std::vector<std::string> device_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore
|
} // namespace AudioCore
|
||||||
|
|
|
@ -31,6 +31,15 @@ public:
|
||||||
|
|
||||||
/// Samples enqueued that have not been played yet.
|
/// Samples enqueued that have not been played yet.
|
||||||
virtual std::size_t SamplesInQueue() const = 0;
|
virtual std::size_t SamplesInQueue() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the desired output device.
|
||||||
|
* @paran device_id Id of the desired device.
|
||||||
|
*/
|
||||||
|
virtual void SetDevice(int device_id) = 0;
|
||||||
|
|
||||||
|
/// Returns the list of available devices.
|
||||||
|
virtual std::vector<std::string> GetDeviceList() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "audio_core/null_sink.h"
|
#include "audio_core/null_sink.h"
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
#include "audio_core/sdl2_sink.h"
|
#include "audio_core/sdl2_sink.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
namespace AudioCore {
|
namespace AudioCore {
|
||||||
|
|
||||||
|
@ -20,4 +22,21 @@ const std::vector<SinkDetails> g_sink_details = {
|
||||||
{"null", []() { return std::make_unique<NullSink>(); }},
|
{"null", []() { return std::make_unique<NullSink>(); }},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SinkDetails& GetSinkDetails(std::string sink_id) {
|
||||||
|
auto iter =
|
||||||
|
std::find_if(g_sink_details.begin(), g_sink_details.end(),
|
||||||
|
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
||||||
|
|
||||||
|
if (sink_id == "auto" || iter == g_sink_details.end()) {
|
||||||
|
if (sink_id != "auto") {
|
||||||
|
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id %s", sink_id.c_str());
|
||||||
|
}
|
||||||
|
// Auto-select.
|
||||||
|
// g_sink_details is ordered in terms of desirability, with the best choice at the front.
|
||||||
|
iter = g_sink_details.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
return *iter;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace AudioCore
|
} // namespace AudioCore
|
||||||
|
|
|
@ -24,4 +24,6 @@ struct SinkDetails {
|
||||||
|
|
||||||
extern const std::vector<SinkDetails> g_sink_details;
|
extern const std::vector<SinkDetails> g_sink_details;
|
||||||
|
|
||||||
|
const SinkDetails& GetSinkDetails(std::string sink_id);
|
||||||
|
|
||||||
} // namespace AudioCore
|
} // namespace AudioCore
|
||||||
|
|
|
@ -82,6 +82,7 @@ void Config::ReadValues() {
|
||||||
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
|
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
|
||||||
Settings::values.enable_audio_stretching =
|
Settings::values.enable_audio_stretching =
|
||||||
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
||||||
|
Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
|
||||||
|
|
||||||
// Data Storage
|
// Data Storage
|
||||||
Settings::values.use_virtual_sd =
|
Settings::values.use_virtual_sd =
|
||||||
|
|
|
@ -91,6 +91,10 @@ output_engine =
|
||||||
# 0: No, 1 (default): Yes
|
# 0: No, 1 (default): Yes
|
||||||
enable_audio_stretching =
|
enable_audio_stretching =
|
||||||
|
|
||||||
|
# Which audio device to use.
|
||||||
|
# auto (default): Auto-select
|
||||||
|
output_device =
|
||||||
|
|
||||||
[Data Storage]
|
[Data Storage]
|
||||||
# Whether to create a virtual SD card.
|
# Whether to create a virtual SD card.
|
||||||
# 1 (default): Yes, 0: No
|
# 1 (default): Yes, 0: No
|
||||||
|
|
|
@ -63,6 +63,8 @@ void Config::ReadValues() {
|
||||||
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
|
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
|
||||||
Settings::values.enable_audio_stretching =
|
Settings::values.enable_audio_stretching =
|
||||||
qt_config->value("enable_audio_stretching", true).toBool();
|
qt_config->value("enable_audio_stretching", true).toBool();
|
||||||
|
Settings::values.audio_device_id =
|
||||||
|
qt_config->value("output_device", "auto").toString().toStdString();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Data Storage");
|
qt_config->beginGroup("Data Storage");
|
||||||
|
@ -169,6 +171,7 @@ void Config::SaveValues() {
|
||||||
qt_config->beginGroup("Audio");
|
qt_config->beginGroup("Audio");
|
||||||
qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
|
qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
|
||||||
qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
|
qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
|
||||||
|
qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id));
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Data Storage");
|
qt_config->beginGroup("Data Storage");
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "audio_core/audio_core.h"
|
||||||
|
#include "audio_core/sink.h"
|
||||||
#include "audio_core/sink_details.h"
|
#include "audio_core/sink_details.h"
|
||||||
#include "citra_qt/configure_audio.h"
|
#include "citra_qt/configure_audio.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
@ -18,6 +21,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
|
connect(ui->output_sink_combo_box, SIGNAL(currentIndexChanged(int)), this,
|
||||||
|
SLOT(updateAudioDevices(int)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigureAudio::~ConfigureAudio() {}
|
ConfigureAudio::~ConfigureAudio() {}
|
||||||
|
@ -33,6 +38,19 @@ void ConfigureAudio::setConfiguration() {
|
||||||
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
|
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
|
||||||
|
|
||||||
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
|
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
|
||||||
|
|
||||||
|
// The device list cannot be pre-populated (nor listed) until the output sink is known.
|
||||||
|
updateAudioDevices(new_sink_index);
|
||||||
|
|
||||||
|
int new_device_index = -1;
|
||||||
|
for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
|
||||||
|
if (ui->audio_device_combo_box->itemText(index).toStdString() ==
|
||||||
|
Settings::values.audio_device_id) {
|
||||||
|
new_device_index = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui->audio_device_combo_box->setCurrentIndex(new_device_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureAudio::applyConfiguration() {
|
void ConfigureAudio::applyConfiguration() {
|
||||||
|
@ -40,5 +58,20 @@ void ConfigureAudio::applyConfiguration() {
|
||||||
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
|
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
|
||||||
.toStdString();
|
.toStdString();
|
||||||
Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
|
Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
|
||||||
|
Settings::values.audio_device_id =
|
||||||
|
ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
|
||||||
|
.toStdString();
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigureAudio::updateAudioDevices(int sink_index) {
|
||||||
|
ui->audio_device_combo_box->clear();
|
||||||
|
ui->audio_device_combo_box->addItem("auto");
|
||||||
|
|
||||||
|
std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
|
||||||
|
std::vector<std::string> device_list =
|
||||||
|
AudioCore::GetSinkDetails(sink_id).factory()->GetDeviceList();
|
||||||
|
for (const auto& device : device_list) {
|
||||||
|
ui->audio_device_combo_box->addItem(device.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ public:
|
||||||
|
|
||||||
void applyConfiguration();
|
void applyConfiguration();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateAudioDevices(int sink_index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setConfiguration();
|
void setConfiguration();
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,21 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Audio Device:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="audio_device_combo_box">
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -104,6 +104,7 @@ struct Values {
|
||||||
// Audio
|
// Audio
|
||||||
std::string sink_id;
|
std::string sink_id;
|
||||||
bool enable_audio_stretching;
|
bool enable_audio_stretching;
|
||||||
|
std::string audio_device_id;
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
bool use_gdbstub;
|
bool use_gdbstub;
|
||||||
|
|
Loading…
Reference in a new issue