From f8523699864b6000572affaa0e36d9a4d89ffce6 Mon Sep 17 00:00:00 2001 From: Kloen Lansfiel Date: Thu, 26 Jan 2017 04:33:26 +0100 Subject: [PATCH] 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 --- src/audio_core/audio_core.cpp | 16 ++-------------- src/audio_core/null_sink.h | 6 ++++++ src/audio_core/sdl2_sink.cpp | 30 +++++++++++++++++++++++++---- src/audio_core/sdl2_sink.h | 5 +++++ src/audio_core/sink.h | 9 +++++++++ src/audio_core/sink_details.cpp | 19 ++++++++++++++++++ src/audio_core/sink_details.h | 2 ++ src/citra/config.cpp | 1 + src/citra/default_ini.h | 4 ++++ src/citra_qt/config.cpp | 3 +++ src/citra_qt/configure_audio.cpp | 33 ++++++++++++++++++++++++++++++++ src/citra_qt/configure_audio.h | 3 +++ src/citra_qt/configure_audio.ui | 15 +++++++++++++++ src/core/settings.h | 1 + 14 files changed, 129 insertions(+), 18 deletions(-) diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index ba6acf28e..84f9c03a7 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -56,20 +56,8 @@ void AddAddressSpace(Kernel::VMManager& address_space) { } void SelectSink(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(); - } - - DSP::HLE::SetSink(iter->factory()); + const SinkDetails& sink_details = GetSinkDetails(sink_id); + DSP::HLE::SetSink(sink_details.factory()); } void EnableStretching(bool enable) { diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index e7668438c..c732926a2 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -23,6 +23,12 @@ public: size_t SamplesInQueue() const override { return 0; } + + void SetDevice(int device_id) override {} + + std::vector GetDeviceList() const override { + return {}; + } }; } // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp index 4b66cd826..933c5f16d 100644 --- a/src/audio_core/sdl2_sink.cpp +++ b/src/audio_core/sdl2_sink.cpp @@ -4,12 +4,12 @@ #include #include -#include #include #include "audio_core/audio_core.h" #include "audio_core/sdl2_sink.h" #include "common/assert.h" #include "common/logging/log.h" +#include "core/settings.h" namespace AudioCore { @@ -42,10 +42,24 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique()) { SDL_AudioSpec obtained_audiospec; SDL_zero(obtained_audiospec); - impl->audio_device_id = - SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0); + int device_count = SDL_GetNumAudioDevices(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) { - 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; } @@ -69,6 +83,10 @@ unsigned int SDL2Sink::GetNativeSampleRate() const { return impl->sample_rate; } +std::vector SDL2Sink::GetDeviceList() const { + return device_list; +} + void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) { if (impl->audio_device_id <= 0) return; @@ -96,6 +114,10 @@ size_t SDL2Sink::SamplesInQueue() const { 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) { Impl* impl = reinterpret_cast(impl_); diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h index ccd0f7c7e..ce75733b7 100644 --- a/src/audio_core/sdl2_sink.h +++ b/src/audio_core/sdl2_sink.h @@ -21,9 +21,14 @@ public: size_t SamplesInQueue() const override; + std::vector GetDeviceList() const override; + void SetDevice(int device_id); + private: struct Impl; std::unique_ptr impl; + int device_id; + std::vector device_list; }; } // namespace AudioCore diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 08f3bab5b..558c8c0fe 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -31,6 +31,15 @@ public: /// Samples enqueued that have not been played yet. 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 GetDeviceList() const = 0; }; } // namespace diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 95ccc9e9d..6972395af 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include "audio_core/null_sink.h" @@ -9,6 +10,7 @@ #ifdef HAVE_SDL2 #include "audio_core/sdl2_sink.h" #endif +#include "common/logging/log.h" namespace AudioCore { @@ -20,4 +22,21 @@ const std::vector g_sink_details = { {"null", []() { return std::make_unique(); }}, }; +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 diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index 4b30cf835..9d3735171 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -24,4 +24,6 @@ struct SinkDetails { extern const std::vector g_sink_details; +const SinkDetails& GetSinkDetails(std::string sink_id); + } // namespace AudioCore diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 1d0faf193..827c90e55 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -82,6 +82,7 @@ void Config::ReadValues() { Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); Settings::values.enable_audio_stretching = sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); + Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); // Data Storage Settings::values.use_virtual_sd = diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 7996813b4..d728fb9e8 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -91,6 +91,10 @@ output_engine = # 0: No, 1 (default): Yes enable_audio_stretching = +# Which audio device to use. +# auto (default): Auto-select +output_device = + [Data Storage] # Whether to create a virtual SD card. # 1 (default): Yes, 0: No diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 8021667d0..f776e16b2 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -63,6 +63,8 @@ void Config::ReadValues() { Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); Settings::values.enable_audio_stretching = 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->beginGroup("Data Storage"); @@ -169,6 +171,7 @@ void Config::SaveValues() { qt_config->beginGroup("Audio"); 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("output_device", QString::fromStdString(Settings::values.audio_device_id)); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configure_audio.cpp index 3cdd4c780..3ddcf9232 100644 --- a/src/citra_qt/configure_audio.cpp +++ b/src/citra_qt/configure_audio.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include "audio_core/audio_core.h" +#include "audio_core/sink.h" #include "audio_core/sink_details.h" #include "citra_qt/configure_audio.h" #include "core/settings.h" @@ -18,6 +21,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) } this->setConfiguration(); + connect(ui->output_sink_combo_box, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateAudioDevices(int))); } ConfigureAudio::~ConfigureAudio() {} @@ -33,6 +38,19 @@ void ConfigureAudio::setConfiguration() { ui->output_sink_combo_box->setCurrentIndex(new_sink_index); 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() { @@ -40,5 +58,20 @@ void ConfigureAudio::applyConfiguration() { ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) .toStdString(); 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(); } + +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 device_list = + AudioCore::GetSinkDetails(sink_id).factory()->GetDeviceList(); + for (const auto& device : device_list) { + ui->audio_device_combo_box->addItem(device.c_str()); + } +} diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configure_audio.h index 51df2e27b..8190e694f 100644 --- a/src/citra_qt/configure_audio.h +++ b/src/citra_qt/configure_audio.h @@ -20,6 +20,9 @@ public: void applyConfiguration(); +public slots: + void updateAudioDevices(int sink_index); + private: void setConfiguration(); diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui index 3e2b4635f..dd870eb61 100644 --- a/src/citra_qt/configure_audio.ui +++ b/src/citra_qt/configure_audio.ui @@ -35,6 +35,21 @@ + + + + + + Audio Device: + + + + + + + + + diff --git a/src/core/settings.h b/src/core/settings.h index 8dbda653a..e22ce0f16 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -104,6 +104,7 @@ struct Values { // Audio std::string sink_id; bool enable_audio_stretching; + std::string audio_device_id; // Debugging bool use_gdbstub;