Improve formatting for setting the UWP default device callback
[openal-soft.git] / alc / backends / oboe.cpp
blob6bd720cbf7d829dbcfd4ae404b2cceadcbd59aeb
2 #include "config.h"
4 #include "oboe.h"
6 #include <cassert>
7 #include <cstdint>
8 #include <cstring>
10 #include "alnumeric.h"
11 #include "alstring.h"
12 #include "core/device.h"
13 #include "core/logging.h"
14 #include "ringbuffer.h"
16 #include "oboe/Oboe.h"
19 namespace {
21 using namespace std::string_view_literals;
23 [[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; }
26 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
27 OboePlayback(DeviceBase *device) : BackendBase{device} { }
29 oboe::ManagedStream mStream;
31 oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
32 int32_t numFrames) override;
34 void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override;
36 void open(std::string_view name) override;
37 bool reset() override;
38 void start() override;
39 void stop() override;
43 oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
44 int32_t numFrames)
46 assert(numFrames > 0);
47 const int32_t numChannels{oboeStream->getChannelCount()};
49 mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
50 static_cast<uint32_t>(numChannels));
51 return oboe::DataCallbackResult::Continue;
54 void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error)
56 if(error == oboe::Result::ErrorDisconnected)
57 mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error));
58 TRACE("Error was %s", oboe::convertToText(error));
61 void OboePlayback::open(std::string_view name)
63 if(name.empty())
64 name = GetDeviceName();
65 else if(name != GetDeviceName())
66 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
67 al::sizei(name), name.data()};
69 /* Open a basic output stream, just to ensure it can work. */
70 oboe::ManagedStream stream;
71 oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
72 ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
73 ->openManagedStream(stream)};
74 if(result != oboe::Result::OK)
75 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
76 oboe::convertToText(result)};
78 mDeviceName = name;
81 bool OboePlayback::reset()
83 oboe::AudioStreamBuilder builder;
84 builder.setDirection(oboe::Direction::Output);
85 builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
86 builder.setUsage(oboe::Usage::Game);
87 /* Don't let Oboe convert. We should be able to handle anything it gives
88 * back.
90 builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
91 builder.setChannelConversionAllowed(false);
92 builder.setFormatConversionAllowed(false);
93 builder.setCallback(this);
95 if(mDevice->Flags.test(FrequencyRequest))
97 builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
98 builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
100 if(mDevice->Flags.test(ChannelsRequest))
102 /* Only use mono or stereo at user request. There's no telling what
103 * other counts may be inferred as.
105 builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
106 : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
107 : oboe::ChannelCount::Unspecified);
109 if(mDevice->Flags.test(SampleTypeRequest))
111 oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
112 switch(mDevice->FmtType)
114 case DevFmtByte:
115 case DevFmtUByte:
116 case DevFmtShort:
117 case DevFmtUShort:
118 format = oboe::AudioFormat::I16;
119 break;
120 case DevFmtInt:
121 case DevFmtUInt:
122 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
123 format = oboe::AudioFormat::I32;
124 break;
125 #endif
126 case DevFmtFloat:
127 format = oboe::AudioFormat::Float;
128 break;
130 builder.setFormat(format);
133 oboe::Result result{builder.openManagedStream(mStream)};
134 /* If the format failed, try asking for the defaults. */
135 while(result == oboe::Result::ErrorInvalidFormat)
137 if(builder.getFormat() != oboe::AudioFormat::Unspecified)
138 builder.setFormat(oboe::AudioFormat::Unspecified);
139 else if(builder.getSampleRate() != oboe::kUnspecified)
140 builder.setSampleRate(oboe::kUnspecified);
141 else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
142 builder.setChannelCount(oboe::ChannelCount::Unspecified);
143 else
144 break;
145 result = builder.openManagedStream(mStream);
147 if(result != oboe::Result::OK)
148 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
149 oboe::convertToText(result)};
150 mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->BufferSize),
151 mStream->getBufferCapacityInFrames()));
152 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
154 if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
156 if(mStream->getChannelCount() >= 2)
157 mDevice->FmtChans = DevFmtStereo;
158 else if(mStream->getChannelCount() == 1)
159 mDevice->FmtChans = DevFmtMono;
160 else
161 throw al::backend_exception{al::backend_error::DeviceError,
162 "Got unhandled channel count: %d", mStream->getChannelCount()};
164 setDefaultWFXChannelOrder();
166 switch(mStream->getFormat())
168 case oboe::AudioFormat::I16:
169 mDevice->FmtType = DevFmtShort;
170 break;
171 case oboe::AudioFormat::Float:
172 mDevice->FmtType = DevFmtFloat;
173 break;
174 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
175 case oboe::AudioFormat::I32:
176 mDevice->FmtType = DevFmtInt;
177 break;
178 case oboe::AudioFormat::I24:
179 #endif
180 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8)
181 case oboe::AudioFormat::IEC61937:
182 #endif
183 case oboe::AudioFormat::Unspecified:
184 case oboe::AudioFormat::Invalid:
185 throw al::backend_exception{al::backend_error::DeviceError,
186 "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
188 mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
190 /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
191 * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
192 * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
193 * update size.
195 mDevice->UpdateSize = std::max(mDevice->Frequency/100u,
196 static_cast<uint32_t>(mStream->getFramesPerBurst()));
197 mDevice->BufferSize = std::max(mDevice->UpdateSize*2u,
198 static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
200 return true;
203 void OboePlayback::start()
205 const oboe::Result result{mStream->start()};
206 if(result != oboe::Result::OK)
207 throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
208 oboe::convertToText(result)};
211 void OboePlayback::stop()
213 oboe::Result result{mStream->stop()};
214 if(result != oboe::Result::OK)
215 ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
219 struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
220 OboeCapture(DeviceBase *device) : BackendBase{device} { }
222 oboe::ManagedStream mStream;
224 RingBufferPtr mRing{nullptr};
226 oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
227 int32_t numFrames) override;
229 void open(std::string_view name) override;
230 void start() override;
231 void stop() override;
232 void captureSamples(std::byte *buffer, uint samples) override;
233 uint availableSamples() override;
236 oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
237 int32_t numFrames)
239 std::ignore = mRing->write(audioData, static_cast<uint32_t>(numFrames));
240 return oboe::DataCallbackResult::Continue;
244 void OboeCapture::open(std::string_view name)
246 if(name.empty())
247 name = GetDeviceName();
248 else if(name != GetDeviceName())
249 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
250 al::sizei(name), name.data()};
252 oboe::AudioStreamBuilder builder;
253 builder.setDirection(oboe::Direction::Input)
254 ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
255 ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
256 ->setChannelConversionAllowed(true)
257 ->setFormatConversionAllowed(true)
258 ->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
259 ->setCallback(this);
260 /* Only use mono or stereo at user request. There's no telling what
261 * other counts may be inferred as.
263 switch(mDevice->FmtChans)
265 case DevFmtMono:
266 builder.setChannelCount(oboe::ChannelCount::Mono);
267 break;
268 case DevFmtStereo:
269 builder.setChannelCount(oboe::ChannelCount::Stereo);
270 break;
271 case DevFmtQuad:
272 case DevFmtX51:
273 case DevFmtX61:
274 case DevFmtX71:
275 case DevFmtX714:
276 case DevFmtX7144:
277 case DevFmtX3D71:
278 case DevFmtAmbi3D:
279 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
280 DevFmtChannelsString(mDevice->FmtChans)};
283 /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
284 * convert.
286 switch(mDevice->FmtType)
288 case DevFmtShort:
289 builder.setFormat(oboe::AudioFormat::I16);
290 break;
291 case DevFmtFloat:
292 builder.setFormat(oboe::AudioFormat::Float);
293 break;
294 case DevFmtInt:
295 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
296 builder.setFormat(oboe::AudioFormat::I32);
297 break;
298 #endif
299 case DevFmtByte:
300 case DevFmtUByte:
301 case DevFmtUShort:
302 case DevFmtUInt:
303 throw al::backend_exception{al::backend_error::DeviceError,
304 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
307 oboe::Result result{builder.openManagedStream(mStream)};
308 if(result != oboe::Result::OK)
309 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
310 oboe::convertToText(result)};
312 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
314 /* Ensure a minimum ringbuffer size of 100ms. */
315 mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u),
316 static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
318 mDeviceName = name;
321 void OboeCapture::start()
323 const oboe::Result result{mStream->start()};
324 if(result != oboe::Result::OK)
325 throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
326 oboe::convertToText(result)};
329 void OboeCapture::stop()
331 const oboe::Result result{mStream->stop()};
332 if(result != oboe::Result::OK)
333 ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
336 uint OboeCapture::availableSamples()
337 { return static_cast<uint>(mRing->readSpace()); }
339 void OboeCapture::captureSamples(std::byte *buffer, uint samples)
340 { std::ignore = mRing->read(buffer, samples); }
342 } // namespace
344 bool OboeBackendFactory::init() { return true; }
346 bool OboeBackendFactory::querySupport(BackendType type)
347 { return type == BackendType::Playback || type == BackendType::Capture; }
349 auto OboeBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
351 switch(type)
353 case BackendType::Playback:
354 case BackendType::Capture:
355 return std::vector{std::string{GetDeviceName()}};
357 return {};
360 BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
362 if(type == BackendType::Playback)
363 return BackendPtr{new OboePlayback{device}};
364 if(type == BackendType::Capture)
365 return BackendPtr{new OboeCapture{device}};
366 return BackendPtr{};
369 BackendFactory &OboeBackendFactory::getFactory()
371 static OboeBackendFactory factory{};
372 return factory;