diff options
Diffstat (limited to 'src/audio_core')
| -rw-r--r-- | src/audio_core/sink/cubeb_sink.cpp | 52 | ||||
| -rw-r--r-- | src/audio_core/sink/cubeb_sink.h | 7 | ||||
| -rw-r--r-- | src/audio_core/sink/sdl2_sink.cpp | 40 | ||||
| -rw-r--r-- | src/audio_core/sink/sdl2_sink.h | 7 | ||||
| -rw-r--r-- | src/audio_core/sink/sink_details.cpp | 49 |
5 files changed, 117 insertions, 38 deletions
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 9a0801888..04d98a865 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include "audio_core/sink/cubeb_sink.h" | 8 | #include "audio_core/sink/cubeb_sink.h" |
| 9 | #include "audio_core/sink/sink_stream.h" | 9 | #include "audio_core/sink/sink_stream.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/scope_exit.h" | ||
| 11 | #include "core/core.h" | 12 | #include "core/core.h" |
| 12 | 13 | ||
| 13 | #ifdef _WIN32 | 14 | #ifdef _WIN32 |
| @@ -332,25 +333,38 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) { | |||
| 332 | return device_list; | 333 | return device_list; |
| 333 | } | 334 | } |
| 334 | 335 | ||
| 335 | u32 GetCubebLatency() { | 336 | namespace { |
| 336 | cubeb* ctx; | 337 | static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) { |
| 338 | return TargetSampleCount; | ||
| 339 | } | ||
| 340 | static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {} | ||
| 341 | } // namespace | ||
| 342 | |||
| 343 | bool IsCubebSuitable() { | ||
| 344 | #if !defined(HAVE_CUBEB) | ||
| 345 | return false; | ||
| 346 | #else | ||
| 347 | cubeb* ctx{nullptr}; | ||
| 337 | 348 | ||
| 338 | #ifdef _WIN32 | 349 | #ifdef _WIN32 |
| 339 | auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | 350 | auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); |
| 340 | #endif | 351 | #endif |
| 341 | 352 | ||
| 353 | // Init cubeb | ||
| 342 | if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) { | 354 | if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) { |
| 343 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | 355 | LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable."); |
| 344 | // Return a large latency so we choose SDL instead. | 356 | return false; |
| 345 | return 10000u; | ||
| 346 | } | 357 | } |
| 347 | 358 | ||
| 359 | SCOPE_EXIT({ cubeb_destroy(ctx); }); | ||
| 360 | |||
| 348 | #ifdef _WIN32 | 361 | #ifdef _WIN32 |
| 349 | if (SUCCEEDED(com_init_result)) { | 362 | if (SUCCEEDED(com_init_result)) { |
| 350 | CoUninitialize(); | 363 | CoUninitialize(); |
| 351 | } | 364 | } |
| 352 | #endif | 365 | #endif |
| 353 | 366 | ||
| 367 | // Test min latency | ||
| 354 | cubeb_stream_params params{}; | 368 | cubeb_stream_params params{}; |
| 355 | params.rate = TargetSampleRate; | 369 | params.rate = TargetSampleRate; |
| 356 | params.channels = 2; | 370 | params.channels = 2; |
| @@ -361,12 +375,32 @@ u32 GetCubebLatency() { | |||
| 361 | u32 latency{0}; | 375 | u32 latency{0}; |
| 362 | const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency); | 376 | const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency); |
| 363 | if (latency_error != CUBEB_OK) { | 377 | if (latency_error != CUBEB_OK) { |
| 364 | LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); | 378 | LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable."); |
| 365 | latency = TargetSampleCount * 2; | 379 | return false; |
| 366 | } | 380 | } |
| 367 | latency = std::max(latency, TargetSampleCount * 2); | 381 | latency = std::max(latency, TargetSampleCount * 2); |
| 368 | cubeb_destroy(ctx); | 382 | |
| 369 | return latency; | 383 | if (latency > TargetSampleCount * 3) { |
| 384 | LOG_ERROR(Audio_Sink, "Cubeb latency is too high, it is not suitable."); | ||
| 385 | return false; | ||
| 386 | } | ||
| 387 | |||
| 388 | // Test opening a device with standard parameters | ||
| 389 | cubeb_devid output_device{0}; | ||
| 390 | cubeb_devid input_device{0}; | ||
| 391 | std::string name{"Yuzu test"}; | ||
| 392 | cubeb_stream* stream{nullptr}; | ||
| 393 | |||
| 394 | if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, ¶ms, | ||
| 395 | latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) { | ||
| 396 | LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable."); | ||
| 397 | return false; | ||
| 398 | } | ||
| 399 | |||
| 400 | cubeb_stream_stop(stream); | ||
| 401 | cubeb_stream_destroy(stream); | ||
| 402 | return true; | ||
| 403 | #endif | ||
| 370 | } | 404 | } |
| 371 | 405 | ||
| 372 | } // namespace AudioCore::Sink | 406 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index 3302cb98d..f49a6fdaa 100644 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h | |||
| @@ -97,10 +97,11 @@ private: | |||
| 97 | std::vector<std::string> ListCubebSinkDevices(bool capture); | 97 | std::vector<std::string> ListCubebSinkDevices(bool capture); |
| 98 | 98 | ||
| 99 | /** | 99 | /** |
| 100 | * Get the reported latency for this sink. | 100 | * Check if this backend is suitable for use. |
| 101 | * Checks if enabled, its latency, whether it opens successfully, etc. | ||
| 101 | * | 102 | * |
| 102 | * @return Minimum latency for this sink. | 103 | * @return True is this backend is suitable, false otherwise. |
| 103 | */ | 104 | */ |
| 104 | u32 GetCubebLatency(); | 105 | bool IsCubebSuitable(); |
| 105 | 106 | ||
| 106 | } // namespace AudioCore::Sink | 107 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index c1529d1f9..7b89151de 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "audio_core/sink/sdl2_sink.h" | 9 | #include "audio_core/sink/sdl2_sink.h" |
| 10 | #include "audio_core/sink/sink_stream.h" | 10 | #include "audio_core/sink/sink_stream.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/scope_exit.h" | ||
| 12 | #include "core/core.h" | 13 | #include "core/core.h" |
| 13 | 14 | ||
| 14 | namespace AudioCore::Sink { | 15 | namespace AudioCore::Sink { |
| @@ -84,6 +85,7 @@ public: | |||
| 84 | } | 85 | } |
| 85 | 86 | ||
| 86 | Stop(); | 87 | Stop(); |
| 88 | SDL_ClearQueuedAudio(device); | ||
| 87 | SDL_CloseAudioDevice(device); | 89 | SDL_CloseAudioDevice(device); |
| 88 | } | 90 | } |
| 89 | 91 | ||
| @@ -227,8 +229,42 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) { | |||
| 227 | return device_list; | 229 | return device_list; |
| 228 | } | 230 | } |
| 229 | 231 | ||
| 230 | u32 GetSDLLatency() { | 232 | bool IsSDLSuitable() { |
| 231 | return TargetSampleCount * 2; | 233 | #if !defined(HAVE_SDL2) |
| 234 | return false; | ||
| 235 | #else | ||
| 236 | // Check SDL can init | ||
| 237 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { | ||
| 238 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||
| 239 | LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}", | ||
| 240 | SDL_GetError()); | ||
| 241 | return false; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | // We can set any latency frequency we want with SDL, so no need to check that. | ||
| 246 | |||
| 247 | // Check we can open a device with standard parameters | ||
| 248 | SDL_AudioSpec spec; | ||
| 249 | spec.freq = TargetSampleRate; | ||
| 250 | spec.channels = 2u; | ||
| 251 | spec.format = AUDIO_S16SYS; | ||
| 252 | spec.samples = TargetSampleCount * 2; | ||
| 253 | spec.callback = nullptr; | ||
| 254 | spec.userdata = nullptr; | ||
| 255 | |||
| 256 | SDL_AudioSpec obtained; | ||
| 257 | auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false); | ||
| 258 | |||
| 259 | if (device == 0) { | ||
| 260 | LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}", | ||
| 261 | SDL_GetError()); | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | |||
| 265 | SDL_CloseAudioDevice(device); | ||
| 266 | return true; | ||
| 267 | #endif | ||
| 232 | } | 268 | } |
| 233 | 269 | ||
| 234 | } // namespace AudioCore::Sink | 270 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h index 27ed1ab94..9211d2e97 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h | |||
| @@ -88,10 +88,11 @@ private: | |||
| 88 | std::vector<std::string> ListSDLSinkDevices(bool capture); | 88 | std::vector<std::string> ListSDLSinkDevices(bool capture); |
| 89 | 89 | ||
| 90 | /** | 90 | /** |
| 91 | * Get the reported latency for this sink. | 91 | * Check if this backend is suitable for use. |
| 92 | * Checks if enabled, its latency, whether it opens successfully, etc. | ||
| 92 | * | 93 | * |
| 93 | * @return Minimum latency for this sink. | 94 | * @return True is this backend is suitable, false otherwise. |
| 94 | */ | 95 | */ |
| 95 | u32 GetSDLLatency(); | 96 | bool IsSDLSuitable(); |
| 96 | 97 | ||
| 97 | } // namespace AudioCore::Sink | 98 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 027bfa517..6bec8ee7c 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp | |||
| @@ -22,7 +22,7 @@ namespace { | |||
| 22 | struct SinkDetails { | 22 | struct SinkDetails { |
| 23 | using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | 23 | using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); |
| 24 | using ListDevicesFn = std::vector<std::string> (*)(bool); | 24 | using ListDevicesFn = std::vector<std::string> (*)(bool); |
| 25 | using LatencyFn = u32 (*)(); | 25 | using SuitableFn = bool (*)(); |
| 26 | 26 | ||
| 27 | /// Name for this sink. | 27 | /// Name for this sink. |
| 28 | Settings::AudioEngine id; | 28 | Settings::AudioEngine id; |
| @@ -30,8 +30,8 @@ struct SinkDetails { | |||
| 30 | FactoryFn factory; | 30 | FactoryFn factory; |
| 31 | /// A method to call to list available devices. | 31 | /// A method to call to list available devices. |
| 32 | ListDevicesFn list_devices; | 32 | ListDevicesFn list_devices; |
| 33 | /// Method to get the latency of this backend. | 33 | /// Check whether this backend is suitable to be used. |
| 34 | LatencyFn latency; | 34 | SuitableFn is_suitable; |
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | // sink_details is ordered in terms of desirability, with the best choice at the top. | 37 | // sink_details is ordered in terms of desirability, with the best choice at the top. |
| @@ -43,7 +43,7 @@ constexpr SinkDetails sink_details[] = { | |||
| 43 | return std::make_unique<CubebSink>(device_id); | 43 | return std::make_unique<CubebSink>(device_id); |
| 44 | }, | 44 | }, |
| 45 | &ListCubebSinkDevices, | 45 | &ListCubebSinkDevices, |
| 46 | &GetCubebLatency, | 46 | &IsCubebSuitable, |
| 47 | }, | 47 | }, |
| 48 | #endif | 48 | #endif |
| 49 | #ifdef HAVE_SDL2 | 49 | #ifdef HAVE_SDL2 |
| @@ -53,14 +53,17 @@ constexpr SinkDetails sink_details[] = { | |||
| 53 | return std::make_unique<SDLSink>(device_id); | 53 | return std::make_unique<SDLSink>(device_id); |
| 54 | }, | 54 | }, |
| 55 | &ListSDLSinkDevices, | 55 | &ListSDLSinkDevices, |
| 56 | &GetSDLLatency, | 56 | &IsSDLSuitable, |
| 57 | }, | 57 | }, |
| 58 | #endif | 58 | #endif |
| 59 | SinkDetails{Settings::AudioEngine::Null, | 59 | SinkDetails{ |
| 60 | [](std::string_view device_id) -> std::unique_ptr<Sink> { | 60 | Settings::AudioEngine::Null, |
| 61 | return std::make_unique<NullSink>(device_id); | 61 | [](std::string_view device_id) -> std::unique_ptr<Sink> { |
| 62 | }, | 62 | return std::make_unique<NullSink>(device_id); |
| 63 | [](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }}, | 63 | }, |
| 64 | [](bool capture) { return std::vector<std::string>{"null"}; }, | ||
| 65 | []() { return true; }, | ||
| 66 | }, | ||
| 64 | }; | 67 | }; |
| 65 | 68 | ||
| 66 | const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { | 69 | const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { |
| @@ -72,18 +75,22 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { | |||
| 72 | auto iter = find_backend(sink_id); | 75 | auto iter = find_backend(sink_id); |
| 73 | 76 | ||
| 74 | if (sink_id == Settings::AudioEngine::Auto) { | 77 | if (sink_id == Settings::AudioEngine::Auto) { |
| 75 | // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which | 78 | // Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking |
| 76 | // causes audio issues, in that case go with SDL. | 79 | // that the backend is available and suitable to use. |
| 77 | #if defined(HAVE_CUBEB) && defined(HAVE_SDL2) | 80 | for (auto& details : sink_details) { |
| 78 | iter = find_backend(Settings::AudioEngine::Cubeb); | 81 | if (details.is_suitable()) { |
| 79 | if (iter->latency() > TargetSampleCount * 3) { | 82 | iter = &details; |
| 80 | iter = find_backend(Settings::AudioEngine::Sdl2); | 83 | break; |
| 84 | } | ||
| 85 | } | ||
| 86 | LOG_ERROR(Service_Audio, "Auto-selecting the {} backend", | ||
| 87 | Settings::CanonicalizeEnum(iter->id)); | ||
| 88 | } else { | ||
| 89 | if (iter != std::end(sink_details) && !iter->is_suitable()) { | ||
| 90 | LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null", | ||
| 91 | Settings::CanonicalizeEnum(iter->id)); | ||
| 92 | iter = find_backend(Settings::AudioEngine::Null); | ||
| 81 | } | 93 | } |
| 82 | #else | ||
| 83 | iter = std::begin(sink_details); | ||
| 84 | #endif | ||
| 85 | LOG_INFO(Service_Audio, "Auto-selecting the {} backend", | ||
| 86 | Settings::CanonicalizeEnum(iter->id)); | ||
| 87 | } | 94 | } |
| 88 | 95 | ||
| 89 | if (iter == std::end(sink_details)) { | 96 | if (iter == std::end(sink_details)) { |