10 #include "alnumeric.h"
12 #include "core/device.h"
13 #include "core/logging.h"
14 #include "ringbuffer.h"
16 #include "oboe/Oboe.h"
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
;
43 oboe::DataCallbackResult
OboePlayback::onAudioReady(oboe::AudioStream
*oboeStream
, void *audioData
,
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
)
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
)};
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
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
)
118 format
= oboe::AudioFormat::I16
;
122 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
123 format
= oboe::AudioFormat::I32
;
127 format
= oboe::AudioFormat::Float
;
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
);
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
;
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
;
171 case oboe::AudioFormat::Float
:
172 mDevice
->FmtType
= DevFmtFloat
;
174 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
175 case oboe::AudioFormat::I32
:
176 mDevice
->FmtType
= DevFmtInt
;
178 case oboe::AudioFormat::I24
:
180 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8)
181 case oboe::AudioFormat::IEC61937
:
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
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()));
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
,
239 std::ignore
= mRing
->write(audioData
, static_cast<uint32_t>(numFrames
));
240 return oboe::DataCallbackResult::Continue
;
244 void OboeCapture::open(std::string_view name
)
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
))
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
)
266 builder
.setChannelCount(oboe::ChannelCount::Mono
);
269 builder
.setChannelCount(oboe::ChannelCount::Stereo
);
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
286 switch(mDevice
->FmtType
)
289 builder
.setFormat(oboe::AudioFormat::I16
);
292 builder
.setFormat(oboe::AudioFormat::Float
);
295 #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
296 builder
.setFormat(oboe::AudioFormat::I32
);
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);
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
); }
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
>
353 case BackendType::Playback
:
354 case BackendType::Capture
:
355 return std::vector
{std::string
{GetDeviceName()}};
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
}};
369 BackendFactory
&OboeBackendFactory::getFactory()
371 static OboeBackendFactory factory
{};