10 #include "alnumeric.h"
11 #include "core/device.h"
12 #include "core/logging.h"
14 #include "oboe/Oboe.h"
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
;
37 oboe::DataCallbackResult
OboePlayback::onAudioReady(oboe::AudioStream
*oboeStream
, void *audioData
,
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
)
53 else if(std::strcmp(name
, device_name
) != 0)
54 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
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
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
)
102 format
= oboe::AudioFormat::I16
;
107 format
= oboe::AudioFormat::Float
;
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
);
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
;
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
;
151 case oboe::AudioFormat::Float
:
152 mDevice
->FmtType
= DevFmtFloat
;
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
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()));
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
)
207 else if(std::strcmp(name
, device_name
) != 0)
208 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
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
)
225 builder
.setChannelCount(oboe::ChannelCount::Mono
);
228 builder
.setChannelCount(oboe::ChannelCount::Stereo
);
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
)
245 builder
.setFormat(oboe::AudioFormat::I16
);
248 builder
.setFormat(oboe::AudioFormat::Float
);
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
));
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};
314 auto frame_size
= static_cast<uint
>(mStream
->getBytesPerFrame());
315 std::fill_n(buffer
+ got
*frame_size
, (samples
-got
)*frame_size
, al::byte
{});
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
)
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
}};
347 BackendFactory
&OboeBackendFactory::getFactory()
349 static OboeBackendFactory factory
{};