Choose the SDL audio backend when Cubeb reports too high of a latency
This commit is contained in:
		| @@ -66,10 +66,10 @@ public: | |||||||
|         const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency); |         const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency); | ||||||
|         if (latency_error != CUBEB_OK) { |         if (latency_error != CUBEB_OK) { | ||||||
|             LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); |             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, |         LOG_INFO(Service_Audio, | ||||||
|                  "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " |                  "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " | ||||||
| @@ -326,4 +326,31 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) { | |||||||
|     return device_list; |     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 | } // namespace AudioCore::Sink | ||||||
|   | |||||||
| @@ -96,4 +96,11 @@ private: | |||||||
|  */ |  */ | ||||||
| std::vector<std::string> ListCubebSinkDevices(bool capture); | 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 | } // namespace AudioCore::Sink | ||||||
|   | |||||||
| @@ -47,11 +47,7 @@ public: | |||||||
|         spec.freq = TargetSampleRate; |         spec.freq = TargetSampleRate; | ||||||
|         spec.channels = static_cast<u8>(device_channels); |         spec.channels = static_cast<u8>(device_channels); | ||||||
|         spec.format = AUDIO_S16SYS; |         spec.format = AUDIO_S16SYS; | ||||||
|         if (type == StreamType::Render) { |         spec.samples = TargetSampleCount * 2; | ||||||
|             spec.samples = TargetSampleCount; |  | ||||||
|         } else { |  | ||||||
|             spec.samples = 1024; |  | ||||||
|         } |  | ||||||
|         spec.callback = &SDLSinkStream::DataCallback; |         spec.callback = &SDLSinkStream::DataCallback; | ||||||
|         spec.userdata = this; |         spec.userdata = this; | ||||||
|  |  | ||||||
| @@ -240,4 +236,8 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) { | |||||||
|     return device_list; |     return device_list; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | u32 GetSDLLatency() { | ||||||
|  |     return TargetSampleCount * 2; | ||||||
|  | } | ||||||
|  |  | ||||||
| } // namespace AudioCore::Sink | } // namespace AudioCore::Sink | ||||||
|   | |||||||
| @@ -87,4 +87,11 @@ private: | |||||||
|  */ |  */ | ||||||
| std::vector<std::string> ListSDLSinkDevices(bool capture); | 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 | } // namespace AudioCore::Sink | ||||||
|   | |||||||
| @@ -21,58 +21,80 @@ namespace { | |||||||
| struct SinkDetails { | struct SinkDetails { | ||||||
|     using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); |     using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | ||||||
|     using ListDevicesFn = std::vector<std::string> (*)(bool); |     using ListDevicesFn = std::vector<std::string> (*)(bool); | ||||||
|  |     using LatencyFn = u32 (*)(); | ||||||
|  |  | ||||||
|     /// Name for this sink. |     /// Name for this sink. | ||||||
|     const char* id; |     std::string_view id; | ||||||
|     /// A method to call to construct an instance of this type of sink. |     /// A method to call to construct an instance of this type of sink. | ||||||
|     FactoryFn factory; |     FactoryFn factory; | ||||||
|     /// A method to call to list available devices. |     /// A method to call to list available devices. | ||||||
|     ListDevicesFn list_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. | // sink_details is ordered in terms of desirability, with the best choice at the top. | ||||||
| constexpr SinkDetails sink_details[] = { | constexpr SinkDetails sink_details[] = { | ||||||
| #ifdef HAVE_CUBEB | #ifdef HAVE_CUBEB | ||||||
|     SinkDetails{"cubeb", |     SinkDetails{ | ||||||
|  |         "cubeb", | ||||||
|         [](std::string_view device_id) -> std::unique_ptr<Sink> { |         [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||||
|             return std::make_unique<CubebSink>(device_id); |             return std::make_unique<CubebSink>(device_id); | ||||||
|         }, |         }, | ||||||
|                 &ListCubebSinkDevices}, |         &ListCubebSinkDevices, | ||||||
|  |         &GetCubebLatency, | ||||||
|  |     }, | ||||||
| #endif | #endif | ||||||
| #ifdef HAVE_SDL2 | #ifdef HAVE_SDL2 | ||||||
|     SinkDetails{"sdl2", |     SinkDetails{ | ||||||
|  |         "sdl", | ||||||
|         [](std::string_view device_id) -> std::unique_ptr<Sink> { |         [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||||
|             return std::make_unique<SDLSink>(device_id); |             return std::make_unique<SDLSink>(device_id); | ||||||
|         }, |         }, | ||||||
|                 &ListSDLSinkDevices}, |         &ListSDLSinkDevices, | ||||||
|  |         &GetSDLLatency, | ||||||
|  |     }, | ||||||
| #endif | #endif | ||||||
|     SinkDetails{"null", |     SinkDetails{"null", | ||||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { |                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||||
|                     return std::make_unique<NullSink>(device_id); |                     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) { | const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { | ||||||
|     auto iter = |     const auto find_backend{[](std::string_view id) { | ||||||
|         std::find_if(std::begin(sink_details), std::end(sink_details), |         return std::find_if(std::begin(sink_details), std::end(sink_details), | ||||||
|                      [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); |                             [&id](const auto& sink_detail) { return sink_detail.id == id; }); | ||||||
|  |     }}; | ||||||
|  |  | ||||||
|     if (sink_id == "auto" || iter == std::end(sink_details)) { |     auto iter = find_backend(sink_id); | ||||||
|         if (sink_id != "auto") { |  | ||||||
|             LOG_ERROR(Audio, "Invalid sink_id {}", 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. | #else | ||||||
|         // sink_details is ordered in terms of desirability, with the best choice at the front. |  | ||||||
|         iter = std::begin(sink_details); |         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; |     return *iter; | ||||||
| } | } | ||||||
| } // Anonymous namespace | } // Anonymous namespace | ||||||
|  |  | ||||||
| std::vector<const char*> GetSinkIDs() { | std::vector<std::string_view> GetSinkIDs() { | ||||||
|     std::vector<const char*> sink_ids(std::size(sink_details)); |     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), |     std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), | ||||||
|                    [](const auto& sink) { return sink.id; }); |                    [](const auto& sink) { return sink.id; }); | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ class Sink; | |||||||
|  * |  * | ||||||
|  * @return Vector of available sink names. |  * @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. |  * 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->clear(); | ||||||
|     ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); |     ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); | ||||||
|  |  | ||||||
|     for (const char* id : AudioCore::Sink::GetSinkIDs()) { |     for (const auto& id : AudioCore::Sink::GetSinkIDs()) { | ||||||
|         ui->sink_combo_box->addItem(QString::fromUtf8(id)); |         ui->sink_combo_box->addItem(QString::fromUtf8(id.data(), static_cast<s32>(id.length()))); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user