Skip to content

Commit

Permalink
Implement Android Oboe audio error recovery mode. (#940)
Browse files Browse the repository at this point in the history
Context: #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.
  • Loading branch information
atsushieno authored Jul 15, 2021
1 parent fc21d28 commit 4240d31
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 41 deletions.
9 changes: 9 additions & 0 deletions doc/fluidsettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,15 @@ and commit the results. Refresh with the following command:
Sets the performance mode as pointed out by Oboe's documentation.
</desc>
</setting>
<setting>
<name>oboe.error-recovery-mode</name>
<type>str</type>
<def>Reconnect</def>
<vals>Reconnect, Stop</vals>
<desc>
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.
</desc>
</setting>
<setting>
<name>oss.device</name>
<type>str</type>
Expand Down
145 changes: 104 additions & 41 deletions src/drivers/fluid_oboe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ using namespace oboe;
constexpr int NUM_CHANNELS = 2;

class OboeAudioStreamCallback;
class OboeAudioStreamErrorCallback;

/** fluid_oboe_audio_driver_t
*
Expand All @@ -55,7 +56,16 @@ typedef struct
fluid_synth_t *synth = nullptr;
bool cont = false;
std::unique_ptr<OboeAudioStreamCallback> oboe_callback;
std::unique_ptr<OboeAudioStreamErrorCallback> oboe_error_callback;
std::shared_ptr<AudioStream> 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;


Expand Down Expand Up @@ -93,20 +103,34 @@ class OboeAudioStreamCallback : public AudioStreamCallback
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");
Expand All @@ -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)
Expand Down Expand Up @@ -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
*/
Expand All @@ -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<OboeAudioStreamCallback>(dev);
dev->oboe_error_callback = std::make_unique<OboeAudioStreamErrorCallback>(dev);

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;

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;

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)
{
Expand Down Expand Up @@ -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

0 comments on commit 4240d31

Please sign in to comment.