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