diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 2315602694..e679a95886 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -7,7 +7,6 @@ #include "audio_core/audio_types.h" #include "audio_core/cubeb_sink.h" #include "common/logging/log.h" -#include "core/settings.h" namespace AudioCore { @@ -25,13 +24,12 @@ struct CubebSink::Impl { static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); }; -CubebSink::CubebSink() : impl(std::make_unique()) { +CubebSink::CubebSink(std::string target_device_name) : impl(std::make_unique()) { if (cubeb_init(&impl->ctx, "Citra", nullptr) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); return; } - const char* target_device_name = nullptr; cubeb_devid output_device = nullptr; cubeb_stream_params params; @@ -46,27 +44,21 @@ CubebSink::CubebSink() : impl(std::make_unique()) { if (cubeb_get_min_latency(impl->ctx, ¶ms, &minimum_latency) != CUBEB_OK) LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); - cubeb_device_collection collection; - if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { - LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); - } else { - if (collection.count >= 1 && Settings::values.audio_device_id != "auto" && - !Settings::values.audio_device_id.empty()) { - target_device_name = Settings::values.audio_device_id.c_str(); - } - - for (size_t i = 0; i < collection.count; i++) { - const cubeb_device_info& device = collection.device[i]; - if (device.friendly_name) { - impl->device_list.emplace_back(device.friendly_name); - - if (target_device_name && strcmp(target_device_name, device.friendly_name) == 0) { - output_device = device.devid; - } + if (target_device_name != auto_device_name && !target_device_name.empty()) { + cubeb_device_collection collection; + if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { + LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); + } else { + const auto collection_end = collection.device + collection.count; + const auto device = std::find_if(collection.device, collection_end, + [&](const cubeb_device_info& device) { + return target_device_name == device.friendly_name; + }); + if (device != collection_end) { + output_device = device->devid; } + cubeb_device_collection_destroy(impl->ctx, &collection); } - - cubeb_device_collection_destroy(impl->ctx, &collection); } if (cubeb_stream_init(impl->ctx, &impl->stream, "Citra Audio Output", nullptr, nullptr, @@ -101,10 +93,6 @@ unsigned int CubebSink::GetNativeSampleRate() const { return impl->sample_rate; } -std::vector CubebSink::GetDeviceList() const { - return impl->device_list; -} - void CubebSink::EnqueueSamples(const s16* samples, size_t sample_count) { if (!impl->ctx) return; @@ -120,8 +108,6 @@ size_t CubebSink::SamplesInQueue() const { return impl->queue.size() / 2; } -void CubebSink::SetDevice(int device_id) {} - long CubebSink::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames) { Impl* impl = static_cast(user_data); @@ -146,4 +132,30 @@ long CubebSink::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void CubebSink::Impl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} +std::vector ListCubebSinkDevices() { + std::vector device_list; + cubeb* ctx; + + if (cubeb_init(&ctx, "Citra Device Enumerator", nullptr) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); + return {}; + } + + cubeb_device_collection collection; + if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { + LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); + } else { + for (size_t i = 0; i < collection.count; i++) { + const cubeb_device_info& device = collection.device[i]; + if (device.friendly_name) { + device_list.emplace_back(device.friendly_name); + } + } + cubeb_device_collection_destroy(ctx, &collection); + } + + cubeb_destroy(ctx); + return device_list; +} + } // namespace AudioCore diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index 1fb5218346..a2ef228aad 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -12,7 +12,7 @@ namespace AudioCore { class CubebSink final : public Sink { public: - CubebSink(); + explicit CubebSink(std::string device_id); ~CubebSink() override; unsigned int GetNativeSampleRate() const override; @@ -21,12 +21,11 @@ public: size_t SamplesInQueue() const override; - std::vector GetDeviceList() const override; - void SetDevice(int device_id) override; - private: struct Impl; std::unique_ptr impl; }; +std::vector ListCubebSinkDevices(); + } // namespace AudioCore diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp index c93d4571ab..dcb902582d 100644 --- a/src/audio_core/dsp_interface.cpp +++ b/src/audio_core/dsp_interface.cpp @@ -18,9 +18,9 @@ DspInterface::~DspInterface() { } } -void DspInterface::SetSink(const std::string& sink_id) { +void DspInterface::SetSink(const std::string& sink_id, const std::string& audio_device) { const SinkDetails& sink_details = GetSinkDetails(sink_id); - sink = sink_details.factory(); + sink = sink_details.factory(audio_device); time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); } diff --git a/src/audio_core/dsp_interface.h b/src/audio_core/dsp_interface.h index 1f3024dc84..fa62ba6913 100644 --- a/src/audio_core/dsp_interface.h +++ b/src/audio_core/dsp_interface.h @@ -61,7 +61,7 @@ public: virtual std::array& GetDspMemory() = 0; /// Select the sink to use based on sink id. - void SetSink(const std::string& sink_id); + void SetSink(const std::string& sink_id, const std::string& audio_device); /// Get the current sink Sink& GetSink(); /// Enable/Disable audio stretching. diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index 3b2129f8f2..97a228f286 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -12,6 +12,7 @@ namespace AudioCore { class NullSink final : public Sink { public: + NullSink(std::string) {} ~NullSink() override = default; unsigned int GetNativeSampleRate() const override { @@ -23,12 +24,6 @@ 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 c3fba3b846..f18da88458 100644 --- a/src/audio_core/sdl2_sink.cpp +++ b/src/audio_core/sdl2_sink.cpp @@ -9,7 +9,6 @@ #include "audio_core/sdl2_sink.h" #include "common/assert.h" #include "common/logging/log.h" -#include "core/settings.h" namespace AudioCore { @@ -23,7 +22,7 @@ struct SDL2Sink::Impl { static void Callback(void* impl_, u8* buffer, int buffer_size_in_bytes); }; -SDL2Sink::SDL2Sink() : impl(std::make_unique()) { +SDL2Sink::SDL2Sink(std::string device_name) : impl(std::make_unique()) { if (SDL_Init(SDL_INIT_AUDIO) < 0) { LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed with: {}", SDL_GetError()); impl->audio_device_id = 0; @@ -42,24 +41,16 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique()) { SDL_AudioSpec obtained_audiospec; SDL_zero(obtained_audiospec); - 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(); + if (device_name != auto_device_name && !device_name.empty()) { + device = device_name.c_str(); } impl->audio_device_id = SDL_OpenAudioDevice( device, false, &desired_audiospec, &obtained_audiospec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); if (impl->audio_device_id <= 0) { LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with code {} for device \"{}\"", - impl->audio_device_id, Settings::values.audio_device_id); + impl->audio_device_id, device_name); return; } @@ -83,10 +74,6 @@ 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; @@ -114,10 +101,6 @@ 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_); @@ -144,4 +127,21 @@ void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) } } +std::vector ListSDL2SinkDevices() { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem failed with: {}", SDL_GetError()); + return {}; + } + + std::vector device_list; + const int device_count = SDL_GetNumAudioDevices(0); + for (int i = 0; i < device_count; ++i) { + device_list.push_back(SDL_GetAudioDeviceName(i, 0)); + } + + SDL_QuitSubSystem(SDL_INIT_AUDIO); + + return device_list; +} + } // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h index bcc725369e..33b5df2839 100644 --- a/src/audio_core/sdl2_sink.h +++ b/src/audio_core/sdl2_sink.h @@ -12,7 +12,7 @@ namespace AudioCore { class SDL2Sink final : public Sink { public: - SDL2Sink(); + explicit SDL2Sink(std::string device_id); ~SDL2Sink() override; unsigned int GetNativeSampleRate() const override; @@ -21,14 +21,11 @@ public: size_t SamplesInQueue() const override; - std::vector GetDeviceList() const override; - void SetDevice(int device_id) override; - private: struct Impl; std::unique_ptr impl; - int device_id; - std::vector device_list; }; +std::vector ListSDL2SinkDevices(); + } // namespace AudioCore diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 19fe128601..723756b675 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -9,6 +9,8 @@ namespace AudioCore { +constexpr char auto_device_name[] = "auto"; + /** * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate. @@ -31,15 +33,6 @@ public: /// Samples enqueued that have not been played yet. virtual std::size_t SamplesInQueue() const = 0; - - /** - * Sets the desired output device. - * @param 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 AudioCore diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 2a51b2b2b2..2bac7e3e19 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -21,12 +21,13 @@ namespace AudioCore { // g_sink_details is ordered in terms of desirability, with the best choice at the top. const std::vector g_sink_details = { #ifdef HAVE_CUBEB - {"cubeb", []() { return std::make_unique(); }}, + SinkDetails{"cubeb", &std::make_unique, &ListCubebSinkDevices}, #endif #ifdef HAVE_SDL2 - {"sdl2", []() { return std::make_unique(); }}, + SinkDetails{"sdl2", &std::make_unique, &ListSDL2SinkDevices}, #endif - {"null", []() { return std::make_unique(); }}, + SinkDetails{"null", &std::make_unique, + [] { return std::vector{"null"}; }}, }; const SinkDetails& GetSinkDetails(std::string sink_id) { diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index 9d37351711..17ecd1e939 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -13,13 +13,16 @@ namespace AudioCore { class Sink; struct SinkDetails { - SinkDetails(const char* id_, std::function()> factory_) - : id(id_), factory(factory_) {} + SinkDetails(const char* id_, std::function(std::string)> factory_, + std::function()> list_devices_) + : id(id_), factory(factory_), list_devices(list_devices_) {} /// Name for this sink. const char* id; /// A method to call to construct an instance of this type of sink. - std::function()> factory; + std::function(std::string device_id)> factory; + /// A method to call to list available devices. + std::function()> list_devices; }; extern const std::vector g_sink_details; diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 123be49970..d0064ca78e 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -65,11 +65,10 @@ void ConfigureAudio::applyConfiguration() { void ConfigureAudio::updateAudioDevices(int sink_index) { ui->audio_device_combo_box->clear(); - ui->audio_device_combo_box->addItem("auto"); + ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); - std::vector device_list = - AudioCore::GetSinkDetails(sink_id).factory()->GetDeviceList(); + std::vector device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); for (const auto& device : device_list) { ui->audio_device_combo_box->addItem(device.c_str()); } diff --git a/src/core/core.cpp b/src/core/core.cpp index 287014c4cf..6d3ddc1369 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -166,7 +166,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { } dsp_core = std::make_unique(); - dsp_core->SetSink(Settings::values.sink_id); + dsp_core->SetSink(Settings::values.sink_id, Settings::values.audio_device_id); dsp_core->EnableStretching(Settings::values.enable_audio_stretching); telemetry_session = std::make_unique(); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 3cc38b9822..422903b708 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -32,7 +32,7 @@ void Apply() { } if (Core::System::GetInstance().IsPoweredOn()) { - Core::DSP().SetSink(values.sink_id); + Core::DSP().SetSink(values.sink_id, values.audio_device_id); Core::DSP().EnableStretching(values.enable_audio_stretching); }