10 #include "alnumeric.h"
11 #include "core/device.h"
12 #include "core/logging.h"
13 #include "ringbuffer.h"
15 #include "oboe/Oboe.h"
20 constexpr char device_name[] = "Oboe Default";
23 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
24 OboePlayback(DeviceBase *device) : BackendBase{device} { }
26 oboe::ManagedStream mStream;
28 oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
29 int32_t numFrames) override;
31 void open(const char *name) override;
32 bool reset() override;
33 void start() override;
38 oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
41 assert(numFrames > 0);
42 const int32_t numChannels{oboeStream->getChannelCount()};
44 mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
45 static_cast<uint32_t>(numChannels));
46 return oboe::DataCallbackResult::Continue;
50 void OboePlayback::open(const char *name)
54 else if(std::strcmp(name, device_name) != 0)
55 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
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)};
67 mDevice->DeviceName = name;
70 bool OboePlayback::reset()
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
78 builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
79 builder.setChannelConversionAllowed(false);
80 builder.setFormatConversionAllowed(false);
81 builder.setCallback(this);
83 if(mDevice->Flags.test(FrequencyRequest))
85 builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
86 builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
88 if(mDevice->Flags.test(ChannelsRequest))
90 /* Only use mono or stereo at user request. There's no telling what
91 * other counts may be inferred as.
93 builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
94 : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
95 : oboe::ChannelCount::Unspecified);
97 if(mDevice->Flags.test(SampleTypeRequest))
99 oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
100 switch(mDevice->FmtType)
106 format = oboe::AudioFormat::I16;
110 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
111 format = oboe::AudioFormat::I32;
115 format = oboe::AudioFormat::Float;
118 builder.setFormat(format);
121 oboe::Result result{builder.openManagedStream(mStream)};
122 /* If the format failed, try asking for the defaults. */
123 while(result == oboe::Result::ErrorInvalidFormat)
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);
133 result = builder.openManagedStream(mStream);
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()));
142 if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
144 if(mStream->getChannelCount() >= 2)
145 mDevice->FmtChans = DevFmtStereo;
146 else if(mStream->getChannelCount() == 1)
147 mDevice->FmtChans = DevFmtMono;
149 throw al::backend_exception{al::backend_error::DeviceError,
150 "Got unhandled channel count: %d", mStream->getChannelCount()};
152 setDefaultWFXChannelOrder();
154 switch(mStream->getFormat())
156 case oboe::AudioFormat::I16:
157 mDevice->FmtType = DevFmtShort;
159 case oboe::AudioFormat::Float:
160 mDevice->FmtType = DevFmtFloat;
162 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
163 case oboe::AudioFormat::I32:
164 mDevice->FmtType = DevFmtInt;
166 case oboe::AudioFormat::I24:
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())};
173 mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
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
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()));
188 void OboePlayback::start()
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)};
196 void OboePlayback::stop()
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)};
205 struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
206 OboeCapture(DeviceBase *device) : BackendBase{device} { }
208 oboe::ManagedStream mStream;
210 RingBufferPtr mRing{nullptr};
212 oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
213 int32_t numFrames) override;
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;
222 oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
225 mRing->write(audioData, static_cast<uint32_t>(numFrames));
226 return oboe::DataCallbackResult::Continue;
230 void OboeCapture::open(const char *name)
234 else if(std::strcmp(name, device_name) != 0)
235 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
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))
246 /* Only use mono or stereo at user request. There's no telling what
247 * other counts may be inferred as.
249 switch(mDevice->FmtChans)
252 builder.setChannelCount(oboe::ChannelCount::Mono);
255 builder.setChannelCount(oboe::ChannelCount::Stereo);
264 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
265 DevFmtChannelsString(mDevice->FmtChans)};
268 /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
271 switch(mDevice->FmtType)
274 builder.setFormat(oboe::AudioFormat::I16);
277 builder.setFormat(oboe::AudioFormat::Float);
280 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
281 builder.setFormat(oboe::AudioFormat::I32);
288 throw al::backend_exception{al::backend_error::DeviceError,
289 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
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)};
297 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
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);
303 mDevice->DeviceName = name;
306 void OboeCapture::start()
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)};
314 void OboeCapture::stop()
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)};
322 uint OboeCapture::availableSamples()
323 { return static_cast<uint>(mRing->readSpace()); }
325 void OboeCapture::captureSamples(al::byte *buffer, uint samples)
326 { mRing->read(buffer, samples); }
330 bool OboeBackendFactory::init() { return true; }
332 bool OboeBackendFactory::querySupport(BackendType type)
333 { return type == BackendType::Playback || type == BackendType::Capture; }
335 std::string OboeBackendFactory::probe(BackendType type)
339 case BackendType::Playback:
340 case BackendType::Capture:
341 /* Includes null char. */
342 return std::string{device_name, sizeof(device_name)};
344 return std::string{};
347 BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
349 if(type == BackendType::Playback)
350 return BackendPtr{new OboePlayback{device}};
351 if(type == BackendType::Capture)
352 return BackendPtr{new OboeCapture{device}};
356 BackendFactory &OboeBackendFactory::getFactory()
358 static OboeBackendFactory factory{};