From 4240d31e519828d9faf3a76506f5c78f5875464a Mon Sep 17 00:00:00 2001 From: Atsushi Eno Date: Fri, 16 Jul 2021 04:40:07 +0900 Subject: [PATCH] Implement Android Oboe audio error recovery mode. (#940) Context: https://github.com/FluidSynth/fluidsynth/discussions/931 There is a new settings "audio.oboe.error-recovery-mode" which has string value of "Reconnect" (default) or "Stop". Under `Reconnect` mode, it automatically recreate AudioStream for the same audio device ID (which is the default = valid device, unless a specific ID is specified). The behavior is the same as OpenSLES. In the future Fluidsynth might want to provide consistent error handling mode for audio device unplugged state, but so far this change makes apps behave not too weird. --- doc/fluidsettings.xml | 9 +++ src/drivers/fluid_oboe.cpp | 143 ++++++++++++++++++++++++++----------- 2 files changed, 112 insertions(+), 40 deletions(-) diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index 20ee5105..264e7b57 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -590,6 +590,15 @@ and commit the results. Refresh with the following command: Sets the performance mode as pointed out by Oboe's documentation. + + oboe.error-recovery-mode + str + Reconnect + Reconnect, Stop + + Sets the error recovery mode when audio device error such as earphone disconnection occurred. It reconnects by default (same as OpenSLES behavior), but can be stopped if Stop is specified. + + oss.device str diff --git a/src/drivers/fluid_oboe.cpp b/src/drivers/fluid_oboe.cpp index eb7de5dc..f95af01f 100644 --- a/src/drivers/fluid_oboe.cpp +++ b/src/drivers/fluid_oboe.cpp @@ -43,6 +43,7 @@ using namespace oboe; constexpr int NUM_CHANNELS = 2; class OboeAudioStreamCallback; +class OboeAudioStreamErrorCallback; /** fluid_oboe_audio_driver_t * @@ -55,7 +56,16 @@ typedef struct fluid_synth_t *synth = nullptr; bool cont = false; std::unique_ptr oboe_callback; + std::unique_ptr oboe_error_callback; std::shared_ptr stream; + + double sample_rate; + int is_sample_format_float; + int device_id; + int sharing_mode; // 0: Shared, 1: Exclusive + int performance_mode; // 0: None, 1: PowerSaving, 2: LowLatency + oboe::SampleRateConversionQuality srate_conversion_quality; + int error_recovery_mode; // 0: Reconnect, 1: Stop } fluid_oboe_audio_driver_t; @@ -93,20 +103,34 @@ private: void *user_data; }; +class OboeAudioStreamErrorCallback : public AudioStreamErrorCallback +{ + fluid_oboe_audio_driver_t *dev; + +public: + OboeAudioStreamErrorCallback(fluid_oboe_audio_driver_t *dev) : dev(dev) {} + + void onErrorAfterClose(AudioStream *stream, Result result); +}; + +constexpr char OBOE_ID[] = "audio.oboe.id"; +constexpr char SHARING_MODE[] = "audio.oboe.sharing-mode"; +constexpr char PERF_MODE[] = "audio.oboe.performance-mode"; constexpr char SRCQ_SET[] = "audio.oboe.sample-rate-conversion-quality"; +constexpr char RECOVERY_MODE[] = "audio.oboe.error-recovery-mode"; void fluid_oboe_audio_driver_settings(fluid_settings_t *settings) { - fluid_settings_register_int(settings, "audio.oboe.id", 0, 0, 0x7FFFFFFF, 0); + fluid_settings_register_int(settings, OBOE_ID, 0, 0, 0x7FFFFFFF, 0); - fluid_settings_register_str(settings, "audio.oboe.sharing-mode", "Shared", 0); - fluid_settings_add_option(settings, "audio.oboe.sharing-mode", "Shared"); - fluid_settings_add_option(settings, "audio.oboe.sharing-mode", "Exclusive"); + fluid_settings_register_str(settings, SHARING_MODE, "Shared", 0); + fluid_settings_add_option(settings, SHARING_MODE, "Shared"); + fluid_settings_add_option(settings, SHARING_MODE, "Exclusive"); - fluid_settings_register_str(settings, "audio.oboe.performance-mode", "None", 0); - fluid_settings_add_option(settings, "audio.oboe.performance-mode", "None"); - fluid_settings_add_option(settings, "audio.oboe.performance-mode", "PowerSaving"); - fluid_settings_add_option(settings, "audio.oboe.performance-mode", "LowLatency"); + fluid_settings_register_str(settings, PERF_MODE, "None", 0); + fluid_settings_add_option(settings, PERF_MODE, "None"); + fluid_settings_add_option(settings, PERF_MODE, "PowerSaving"); + fluid_settings_add_option(settings, PERF_MODE, "LowLatency"); fluid_settings_register_str(settings, SRCQ_SET, "Medium", 0); fluid_settings_add_option(settings, SRCQ_SET, "None"); @@ -115,6 +139,10 @@ void fluid_oboe_audio_driver_settings(fluid_settings_t *settings) fluid_settings_add_option(settings, SRCQ_SET, "Medium"); fluid_settings_add_option(settings, SRCQ_SET, "High"); fluid_settings_add_option(settings, SRCQ_SET, "Best"); + + fluid_settings_register_str(settings, RECOVERY_MODE, "Reconnect", 0); + fluid_settings_add_option(settings, RECOVERY_MODE, "Reconnect"); + fluid_settings_add_option(settings, RECOVERY_MODE, "Stop"); } static oboe::SampleRateConversionQuality get_srate_conversion_quality(fluid_settings_t *settings) @@ -157,6 +185,28 @@ static oboe::SampleRateConversionQuality get_srate_conversion_quality(fluid_sett return q; } +Result +fluid_oboe_connect_or_reconnect(fluid_oboe_audio_driver_t *dev) +{ + AudioStreamBuilder builder; + builder.setDeviceId(dev->device_id) + ->setDirection(Direction::Output) + ->setChannelCount(NUM_CHANNELS) + ->setSampleRate(dev->sample_rate) + ->setFormat(dev->is_sample_format_float ? AudioFormat::Float : AudioFormat::I16) + ->setSharingMode(dev->sharing_mode == 1 ? SharingMode::Exclusive : SharingMode::Shared) + ->setPerformanceMode( + dev->performance_mode == 1 ? PerformanceMode::PowerSaving : + dev->performance_mode == 2 ? PerformanceMode::LowLatency : PerformanceMode::None) + ->setUsage(Usage::Media) + ->setContentType(ContentType::Music) + ->setCallback(dev->oboe_callback.get()) + ->setErrorCallback(dev->oboe_error_callback.get()) + ->setSampleRateConversionQuality(dev->srate_conversion_quality); + + return builder.openStream(dev->stream); +} + /* * new_fluid_oboe_audio_driver */ @@ -168,44 +218,24 @@ new_fluid_oboe_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) try { Result result; - AudioStreamBuilder builder_obj; - AudioStreamBuilder *builder = &builder_obj; - - double sample_rate; - int is_sample_format_float; - int device_id; - int sharing_mode; // 0: Shared, 1: Exclusive - int performance_mode; // 0: None, 1: PowerSaving, 2: LowLatency - dev = new fluid_oboe_audio_driver_t(); dev->synth = synth; dev->oboe_callback = std::make_unique(dev); + dev->oboe_error_callback = std::make_unique(dev); - fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); - is_sample_format_float = fluid_settings_str_equal(settings, "audio.sample-format", "float"); - fluid_settings_getint(settings, "audio.oboe.id", &device_id); - sharing_mode = - fluid_settings_str_equal(settings, "audio.oboe.sharing-mode", "Exclusive") ? 1 : 0; - performance_mode = - fluid_settings_str_equal(settings, "audio.oboe.performance-mode", "PowerSaving") ? 1 : - fluid_settings_str_equal(settings, "audio.oboe.performance-mode", "LowLatency") ? 2 : 0; + fluid_settings_getnum(settings, "synth.sample-rate", &dev->sample_rate); + dev->is_sample_format_float = fluid_settings_str_equal(settings, "audio.sample-format", "float"); + fluid_settings_getint(settings, OBOE_ID, &dev->device_id); + dev->sharing_mode = + fluid_settings_str_equal(settings, SHARING_MODE, "Exclusive") ? 1 : 0; + dev->performance_mode = + fluid_settings_str_equal(settings, PERF_MODE, "PowerSaving") ? 1 : + fluid_settings_str_equal(settings, PERF_MODE, "LowLatency") ? 2 : 0; + dev->srate_conversion_quality = get_srate_conversion_quality(settings); + dev->error_recovery_mode = fluid_settings_str_equal(settings, RECOVERY_MODE, "Stop") ? 1 : 0; - builder->setDeviceId(device_id) - ->setDirection(Direction::Output) - ->setChannelCount(NUM_CHANNELS) - ->setSampleRate(sample_rate) - ->setFormat(is_sample_format_float ? AudioFormat::Float : AudioFormat::I16) - ->setSharingMode(sharing_mode == 1 ? SharingMode::Exclusive : SharingMode::Shared) - ->setPerformanceMode( - performance_mode == 1 ? PerformanceMode::PowerSaving : - performance_mode == 2 ? PerformanceMode::LowLatency : PerformanceMode::None) - ->setUsage(Usage::Media) - ->setContentType(ContentType::Music) - ->setCallback(dev->oboe_callback.get()) - ->setSampleRateConversionQuality(get_srate_conversion_quality(settings)); - - result = builder->openStream(dev->stream); + result = fluid_oboe_connect_or_reconnect(dev); if(result != Result::OK) { @@ -269,5 +299,38 @@ void delete_fluid_oboe_audio_driver(fluid_audio_driver_t *p) delete dev; } + +void +OboeAudioStreamErrorCallback::onErrorAfterClose(AudioStream *stream, Result result) +{ + if(dev->error_recovery_mode == 1) // Stop + { + FLUID_LOG(FLUID_ERR, "Oboe driver encountered an error (such as earphone unplugged). Stopped."); + dev->stream.reset(); + return; + } + else + { + FLUID_LOG(FLUID_WARN, "Oboe driver encountered an error (such as earphone unplugged). Recovering..."); + } + + result = fluid_oboe_connect_or_reconnect(dev); + + if(result != Result::OK) + { + FLUID_LOG(FLUID_ERR, "Unable to reconnect Oboe audio stream"); + return; // cannot do anything further + } + + // start the new stream. + result = dev->stream->start(); + + if(result != Result::OK) + { + FLUID_LOG(FLUID_ERR, "Unable to restart Oboe audio stream"); + return; // cannot do anything further + } +} + #endif // OBOE_SUPPORT