Merge pull request #9039 from Kelebek1/auto_backend
Auto select the SDL audio backend when Cubeb latency is too high
This commit is contained in:
		| @@ -66,10 +66,10 @@ public: | ||||
|         const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency); | ||||
|         if (latency_error != CUBEB_OK) { | ||||
|             LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); | ||||
|             minimum_latency = 256U; | ||||
|             minimum_latency = TargetSampleCount * 2; | ||||
|         } | ||||
|  | ||||
|         minimum_latency = std::max(minimum_latency, 256u); | ||||
|         minimum_latency = std::max(minimum_latency, TargetSampleCount * 2); | ||||
|  | ||||
|         LOG_INFO(Service_Audio, | ||||
|                  "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " | ||||
| @@ -326,4 +326,31 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) { | ||||
|     return device_list; | ||||
| } | ||||
|  | ||||
| u32 GetCubebLatency() { | ||||
|     cubeb* ctx; | ||||
|  | ||||
|     if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) { | ||||
|         LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||||
|         // Return a large latency so we choose SDL instead. | ||||
|         return 10000u; | ||||
|     } | ||||
|  | ||||
|     cubeb_stream_params params{}; | ||||
|     params.rate = TargetSampleRate; | ||||
|     params.channels = 2; | ||||
|     params.format = CUBEB_SAMPLE_S16LE; | ||||
|     params.prefs = CUBEB_STREAM_PREF_NONE; | ||||
|     params.layout = CUBEB_LAYOUT_STEREO; | ||||
|  | ||||
|     u32 latency{0}; | ||||
|     const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency); | ||||
|     if (latency_error != CUBEB_OK) { | ||||
|         LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); | ||||
|         latency = TargetSampleCount * 2; | ||||
|     } | ||||
|     latency = std::max(latency, TargetSampleCount * 2); | ||||
|     cubeb_destroy(ctx); | ||||
|     return latency; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::Sink | ||||
|   | ||||
| @@ -96,4 +96,11 @@ private: | ||||
|  */ | ||||
| std::vector<std::string> ListCubebSinkDevices(bool capture); | ||||
|  | ||||
| /** | ||||
|  * Get the reported latency for this sink. | ||||
|  * | ||||
|  * @return Minimum latency for this sink. | ||||
|  */ | ||||
| u32 GetCubebLatency(); | ||||
|  | ||||
| } // namespace AudioCore::Sink | ||||
|   | ||||
| @@ -47,11 +47,7 @@ public: | ||||
|         spec.freq = TargetSampleRate; | ||||
|         spec.channels = static_cast<u8>(device_channels); | ||||
|         spec.format = AUDIO_S16SYS; | ||||
|         if (type == StreamType::Render) { | ||||
|             spec.samples = TargetSampleCount; | ||||
|         } else { | ||||
|             spec.samples = 1024; | ||||
|         } | ||||
|         spec.samples = TargetSampleCount * 2; | ||||
|         spec.callback = &SDLSinkStream::DataCallback; | ||||
|         spec.userdata = this; | ||||
|  | ||||
| @@ -240,4 +236,8 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) { | ||||
|     return device_list; | ||||
| } | ||||
|  | ||||
| u32 GetSDLLatency() { | ||||
|     return TargetSampleCount * 2; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::Sink | ||||
|   | ||||
| @@ -87,4 +87,11 @@ private: | ||||
|  */ | ||||
| std::vector<std::string> ListSDLSinkDevices(bool capture); | ||||
|  | ||||
| /** | ||||
|  * Get the reported latency for this sink. | ||||
|  * | ||||
|  * @return Minimum latency for this sink. | ||||
|  */ | ||||
| u32 GetSDLLatency(); | ||||
|  | ||||
| } // namespace AudioCore::Sink | ||||
|   | ||||
| @@ -21,58 +21,80 @@ namespace { | ||||
| struct SinkDetails { | ||||
|     using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | ||||
|     using ListDevicesFn = std::vector<std::string> (*)(bool); | ||||
|     using LatencyFn = u32 (*)(); | ||||
|  | ||||
|     /// Name for this sink. | ||||
|     const char* id; | ||||
|     std::string_view id; | ||||
|     /// A method to call to construct an instance of this type of sink. | ||||
|     FactoryFn factory; | ||||
|     /// A method to call to list available devices. | ||||
|     ListDevicesFn list_devices; | ||||
|     /// Method to get the latency of this backend. | ||||
|     LatencyFn latency; | ||||
| }; | ||||
|  | ||||
| // sink_details is ordered in terms of desirability, with the best choice at the top. | ||||
| constexpr SinkDetails sink_details[] = { | ||||
| #ifdef HAVE_CUBEB | ||||
|     SinkDetails{"cubeb", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<CubebSink>(device_id); | ||||
|                 }, | ||||
|                 &ListCubebSinkDevices}, | ||||
|     SinkDetails{ | ||||
|         "cubeb", | ||||
|         [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|             return std::make_unique<CubebSink>(device_id); | ||||
|         }, | ||||
|         &ListCubebSinkDevices, | ||||
|         &GetCubebLatency, | ||||
|     }, | ||||
| #endif | ||||
| #ifdef HAVE_SDL2 | ||||
|     SinkDetails{"sdl2", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<SDLSink>(device_id); | ||||
|                 }, | ||||
|                 &ListSDLSinkDevices}, | ||||
|     SinkDetails{ | ||||
|         "sdl", | ||||
|         [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|             return std::make_unique<SDLSink>(device_id); | ||||
|         }, | ||||
|         &ListSDLSinkDevices, | ||||
|         &GetSDLLatency, | ||||
|     }, | ||||
| #endif | ||||
|     SinkDetails{"null", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<NullSink>(device_id); | ||||
|                 }, | ||||
|                 [](bool capture) { return std::vector<std::string>{"null"}; }}, | ||||
|                 [](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }}, | ||||
| }; | ||||
|  | ||||
| const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { | ||||
|     auto iter = | ||||
|         std::find_if(std::begin(sink_details), std::end(sink_details), | ||||
|                      [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); | ||||
|     const auto find_backend{[](std::string_view id) { | ||||
|         return std::find_if(std::begin(sink_details), std::end(sink_details), | ||||
|                             [&id](const auto& sink_detail) { return sink_detail.id == id; }); | ||||
|     }}; | ||||
|  | ||||
|     if (sink_id == "auto" || iter == std::end(sink_details)) { | ||||
|         if (sink_id != "auto") { | ||||
|             LOG_ERROR(Audio, "Invalid sink_id {}", sink_id); | ||||
|     auto iter = find_backend(sink_id); | ||||
|  | ||||
|     if (sink_id == "auto") { | ||||
|         // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which | ||||
|         // causes audio issues, in that case go with SDL. | ||||
| #if defined(HAVE_CUBEB) && defined(HAVE_SDL2) | ||||
|         iter = find_backend("cubeb"); | ||||
|         if (iter->latency() > TargetSampleCount * 3) { | ||||
|             iter = find_backend("sdl"); | ||||
|         } | ||||
|         // Auto-select. | ||||
|         // sink_details is ordered in terms of desirability, with the best choice at the front. | ||||
| #else | ||||
|         iter = std::begin(sink_details); | ||||
| #endif | ||||
|         LOG_INFO(Service_Audio, "Auto-selecting the {} backend", iter->id); | ||||
|     } | ||||
|  | ||||
|     if (iter == std::end(sink_details)) { | ||||
|         LOG_ERROR(Audio, "Invalid sink_id {}", sink_id); | ||||
|         iter = find_backend("null"); | ||||
|     } | ||||
|  | ||||
|     return *iter; | ||||
| } | ||||
| } // Anonymous namespace | ||||
|  | ||||
| std::vector<const char*> GetSinkIDs() { | ||||
|     std::vector<const char*> sink_ids(std::size(sink_details)); | ||||
| std::vector<std::string_view> GetSinkIDs() { | ||||
|     std::vector<std::string_view> sink_ids(std::size(sink_details)); | ||||
|  | ||||
|     std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), | ||||
|                    [](const auto& sink) { return sink.id; }); | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class Sink; | ||||
|  * | ||||
|  * @return Vector of available sink names. | ||||
|  */ | ||||
| std::vector<const char*> GetSinkIDs(); | ||||
| std::vector<std::string_view> GetSinkIDs(); | ||||
|  | ||||
| /** | ||||
|  * Gets the list of devices for a particular sink identified by the given ID. | ||||
|   | ||||
| @@ -161,8 +161,8 @@ void ConfigureAudio::InitializeAudioSinkComboBox() { | ||||
|     ui->sink_combo_box->clear(); | ||||
|     ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); | ||||
|  | ||||
|     for (const char* id : AudioCore::Sink::GetSinkIDs()) { | ||||
|         ui->sink_combo_box->addItem(QString::fromUtf8(id)); | ||||
|     for (const auto& id : AudioCore::Sink::GetSinkIDs()) { | ||||
|         ui->sink_combo_box->addItem(QString::fromUtf8(id.data(), static_cast<s32>(id.length()))); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user