Avoid a stateful unique_ptr deleter
[openal-soft.git] / alc / backends / oboe.cpp
blobaefda9746fed10952d4fb90db79dab0f5a9717d6
2 #include "config.h"
4 #include "oboe.h"
6 #include <cassert>
7 #include <cstring>
8 #include <stdint.h>
10 #include "alnumeric.h"
11 #include "core/device.h"
12 #include "core/logging.h"
14 #include "oboe/Oboe.h"
17 namespace {
19 constexpr char device_name[] = "Oboe Default";
22 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
23 OboePlayback(DeviceBase *device) : BackendBase{device} { }
25 oboe::ManagedStream mStream;
27 oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
28 int32_t numFrames) override;
30 void open(const char *name) override;
31 bool reset() override;
32 void start() override;
33 void stop() override;
37 oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
38 int32_t numFrames)
40 assert(numFrames > 0);
41 const int32_t numChannels{oboeStream->getChannelCount()};
43 mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
44 static_cast<uint32_t>(numChannels));
45 return oboe::DataCallbackResult::Continue;
49 void OboePlayback::open(const char *name)
51 if(!name)
52 name = device_name;
53 else if(std::strcmp(name, device_name) != 0)
54 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
55 name};
57 /* Open a basic output stream, just to ensure it can work. */
58 oboe::ManagedStream stream;
59 oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
60 ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
61 ->openManagedStream(stream)};
62 if(result != oboe::Result::OK)
63 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
64 oboe::convertToText(result)};
66 mDevice->DeviceName = name;
69 bool OboePlayback::reset()
71 oboe::AudioStreamBuilder builder;
72 builder.setDirection(oboe::Direction::Output);
73 builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
74 /* Don't let Oboe convert. We should be able to handle anything it gives
75 * back.
77 builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
78 builder.setChannelConversionAllowed(false);
79 builder.setFormatConversionAllowed(false);
80 builder.setCallback(this);
82 if(mDevice->Flags.test(FrequencyRequest))
83 builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
84 if(mDevice->Flags.test(ChannelsRequest))
86 /* Only use mono or stereo at user request. There's no telling what
87 * other counts may be inferred as.
89 builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
90 : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
91 : oboe::ChannelCount::Unspecified);
93 if(mDevice->Flags.test(SampleTypeRequest))
95 oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
96 switch(mDevice->FmtType)
98 case DevFmtByte:
99 case DevFmtUByte:
100 case DevFmtShort:
101 case DevFmtUShort:
102 format = oboe::AudioFormat::I16;
103 break;
104 case DevFmtInt:
105 case DevFmtUInt:
106 case DevFmtFloat:
107 format = oboe::AudioFormat::Float;
108 break;
110 builder.setFormat(format);
113 oboe::Result result{builder.openManagedStream(mStream)};
114 /* If the format failed, try asking for the defaults. */
115 while(result == oboe::Result::ErrorInvalidFormat)
117 if(builder.getFormat() != oboe::AudioFormat::Unspecified)
118 builder.setFormat(oboe::AudioFormat::Unspecified);
119 else if(builder.getSampleRate() != oboe::kUnspecified)
120 builder.setSampleRate(oboe::kUnspecified);
121 else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
122 builder.setChannelCount(oboe::ChannelCount::Unspecified);
123 else
124 break;
125 result = builder.openManagedStream(mStream);
127 if(result != oboe::Result::OK)
128 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
129 oboe::convertToText(result)};
130 mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
131 mStream->getBufferCapacityInFrames()));
132 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
134 if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
136 if(mStream->getChannelCount() >= 2)
137 mDevice->FmtChans = DevFmtStereo;
138 else if(mStream->getChannelCount() == 1)
139 mDevice->FmtChans = DevFmtMono;
140 else
141 throw al::backend_exception{al::backend_error::DeviceError,
142 "Got unhandled channel count: %d", mStream->getChannelCount()};
144 setDefaultWFXChannelOrder();
146 switch(mStream->getFormat())
148 case oboe::AudioFormat::I16:
149 mDevice->FmtType = DevFmtShort;
150 break;
151 case oboe::AudioFormat::Float:
152 mDevice->FmtType = DevFmtFloat;
153 break;
154 case oboe::AudioFormat::Unspecified:
155 case oboe::AudioFormat::Invalid:
156 throw al::backend_exception{al::backend_error::DeviceError,
157 "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
159 mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
161 /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
162 * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
163 * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
164 * update size.
166 mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
167 static_cast<uint32_t>(mStream->getFramesPerBurst()));
168 mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
169 static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
171 return true;
174 void OboePlayback::start()
176 const oboe::Result result{mStream->start()};
177 if(result != oboe::Result::OK)
178 throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
179 oboe::convertToText(result)};
182 void OboePlayback::stop()
184 oboe::Result result{mStream->stop()};
185 if(result != oboe::Result::OK)
186 throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
187 oboe::convertToText(result)};
191 struct OboeCapture final : public BackendBase {
192 OboeCapture(DeviceBase *device) : BackendBase{device} { }
194 oboe::ManagedStream mStream;
196 void open(const char *name) override;
197 void start() override;
198 void stop() override;
199 void captureSamples(al::byte *buffer, uint samples) override;
200 uint availableSamples() override;
203 void OboeCapture::open(const char *name)
205 if(!name)
206 name = device_name;
207 else if(std::strcmp(name, device_name) != 0)
208 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
209 name};
211 oboe::AudioStreamBuilder builder;
212 builder.setDirection(oboe::Direction::Input)
213 ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
214 ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
215 ->setChannelConversionAllowed(true)
216 ->setFormatConversionAllowed(true)
217 ->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize))
218 ->setSampleRate(static_cast<int32_t>(mDevice->Frequency));
219 /* Only use mono or stereo at user request. There's no telling what
220 * other counts may be inferred as.
222 switch(mDevice->FmtChans)
224 case DevFmtMono:
225 builder.setChannelCount(oboe::ChannelCount::Mono);
226 break;
227 case DevFmtStereo:
228 builder.setChannelCount(oboe::ChannelCount::Stereo);
229 break;
230 case DevFmtQuad:
231 case DevFmtX51:
232 case DevFmtX61:
233 case DevFmtX71:
234 case DevFmtAmbi3D:
235 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
236 DevFmtChannelsString(mDevice->FmtChans)};
239 /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
240 * use a temp buffer and convert.
242 switch(mDevice->FmtType)
244 case DevFmtShort:
245 builder.setFormat(oboe::AudioFormat::I16);
246 break;
247 case DevFmtFloat:
248 builder.setFormat(oboe::AudioFormat::Float);
249 break;
250 case DevFmtByte:
251 case DevFmtUByte:
252 case DevFmtUShort:
253 case DevFmtInt:
254 case DevFmtUInt:
255 throw al::backend_exception{al::backend_error::DeviceError,
256 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
259 oboe::Result result{builder.openManagedStream(mStream)};
260 if(result != oboe::Result::OK)
261 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
262 oboe::convertToText(result)};
263 if(static_cast<int32_t>(mDevice->BufferSize) > mStream->getBufferCapacityInFrames())
264 throw al::backend_exception{al::backend_error::DeviceError,
265 "Buffer size too large (%u > %d)", mDevice->BufferSize,
266 mStream->getBufferCapacityInFrames()};
267 auto buffer_result = mStream->setBufferSizeInFrames(static_cast<int32_t>(mDevice->BufferSize));
268 if(!buffer_result)
269 throw al::backend_exception{al::backend_error::DeviceError,
270 "Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())};
271 else if(buffer_result.value() < static_cast<int32_t>(mDevice->BufferSize))
272 throw al::backend_exception{al::backend_error::DeviceError,
273 "Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize,
274 buffer_result.value()};
275 mDevice->BufferSize = static_cast<uint>(buffer_result.value());
277 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
279 mDevice->DeviceName = name;
282 void OboeCapture::start()
284 const oboe::Result result{mStream->start()};
285 if(result != oboe::Result::OK)
286 throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
287 oboe::convertToText(result)};
290 void OboeCapture::stop()
292 const oboe::Result result{mStream->stop()};
293 if(result != oboe::Result::OK)
294 throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
295 oboe::convertToText(result)};
298 uint OboeCapture::availableSamples()
300 auto result = mStream->getAvailableFrames();
301 /* FIXME: This shouldn't report less samples than have been previously
302 * reported and not captured.
304 if(!result) return 0;
305 return static_cast<uint>(result.value());
308 void OboeCapture::captureSamples(al::byte *buffer, uint samples)
310 auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0);
311 uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
312 if(got < samples)
314 auto frame_size = static_cast<uint>(mStream->getBytesPerFrame());
315 std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{});
319 } // namespace
321 bool OboeBackendFactory::init() { return true; }
323 bool OboeBackendFactory::querySupport(BackendType type)
324 { return type == BackendType::Playback || type == BackendType::Capture; }
326 std::string OboeBackendFactory::probe(BackendType type)
328 switch(type)
330 case BackendType::Playback:
331 case BackendType::Capture:
332 /* Includes null char. */
333 return std::string{device_name, sizeof(device_name)};
335 return std::string{};
338 BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
340 if(type == BackendType::Playback)
341 return BackendPtr{new OboePlayback{device}};
342 if(type == BackendType::Capture)
343 return BackendPtr{new OboeCapture{device}};
344 return BackendPtr{};
347 BackendFactory &OboeBackendFactory::getFactory()
349 static OboeBackendFactory factory{};
350 return factory;