]> git.tdb.fi Git - ext/openal.git/blob - alc/backends/oboe.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / backends / oboe.cpp
1
2 #include "config.h"
3
4 #include "oboe.h"
5
6 #include <cassert>
7 #include <cstring>
8 #include <stdint.h>
9
10 #include "alnumeric.h"
11 #include "core/device.h"
12 #include "core/logging.h"
13 #include "ringbuffer.h"
14
15 #include "oboe/Oboe.h"
16
17
18 namespace {
19
20 constexpr char device_name[] = "Oboe Default";
21
22
23 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
24     OboePlayback(DeviceBase *device) : BackendBase{device} { }
25
26     oboe::ManagedStream mStream;
27
28     oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
29         int32_t numFrames) override;
30
31     void open(const char *name) override;
32     bool reset() override;
33     void start() override;
34     void stop() override;
35 };
36
37
38 oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
39     int32_t numFrames)
40 {
41     assert(numFrames > 0);
42     const int32_t numChannels{oboeStream->getChannelCount()};
43
44     mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
45         static_cast<uint32_t>(numChannels));
46     return oboe::DataCallbackResult::Continue;
47 }
48
49
50 void OboePlayback::open(const char *name)
51 {
52     if(!name)
53         name = device_name;
54     else if(std::strcmp(name, device_name) != 0)
55         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
56             name};
57
58     /* Open a basic output stream, just to ensure it can work. */
59     oboe::ManagedStream stream;
60     oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
61         ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
62         ->openManagedStream(stream)};
63     if(result != oboe::Result::OK)
64         throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
65             oboe::convertToText(result)};
66
67     mDevice->DeviceName = name;
68 }
69
70 bool OboePlayback::reset()
71 {
72     oboe::AudioStreamBuilder builder;
73     builder.setDirection(oboe::Direction::Output);
74     builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
75     /* Don't let Oboe convert. We should be able to handle anything it gives
76      * back.
77      */
78     builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
79     builder.setChannelConversionAllowed(false);
80     builder.setFormatConversionAllowed(false);
81     builder.setCallback(this);
82
83     if(mDevice->Flags.test(FrequencyRequest))
84     {
85         builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
86         builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
87     }
88     if(mDevice->Flags.test(ChannelsRequest))
89     {
90         /* Only use mono or stereo at user request. There's no telling what
91          * other counts may be inferred as.
92          */
93         builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
94             : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
95             : oboe::ChannelCount::Unspecified);
96     }
97     if(mDevice->Flags.test(SampleTypeRequest))
98     {
99         oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
100         switch(mDevice->FmtType)
101         {
102         case DevFmtByte:
103         case DevFmtUByte:
104         case DevFmtShort:
105         case DevFmtUShort:
106             format = oboe::AudioFormat::I16;
107             break;
108         case DevFmtInt:
109         case DevFmtUInt:
110 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
111             format = oboe::AudioFormat::I32;
112             break;
113 #endif
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     if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
143     {
144         if(mStream->getChannelCount() >= 2)
145             mDevice->FmtChans = DevFmtStereo;
146         else if(mStream->getChannelCount() == 1)
147             mDevice->FmtChans = DevFmtMono;
148         else
149             throw al::backend_exception{al::backend_error::DeviceError,
150                 "Got unhandled channel count: %d", mStream->getChannelCount()};
151     }
152     setDefaultWFXChannelOrder();
153
154     switch(mStream->getFormat())
155     {
156     case oboe::AudioFormat::I16:
157         mDevice->FmtType = DevFmtShort;
158         break;
159     case oboe::AudioFormat::Float:
160         mDevice->FmtType = DevFmtFloat;
161         break;
162 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
163     case oboe::AudioFormat::I32:
164         mDevice->FmtType = DevFmtInt;
165         break;
166     case oboe::AudioFormat::I24:
167 #endif
168     case oboe::AudioFormat::Unspecified:
169     case oboe::AudioFormat::Invalid:
170         throw al::backend_exception{al::backend_error::DeviceError,
171             "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
172     }
173     mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
174
175     /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
176      * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
177      * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
178      * update size.
179      */
180     mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
181         static_cast<uint32_t>(mStream->getFramesPerBurst()));
182     mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
183         static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
184
185     return true;
186 }
187
188 void OboePlayback::start()
189 {
190     const oboe::Result result{mStream->start()};
191     if(result != oboe::Result::OK)
192         throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
193             oboe::convertToText(result)};
194 }
195
196 void OboePlayback::stop()
197 {
198     oboe::Result result{mStream->stop()};
199     if(result != oboe::Result::OK)
200         throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
201             oboe::convertToText(result)};
202 }
203
204
205 struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
206     OboeCapture(DeviceBase *device) : BackendBase{device} { }
207
208     oboe::ManagedStream mStream;
209
210     RingBufferPtr mRing{nullptr};
211
212     oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
213         int32_t numFrames) override;
214
215     void open(const char *name) override;
216     void start() override;
217     void stop() override;
218     void captureSamples(al::byte *buffer, uint samples) override;
219     uint availableSamples() override;
220 };
221
222 oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
223     int32_t numFrames)
224 {
225     mRing->write(audioData, static_cast<uint32_t>(numFrames));
226     return oboe::DataCallbackResult::Continue;
227 }
228
229
230 void OboeCapture::open(const char *name)
231 {
232     if(!name)
233         name = device_name;
234     else if(std::strcmp(name, device_name) != 0)
235         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
236             name};
237
238     oboe::AudioStreamBuilder builder;
239     builder.setDirection(oboe::Direction::Input)
240         ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
241         ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
242         ->setChannelConversionAllowed(true)
243         ->setFormatConversionAllowed(true)
244         ->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
245         ->setCallback(this);
246     /* Only use mono or stereo at user request. There's no telling what
247      * other counts may be inferred as.
248      */
249     switch(mDevice->FmtChans)
250     {
251     case DevFmtMono:
252         builder.setChannelCount(oboe::ChannelCount::Mono);
253         break;
254     case DevFmtStereo:
255         builder.setChannelCount(oboe::ChannelCount::Stereo);
256         break;
257     case DevFmtQuad:
258     case DevFmtX51:
259     case DevFmtX61:
260     case DevFmtX71:
261     case DevFmtX714:
262     case DevFmtX3D71:
263     case DevFmtAmbi3D:
264         throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
265             DevFmtChannelsString(mDevice->FmtChans)};
266     }
267
268     /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
269      * convert.
270      */
271     switch(mDevice->FmtType)
272     {
273     case DevFmtShort:
274         builder.setFormat(oboe::AudioFormat::I16);
275         break;
276     case DevFmtFloat:
277         builder.setFormat(oboe::AudioFormat::Float);
278         break;
279     case DevFmtInt:
280 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
281         builder.setFormat(oboe::AudioFormat::I32);
282         break;
283 #endif
284     case DevFmtByte:
285     case DevFmtUByte:
286     case DevFmtUShort:
287     case DevFmtUInt:
288         throw al::backend_exception{al::backend_error::DeviceError,
289             "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
290     }
291
292     oboe::Result result{builder.openManagedStream(mStream)};
293     if(result != oboe::Result::OK)
294         throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
295             oboe::convertToText(result)};
296
297     TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
298
299     /* Ensure a minimum ringbuffer size of 100ms. */
300     mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
301         static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
302
303     mDevice->DeviceName = name;
304 }
305
306 void OboeCapture::start()
307 {
308     const oboe::Result result{mStream->start()};
309     if(result != oboe::Result::OK)
310         throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
311             oboe::convertToText(result)};
312 }
313
314 void OboeCapture::stop()
315 {
316     const oboe::Result result{mStream->stop()};
317     if(result != oboe::Result::OK)
318         throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
319             oboe::convertToText(result)};
320 }
321
322 uint OboeCapture::availableSamples()
323 { return static_cast<uint>(mRing->readSpace()); }
324
325 void OboeCapture::captureSamples(al::byte *buffer, uint samples)
326 { mRing->read(buffer, samples); }
327
328 } // namespace
329
330 bool OboeBackendFactory::init() { return true; }
331
332 bool OboeBackendFactory::querySupport(BackendType type)
333 { return type == BackendType::Playback || type == BackendType::Capture; }
334
335 std::string OboeBackendFactory::probe(BackendType type)
336 {
337     switch(type)
338     {
339     case BackendType::Playback:
340     case BackendType::Capture:
341         /* Includes null char. */
342         return std::string{device_name, sizeof(device_name)};
343     }
344     return std::string{};
345 }
346
347 BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
348 {
349     if(type == BackendType::Playback)
350         return BackendPtr{new OboePlayback{device}};
351     if(type == BackendType::Capture)
352         return BackendPtr{new OboeCapture{device}};
353     return BackendPtr{};
354 }
355
356 BackendFactory &OboeBackendFactory::getFactory()
357 {
358     static OboeBackendFactory factory{};
359     return factory;
360 }