1 /* FluidSynth - A Software Synthesizer
2  *
3  * Copyright (C) 2003  Peter Hanappe and others.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public License
7  * as published by the Free Software Foundation; either version 2 of
8  * the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301, USA
19  */
20 
21 /* fluid_oboe.c
22  *
23  * Audio driver for Android Oboe.
24  *
25  * This file may make use of C++14, because it's required by oboe anyway.
26  */
27 
28 extern "C" {
29 
30 #include "fluid_adriver.h"
31 #include "fluid_settings.h"
32 
33 } // extern "C"
34 
35 #if OBOE_SUPPORT
36 
37 #include <oboe/Oboe.h>
38 #include <sstream>
39 #include <stdexcept>
40 
41 using namespace oboe;
42 
43 constexpr int NUM_CHANNELS = 2;
44 
45 class OboeAudioStreamCallback;
46 class OboeAudioStreamErrorCallback;
47 
48 /** fluid_oboe_audio_driver_t
49  *
50  * This structure should not be accessed directly. Use audio port
51  * functions instead.
52  */
53 typedef struct
54 {
55     fluid_audio_driver_t driver;
56     fluid_synth_t *synth = nullptr;
57     bool cont = false;
58     std::unique_ptr<OboeAudioStreamCallback> oboe_callback;
59     std::unique_ptr<OboeAudioStreamErrorCallback> oboe_error_callback;
60     std::shared_ptr<AudioStream> stream;
61 
62     double sample_rate;
63     int is_sample_format_float;
64     int device_id;
65     int sharing_mode; // 0: Shared, 1: Exclusive
66     int performance_mode; // 0: None, 1: PowerSaving, 2: LowLatency
67     oboe::SampleRateConversionQuality srate_conversion_quality;
68     int error_recovery_mode; // 0: Reconnect, 1: Stop
69 } fluid_oboe_audio_driver_t;
70 
71 
72 class OboeAudioStreamCallback : public AudioStreamCallback
73 {
74 public:
75 
OboeAudioStreamCallback(void * userData)76     OboeAudioStreamCallback(void *userData)
77         : user_data(userData)
78     {
79     }
80 
onAudioReady(AudioStream * stream,void * audioData,int32_t numFrames)81     DataCallbackResult onAudioReady(AudioStream *stream, void *audioData, int32_t numFrames)
82     {
83         fluid_oboe_audio_driver_t *dev = static_cast<fluid_oboe_audio_driver_t *>(this->user_data);
84 
85         if(!dev->cont)
86         {
87             return DataCallbackResult::Stop;
88         }
89 
90         if(stream->getFormat() == AudioFormat::Float)
91         {
92             fluid_synth_write_float(dev->synth, numFrames, static_cast<float *>(audioData), 0, 2, static_cast<float *>(audioData), 1, 2);
93         }
94         else
95         {
96             fluid_synth_write_s16(dev->synth, numFrames, static_cast<short *>(audioData), 0, 2, static_cast<short *>(audioData), 1, 2);
97         }
98 
99         return DataCallbackResult::Continue;
100     }
101 
102 private:
103     void *user_data;
104 };
105 
106 class OboeAudioStreamErrorCallback : public AudioStreamErrorCallback
107 {
108     fluid_oboe_audio_driver_t *dev;
109 
110 public:
OboeAudioStreamErrorCallback(fluid_oboe_audio_driver_t * dev)111     OboeAudioStreamErrorCallback(fluid_oboe_audio_driver_t *dev) : dev(dev) {}
112 
113     void onErrorAfterClose(AudioStream *stream, Result result);
114 };
115 
116 constexpr char OBOE_ID[] = "audio.oboe.id";
117 constexpr char SHARING_MODE[] = "audio.oboe.sharing-mode";
118 constexpr char PERF_MODE[] = "audio.oboe.performance-mode";
119 constexpr char SRCQ_SET[] = "audio.oboe.sample-rate-conversion-quality";
120 constexpr char RECOVERY_MODE[] = "audio.oboe.error-recovery-mode";
121 
fluid_oboe_audio_driver_settings(fluid_settings_t * settings)122 void fluid_oboe_audio_driver_settings(fluid_settings_t *settings)
123 {
124     fluid_settings_register_int(settings, OBOE_ID, 0, 0, 0x7FFFFFFF, 0);
125 
126     fluid_settings_register_str(settings, SHARING_MODE, "Shared", 0);
127     fluid_settings_add_option(settings,   SHARING_MODE, "Shared");
128     fluid_settings_add_option(settings,   SHARING_MODE, "Exclusive");
129 
130     fluid_settings_register_str(settings, PERF_MODE, "None", 0);
131     fluid_settings_add_option(settings,   PERF_MODE, "None");
132     fluid_settings_add_option(settings,   PERF_MODE, "PowerSaving");
133     fluid_settings_add_option(settings,   PERF_MODE, "LowLatency");
134 
135     fluid_settings_register_str(settings, SRCQ_SET, "Medium", 0);
136     fluid_settings_add_option(settings,   SRCQ_SET, "None");
137     fluid_settings_add_option(settings,   SRCQ_SET, "Fastest");
138     fluid_settings_add_option(settings,   SRCQ_SET, "Low");
139     fluid_settings_add_option(settings,   SRCQ_SET, "Medium");
140     fluid_settings_add_option(settings,   SRCQ_SET, "High");
141     fluid_settings_add_option(settings,   SRCQ_SET, "Best");
142 
143     fluid_settings_register_str(settings, RECOVERY_MODE, "Reconnect", 0);
144     fluid_settings_add_option(settings, RECOVERY_MODE, "Reconnect");
145     fluid_settings_add_option(settings, RECOVERY_MODE, "Stop");
146 }
147 
get_srate_conversion_quality(fluid_settings_t * settings)148 static oboe::SampleRateConversionQuality get_srate_conversion_quality(fluid_settings_t *settings)
149 {
150     oboe::SampleRateConversionQuality q;
151 
152     if(fluid_settings_str_equal(settings, SRCQ_SET, "None"))
153     {
154         q = oboe::SampleRateConversionQuality::None;
155     }
156     else if(fluid_settings_str_equal(settings, SRCQ_SET, "Fastest"))
157     {
158         q = oboe::SampleRateConversionQuality::Fastest;
159     }
160     else if(fluid_settings_str_equal(settings, SRCQ_SET, "Low"))
161     {
162         q = oboe::SampleRateConversionQuality::Low;
163     }
164     else if(fluid_settings_str_equal(settings, SRCQ_SET, "Medium"))
165     {
166         q = oboe::SampleRateConversionQuality::Medium;
167     }
168     else if(fluid_settings_str_equal(settings, SRCQ_SET, "High"))
169     {
170         q = oboe::SampleRateConversionQuality::High;
171     }
172     else if(fluid_settings_str_equal(settings, SRCQ_SET, "Best"))
173     {
174         q = oboe::SampleRateConversionQuality::Best;
175     }
176     else
177     {
178         char buf[256];
179         fluid_settings_copystr(settings, SRCQ_SET, buf, sizeof(buf));
180         std::stringstream ss;
181         ss << "'" << SRCQ_SET << "' has unexpected value '" << buf << "'";
182         throw std::runtime_error(ss.str());
183     }
184 
185     return q;
186 }
187 
188 Result
fluid_oboe_connect_or_reconnect(fluid_oboe_audio_driver_t * dev)189 fluid_oboe_connect_or_reconnect(fluid_oboe_audio_driver_t *dev)
190 {
191     AudioStreamBuilder builder;
192     builder.setDeviceId(dev->device_id)
193     ->setDirection(Direction::Output)
194     ->setChannelCount(NUM_CHANNELS)
195     ->setSampleRate(dev->sample_rate)
196     ->setFormat(dev->is_sample_format_float ? AudioFormat::Float : AudioFormat::I16)
197     ->setSharingMode(dev->sharing_mode == 1 ? SharingMode::Exclusive : SharingMode::Shared)
198     ->setPerformanceMode(
199         dev->performance_mode == 1 ? PerformanceMode::PowerSaving :
200         dev->performance_mode == 2 ? PerformanceMode::LowLatency : PerformanceMode::None)
201     ->setUsage(Usage::Media)
202     ->setContentType(ContentType::Music)
203     ->setCallback(dev->oboe_callback.get())
204     ->setErrorCallback(dev->oboe_error_callback.get())
205     ->setSampleRateConversionQuality(dev->srate_conversion_quality);
206 
207     return builder.openStream(dev->stream);
208 }
209 
210 /*
211  * new_fluid_oboe_audio_driver
212  */
213 fluid_audio_driver_t *
new_fluid_oboe_audio_driver(fluid_settings_t * settings,fluid_synth_t * synth)214 new_fluid_oboe_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
215 {
216     fluid_oboe_audio_driver_t *dev = nullptr;
217 
218     try
219     {
220         Result result;
221         dev = new fluid_oboe_audio_driver_t();
222 
223         dev->synth = synth;
224         dev->oboe_callback = std::make_unique<OboeAudioStreamCallback>(dev);
225         dev->oboe_error_callback = std::make_unique<OboeAudioStreamErrorCallback>(dev);
226 
227         fluid_settings_getnum(settings, "synth.sample-rate", &dev->sample_rate);
228         dev->is_sample_format_float = fluid_settings_str_equal(settings, "audio.sample-format", "float");
229         fluid_settings_getint(settings, OBOE_ID, &dev->device_id);
230         dev->sharing_mode =
231             fluid_settings_str_equal(settings, SHARING_MODE, "Exclusive") ? 1 : 0;
232         dev->performance_mode =
233             fluid_settings_str_equal(settings, PERF_MODE, "PowerSaving") ? 1 :
234             fluid_settings_str_equal(settings, PERF_MODE, "LowLatency") ? 2 : 0;
235         dev->srate_conversion_quality = get_srate_conversion_quality(settings);
236         dev->error_recovery_mode = fluid_settings_str_equal(settings, RECOVERY_MODE, "Stop") ? 1 : 0;
237 
238         result = fluid_oboe_connect_or_reconnect(dev);
239 
240         if(result != Result::OK)
241         {
242             FLUID_LOG(FLUID_ERR, "Unable to open Oboe audio stream");
243             goto error_recovery;
244         }
245 
246         dev->cont = true;
247 
248         FLUID_LOG(FLUID_INFO, "Using Oboe driver");
249 
250         result = dev->stream->start();
251 
252         if(result != Result::OK)
253         {
254             FLUID_LOG(FLUID_ERR, "Unable to start Oboe audio stream");
255             goto error_recovery;
256         }
257 
258         return &dev->driver;
259     }
260     catch(const std::bad_alloc &)
261     {
262         FLUID_LOG(FLUID_ERR, "oboe: std::bad_alloc caught: Out of memory");
263     }
264     catch(const std::exception &e)
265     {
266         FLUID_LOG(FLUID_ERR, "oboe: std::exception caught: %s", e.what());
267     }
268     catch(...)
269     {
270         FLUID_LOG(FLUID_ERR, "Unexpected Oboe driver initialization error");
271     }
272 
273 error_recovery:
274     delete_fluid_oboe_audio_driver(reinterpret_cast<fluid_audio_driver_t *>(dev));
275     return nullptr;
276 }
277 
delete_fluid_oboe_audio_driver(fluid_audio_driver_t * p)278 void delete_fluid_oboe_audio_driver(fluid_audio_driver_t *p)
279 {
280     fluid_oboe_audio_driver_t *dev = reinterpret_cast<fluid_oboe_audio_driver_t *>(p);
281 
282     fluid_return_if_fail(dev != nullptr);
283 
284     try
285     {
286         dev->cont = false;
287 
288         if(dev->stream != nullptr)
289         {
290             dev->stream->stop();
291             dev->stream->close();
292         }
293     }
294     catch(...)
295     {
296         FLUID_LOG(FLUID_ERR, "Exception caught while stopping and closing Oboe stream.");
297     }
298 
299     delete dev;
300 }
301 
302 
303 void
onErrorAfterClose(AudioStream * stream,Result result)304 OboeAudioStreamErrorCallback::onErrorAfterClose(AudioStream *stream, Result result)
305 {
306     if(dev->error_recovery_mode == 1)    // Stop
307     {
308         FLUID_LOG(FLUID_ERR, "Oboe driver encountered an error (such as earphone unplugged). Stopped.");
309         dev->stream.reset();
310         return;
311     }
312     else
313     {
314         FLUID_LOG(FLUID_WARN, "Oboe driver encountered an error (such as earphone unplugged). Recovering...");
315     }
316 
317     result = fluid_oboe_connect_or_reconnect(dev);
318 
319     if(result != Result::OK)
320     {
321         FLUID_LOG(FLUID_ERR, "Unable to reconnect Oboe audio stream");
322         return; // cannot do anything further
323     }
324 
325     // start the new stream.
326     result = dev->stream->start();
327 
328     if(result != Result::OK)
329     {
330         FLUID_LOG(FLUID_ERR, "Unable to restart Oboe audio stream");
331         return; // cannot do anything further
332     }
333 }
334 
335 #endif // OBOE_SUPPORT
336 
337