1 
2 #include "config.h"
3 
4 #include "oboe.h"
5 
6 #include <cassert>
7 #include <cstring>
8 
9 #include "alu.h"
10 #include "core/logging.h"
11 
12 #include "oboe/Oboe.h"
13 
14 
15 namespace {
16 
17 constexpr char device_name[] = "Oboe Default";
18 
19 
20 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
21     OboePlayback(ALCdevice *device) : BackendBase{device} { }
22 
23     oboe::ManagedStream mStream;
24 
25     oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
26         int32_t numFrames) override;
27 
28     void open(const char *name) override;
29     bool reset() override;
30     void start() override;
31     void stop() override;
32 };
33 
34 
35 oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
36     int32_t numFrames)
37 {
38     assert(numFrames > 0);
39     const int32_t numChannels{oboeStream->getChannelCount()};
40 
41     if UNLIKELY(numChannels > 2 && mDevice->FmtChans == DevFmtStereo)
42     {
43         /* If the device is only mixing stereo but there's more than two
44          * output channels, there are unused channels that need to be silenced.
45          */
46         if(mStream->getFormat() == oboe::AudioFormat::Float)
47             memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(float));
48         else
49             memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(int16_t));
50     }
51 
52     mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
53         static_cast<uint32_t>(numChannels));
54     return oboe::DataCallbackResult::Continue;
55 }
56 
57 
58 void OboePlayback::open(const char *name)
59 {
60     if(!name)
61         name = device_name;
62     else if(std::strcmp(name, device_name) != 0)
63         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
64             name};
65 
66     /* Open a basic output stream, just to ensure it can work. */
67     oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
68         ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
69         ->openManagedStream(mStream)};
70     if(result != oboe::Result::OK)
71         throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
72             oboe::convertToText(result)};
73 
74     mDevice->DeviceName = name;
75 }
76 
77 bool OboePlayback::reset()
78 {
79     oboe::AudioStreamBuilder builder;
80     builder.setDirection(oboe::Direction::Output);
81     builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
82     /* Don't let Oboe convert. We should be able to handle anything it gives
83      * back.
84      */
85     builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
86     builder.setChannelConversionAllowed(false);
87     builder.setFormatConversionAllowed(false);
88     builder.setCallback(this);
89 
90     if(mDevice->Flags.test(FrequencyRequest))
91         builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
92     if(mDevice->Flags.test(ChannelsRequest))
93     {
94         /* Only use mono or stereo at user request. There's no telling what
95          * other counts may be inferred as.
96          */
97         builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
98             : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
99             : oboe::ChannelCount::Unspecified);
100     }
101     if(mDevice->Flags.test(SampleTypeRequest))
102     {
103         oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
104         switch(mDevice->FmtType)
105         {
106         case DevFmtByte:
107         case DevFmtUByte:
108         case DevFmtShort:
109         case DevFmtUShort:
110             format = oboe::AudioFormat::I16;
111             break;
112         case DevFmtInt:
113         case DevFmtUInt:
114         case DevFmtFloat:
115             format = oboe::AudioFormat::Float;
116             break;
117         }
118         builder.setFormat(format);
119     }
120 
121     oboe::Result result{builder.openManagedStream(mStream)};
122     /* If the format failed, try asking for the defaults. */
123     while(result == oboe::Result::ErrorInvalidFormat)
124     {
125         if(builder.getFormat() != oboe::AudioFormat::Unspecified)
126             builder.setFormat(oboe::AudioFormat::Unspecified);
127         else if(builder.getSampleRate() != oboe::kUnspecified)
128             builder.setSampleRate(oboe::kUnspecified);
129         else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
130             builder.setChannelCount(oboe::ChannelCount::Unspecified);
131         else
132             break;
133         result = builder.openManagedStream(mStream);
134     }
135     if(result != oboe::Result::OK)
136         throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
137             oboe::convertToText(result)};
138     mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
139         mStream->getBufferCapacityInFrames()));
140     TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
141 
142     switch(mStream->getChannelCount())
143     {
144     case oboe::ChannelCount::Mono:
145         mDevice->FmtChans = DevFmtMono;
146         break;
147     case oboe::ChannelCount::Stereo:
148         mDevice->FmtChans = DevFmtStereo;
149         break;
150     /* Other potential configurations. Could be wrong, but better than failing.
151      * Assume WFX channel order.
152      */
153     case 4:
154         mDevice->FmtChans = DevFmtQuad;
155         break;
156     case 6:
157         mDevice->FmtChans = DevFmtX51Rear;
158         break;
159     case 7:
160         mDevice->FmtChans = DevFmtX61;
161         break;
162     case 8:
163         mDevice->FmtChans = DevFmtX71;
164         break;
165     default:
166         if(mStream->getChannelCount() < 1)
167             throw al::backend_exception{al::backend_error::DeviceError,
168                 "Got unhandled channel count: %d", mStream->getChannelCount()};
169         /* Assume first two channels are front left/right. We can do a stereo
170          * mix and keep the other channels silent.
171          */
172         mDevice->FmtChans = DevFmtStereo;
173         break;
174     }
175     setDefaultWFXChannelOrder();
176 
177     switch(mStream->getFormat())
178     {
179     case oboe::AudioFormat::I16:
180         mDevice->FmtType = DevFmtShort;
181         break;
182     case oboe::AudioFormat::Float:
183         mDevice->FmtType = DevFmtFloat;
184         break;
185     case oboe::AudioFormat::Unspecified:
186     case oboe::AudioFormat::Invalid:
187         throw al::backend_exception{al::backend_error::DeviceError,
188             "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
189     }
190     mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
191 
192     /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
193      * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
194      * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
195      * update size.
196      */
197     mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
198         static_cast<uint32_t>(mStream->getFramesPerBurst()));
199     mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
200         static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
201 
202     return true;
203 }
204 
205 void OboePlayback::start()
206 {
207     const oboe::Result result{mStream->start()};
208     if(result != oboe::Result::OK)
209         throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
210             oboe::convertToText(result)};
211 }
212 
213 void OboePlayback::stop()
214 {
215     oboe::Result result{mStream->stop()};
216     if(result != oboe::Result::OK)
217         throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
218             oboe::convertToText(result)};
219 }
220 
221 
222 struct OboeCapture final : public BackendBase {
223     OboeCapture(ALCdevice *device) : BackendBase{device} { }
224 
225     oboe::ManagedStream mStream;
226 
227     void open(const char *name) override;
228     void start() override;
229     void stop() override;
230     void captureSamples(al::byte *buffer, uint samples) override;
231     uint availableSamples() override;
232 };
233 
234 void OboeCapture::open(const char *name)
235 {
236     if(!name)
237         name = device_name;
238     else if(std::strcmp(name, device_name) != 0)
239         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
240             name};
241 
242     oboe::AudioStreamBuilder builder;
243     builder.setDirection(oboe::Direction::Input)
244         ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
245         ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
246         ->setChannelConversionAllowed(true)
247         ->setFormatConversionAllowed(true)
248         ->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize))
249         ->setSampleRate(static_cast<int32_t>(mDevice->Frequency));
250     /* Only use mono or stereo at user request. There's no telling what
251      * other counts may be inferred as.
252      */
253     switch(mDevice->FmtChans)
254     {
255     case DevFmtMono:
256         builder.setChannelCount(oboe::ChannelCount::Mono);
257         break;
258     case DevFmtStereo:
259         builder.setChannelCount(oboe::ChannelCount::Stereo);
260         break;
261     case DevFmtQuad:
262     case DevFmtX51:
263     case DevFmtX51Rear:
264     case DevFmtX61:
265     case DevFmtX71:
266     case DevFmtAmbi3D:
267         throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
268             DevFmtChannelsString(mDevice->FmtChans)};
269     }
270 
271     /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
272      * use a temp buffer and convert.
273      */
274     switch(mDevice->FmtType)
275     {
276     case DevFmtShort:
277         builder.setFormat(oboe::AudioFormat::I16);
278         break;
279     case DevFmtFloat:
280         builder.setFormat(oboe::AudioFormat::Float);
281         break;
282     case DevFmtByte:
283     case DevFmtUByte:
284     case DevFmtUShort:
285     case DevFmtInt:
286     case DevFmtUInt:
287         throw al::backend_exception{al::backend_error::DeviceError,
288             "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
289     }
290 
291     oboe::Result result{builder.openManagedStream(mStream)};
292     if(result != oboe::Result::OK)
293         throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
294             oboe::convertToText(result)};
295     if(static_cast<int32_t>(mDevice->BufferSize) > mStream->getBufferCapacityInFrames())
296         throw al::backend_exception{al::backend_error::DeviceError,
297             "Buffer size too large (%u > %d)", mDevice->BufferSize,
298             mStream->getBufferCapacityInFrames()};
299     auto buffer_result = mStream->setBufferSizeInFrames(static_cast<int32_t>(mDevice->BufferSize));
300     if(!buffer_result)
301         throw al::backend_exception{al::backend_error::DeviceError,
302             "Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())};
303     else if(buffer_result.value() < static_cast<int32_t>(mDevice->BufferSize))
304         throw al::backend_exception{al::backend_error::DeviceError,
305             "Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize,
306             buffer_result.value()};
307     mDevice->BufferSize = static_cast<uint>(buffer_result.value());
308 
309     TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
310 
311     mDevice->DeviceName = name;
312 }
313 
314 void OboeCapture::start()
315 {
316     const oboe::Result result{mStream->start()};
317     if(result != oboe::Result::OK)
318         throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
319             oboe::convertToText(result)};
320 }
321 
322 void OboeCapture::stop()
323 {
324     const oboe::Result result{mStream->stop()};
325     if(result != oboe::Result::OK)
326         throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
327             oboe::convertToText(result)};
328 }
329 
330 uint OboeCapture::availableSamples()
331 {
332     auto result = mStream->getAvailableFrames();
333     /* FIXME: This shouldn't report less samples than have been previously
334      * reported and not captured.
335      */
336     if(!result) return 0;
337     return static_cast<uint>(result.value());
338 }
339 
340 void OboeCapture::captureSamples(al::byte *buffer, uint samples)
341 {
342     auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0);
343     uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
344     if(got < samples)
345     {
346         auto frame_size = static_cast<uint>(mStream->getBytesPerFrame());
347         std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{});
348     }
349 }
350 
351 } // namespace
352 
353 bool OboeBackendFactory::init() { return true; }
354 
355 bool OboeBackendFactory::querySupport(BackendType type)
356 { return type == BackendType::Playback || type == BackendType::Capture; }
357 
358 std::string OboeBackendFactory::probe(BackendType type)
359 {
360     switch(type)
361     {
362     case BackendType::Playback:
363     case BackendType::Capture:
364         /* Includes null char. */
365         return std::string{device_name, sizeof(device_name)};
366     }
367     return std::string{};
368 }
369 
370 BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type)
371 {
372     if(type == BackendType::Playback)
373         return BackendPtr{new OboePlayback{device}};
374     if(type == BackendType::Capture)
375         return BackendPtr{new OboeCapture{device}};
376     return BackendPtr{};
377 }
378 
379 BackendFactory &OboeBackendFactory::getFactory()
380 {
381     static OboeBackendFactory factory{};
382     return factory;
383 }
384