mirror of
				https://git.suyu.dev/suyu/suyu.git
				synced 2025-10-31 23:06:43 +08:00 
			
		
		
		
	Merge pull request #11359 from Kelebek1/check_suitable_backend
Pre-test for valid audio backends
This commit is contained in:
		
						commit
						84b384fbea
					
				| @ -8,6 +8,7 @@ | ||||
| #include "audio_core/sink/cubeb_sink.h" | ||||
| #include "audio_core/sink/sink_stream.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/scope_exit.h" | ||||
| #include "core/core.h" | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| @ -332,25 +333,38 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) { | ||||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| u32 GetCubebLatency() { | ||||
|     cubeb* ctx; | ||||
| namespace { | ||||
| static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) { | ||||
|     return TargetSampleCount; | ||||
| } | ||||
| static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {} | ||||
| } // namespace
 | ||||
| 
 | ||||
| bool IsCubebSuitable() { | ||||
| #if !defined(HAVE_CUBEB) | ||||
|     return false; | ||||
| #else | ||||
|     cubeb* ctx{nullptr}; | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|     auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | ||||
| #endif | ||||
| 
 | ||||
|     // Init cubeb
 | ||||
|     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; | ||||
|         LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable."); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     SCOPE_EXIT({ cubeb_destroy(ctx); }); | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|     if (SUCCEEDED(com_init_result)) { | ||||
|         CoUninitialize(); | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     // Test min latency
 | ||||
|     cubeb_stream_params params{}; | ||||
|     params.rate = TargetSampleRate; | ||||
|     params.channels = 2; | ||||
| @ -361,12 +375,32 @@ u32 GetCubebLatency() { | ||||
|     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; | ||||
|         LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable."); | ||||
|         return false; | ||||
|     } | ||||
|     latency = std::max(latency, TargetSampleCount * 2); | ||||
|     cubeb_destroy(ctx); | ||||
|     return latency; | ||||
| 
 | ||||
|     if (latency > TargetSampleCount * 3) { | ||||
|         LOG_ERROR(Audio_Sink, "Cubeb latency is too high, it is not suitable."); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Test opening a device with standard parameters
 | ||||
|     cubeb_devid output_device{0}; | ||||
|     cubeb_devid input_device{0}; | ||||
|     std::string name{"Yuzu test"}; | ||||
|     cubeb_stream* stream{nullptr}; | ||||
| 
 | ||||
|     if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, ¶ms, | ||||
|                           latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) { | ||||
|         LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable."); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     cubeb_stream_stop(stream); | ||||
|     cubeb_stream_destroy(stream); | ||||
|     return true; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore::Sink
 | ||||
|  | ||||
| @ -97,10 +97,11 @@ private: | ||||
| std::vector<std::string> ListCubebSinkDevices(bool capture); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the reported latency for this sink. | ||||
|  * Check if this backend is suitable for use. | ||||
|  * Checks if enabled, its latency, whether it opens successfully, etc. | ||||
|  * | ||||
|  * @return Minimum latency for this sink. | ||||
|  * @return True is this backend is suitable, false otherwise. | ||||
|  */ | ||||
| u32 GetCubebLatency(); | ||||
| bool IsCubebSuitable(); | ||||
| 
 | ||||
| } // namespace AudioCore::Sink
 | ||||
|  | ||||
| @ -9,6 +9,7 @@ | ||||
| #include "audio_core/sink/sdl2_sink.h" | ||||
| #include "audio_core/sink/sink_stream.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/scope_exit.h" | ||||
| #include "core/core.h" | ||||
| 
 | ||||
| namespace AudioCore::Sink { | ||||
| @ -84,6 +85,7 @@ public: | ||||
|         } | ||||
| 
 | ||||
|         Stop(); | ||||
|         SDL_ClearQueuedAudio(device); | ||||
|         SDL_CloseAudioDevice(device); | ||||
|     } | ||||
| 
 | ||||
| @ -227,8 +229,42 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) { | ||||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| u32 GetSDLLatency() { | ||||
|     return TargetSampleCount * 2; | ||||
| bool IsSDLSuitable() { | ||||
| #if !defined(HAVE_SDL2) | ||||
|     return false; | ||||
| #else | ||||
|     // Check SDL can init
 | ||||
|     if (!SDL_WasInit(SDL_INIT_AUDIO)) { | ||||
|         if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||||
|             LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}", | ||||
|                       SDL_GetError()); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // We can set any latency frequency we want with SDL, so no need to check that.
 | ||||
| 
 | ||||
|     // Check we can open a device with standard parameters
 | ||||
|     SDL_AudioSpec spec; | ||||
|     spec.freq = TargetSampleRate; | ||||
|     spec.channels = 2u; | ||||
|     spec.format = AUDIO_S16SYS; | ||||
|     spec.samples = TargetSampleCount * 2; | ||||
|     spec.callback = nullptr; | ||||
|     spec.userdata = nullptr; | ||||
| 
 | ||||
|     SDL_AudioSpec obtained; | ||||
|     auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false); | ||||
| 
 | ||||
|     if (device == 0) { | ||||
|         LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}", | ||||
|                   SDL_GetError()); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     SDL_CloseAudioDevice(device); | ||||
|     return true; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore::Sink
 | ||||
|  | ||||
| @ -88,10 +88,11 @@ private: | ||||
| std::vector<std::string> ListSDLSinkDevices(bool capture); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the reported latency for this sink. | ||||
|  * Check if this backend is suitable for use. | ||||
|  * Checks if enabled, its latency, whether it opens successfully, etc. | ||||
|  * | ||||
|  * @return Minimum latency for this sink. | ||||
|  * @return True is this backend is suitable, false otherwise. | ||||
|  */ | ||||
| u32 GetSDLLatency(); | ||||
| bool IsSDLSuitable(); | ||||
| 
 | ||||
| } // namespace AudioCore::Sink
 | ||||
|  | ||||
| @ -22,7 +22,7 @@ namespace { | ||||
| struct SinkDetails { | ||||
|     using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | ||||
|     using ListDevicesFn = std::vector<std::string> (*)(bool); | ||||
|     using LatencyFn = u32 (*)(); | ||||
|     using SuitableFn = bool (*)(); | ||||
| 
 | ||||
|     /// Name for this sink.
 | ||||
|     Settings::AudioEngine id; | ||||
| @ -30,8 +30,8 @@ struct SinkDetails { | ||||
|     FactoryFn factory; | ||||
|     /// A method to call to list available devices.
 | ||||
|     ListDevicesFn list_devices; | ||||
|     /// Method to get the latency of this backend.
 | ||||
|     LatencyFn latency; | ||||
|     /// Check whether this backend is suitable to be used.
 | ||||
|     SuitableFn is_suitable; | ||||
| }; | ||||
| 
 | ||||
| // sink_details is ordered in terms of desirability, with the best choice at the top.
 | ||||
| @ -43,7 +43,7 @@ constexpr SinkDetails sink_details[] = { | ||||
|             return std::make_unique<CubebSink>(device_id); | ||||
|         }, | ||||
|         &ListCubebSinkDevices, | ||||
|         &GetCubebLatency, | ||||
|         &IsCubebSuitable, | ||||
|     }, | ||||
| #endif | ||||
| #ifdef HAVE_SDL2 | ||||
| @ -53,14 +53,17 @@ constexpr SinkDetails sink_details[] = { | ||||
|             return std::make_unique<SDLSink>(device_id); | ||||
|         }, | ||||
|         &ListSDLSinkDevices, | ||||
|         &GetSDLLatency, | ||||
|         &IsSDLSuitable, | ||||
|     }, | ||||
| #endif | ||||
|     SinkDetails{Settings::AudioEngine::Null, | ||||
|     SinkDetails{ | ||||
|         Settings::AudioEngine::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"}; }, []() { return 0u; }}, | ||||
|         [](bool capture) { return std::vector<std::string>{"null"}; }, | ||||
|         []() { return true; }, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { | ||||
| @ -72,18 +75,22 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { | ||||
|     auto iter = find_backend(sink_id); | ||||
| 
 | ||||
|     if (sink_id == Settings::AudioEngine::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(Settings::AudioEngine::Cubeb); | ||||
|         if (iter->latency() > TargetSampleCount * 3) { | ||||
|             iter = find_backend(Settings::AudioEngine::Sdl2); | ||||
|         // Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking
 | ||||
|         // that the backend is available and suitable to use.
 | ||||
|         for (auto& details : sink_details) { | ||||
|             if (details.is_suitable()) { | ||||
|                 iter = &details; | ||||
|                 break; | ||||
|             } | ||||
| #else | ||||
|         iter = std::begin(sink_details); | ||||
| #endif | ||||
|         LOG_INFO(Service_Audio, "Auto-selecting the {} backend", | ||||
|         } | ||||
|         LOG_ERROR(Service_Audio, "Auto-selecting the {} backend", | ||||
|                   Settings::CanonicalizeEnum(iter->id)); | ||||
|     } else { | ||||
|         if (iter != std::end(sink_details) && !iter->is_suitable()) { | ||||
|             LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null", | ||||
|                       Settings::CanonicalizeEnum(iter->id)); | ||||
|             iter = find_backend(Settings::AudioEngine::Null); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (iter == std::end(sink_details)) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user