12 #include "oboe/Oboe.h"
17 constexpr char device_name
[] = "Oboe Default";
20 struct OboePlayback final
: public BackendBase
, public oboe::AudioStreamCallback
{
21 OboePlayback(ALCdevice
*device
) : BackendBase
{device
} { }
23 oboe::ManagedStream mStream
;
25 oboe::DataCallbackResult
onAudioReady(oboe::AudioStream
*oboeStream
, void *audioData
,
26 int32_t numFrames
) override
;
28 void open(const ALCchar
*name
) override
;
29 bool reset() override
;
30 void start() override
;
35 oboe::DataCallbackResult
OboePlayback::onAudioReady(oboe::AudioStream
*oboeStream
, void *audioData
,
38 assert(numFrames
> 0);
39 const int32_t numChannels
{oboeStream
->getChannelCount()};
41 if UNLIKELY(numChannels
> 2 && mDevice
->FmtChans
== DevFmtStereo
)
43 /* If the device is only mixing stereo but there's more than two
44 * output channels, there are unused channels that need to be silenced.
46 if(mStream
->getFormat() == oboe::AudioFormat::Float
)
47 memset(audioData
, 0, static_cast<uint32_t>(numFrames
*numChannels
)*sizeof(float));
49 memset(audioData
, 0, static_cast<uint32_t>(numFrames
*numChannels
)*sizeof(int16_t));
52 mDevice
->renderSamples(audioData
, static_cast<uint32_t>(numFrames
),
53 static_cast<uint32_t>(numChannels
));
54 return oboe::DataCallbackResult::Continue
;
58 void OboePlayback::open(const ALCchar
*name
)
62 else if(std::strcmp(name
, device_name
) != 0)
63 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device name \"%s\" not found", name
};
65 /* Open a basic output stream, just to ensure it can work. */
66 oboe::Result result
{oboe::AudioStreamBuilder
{}.setDirection(oboe::Direction::Output
)
67 ->setPerformanceMode(oboe::PerformanceMode::LowLatency
)
68 ->openManagedStream(mStream
)};
69 if(result
!= oboe::Result::OK
)
70 throw al::backend_exception
{ALC_INVALID_VALUE
, "Failed to create stream: %s",
71 oboe::convertToText(result
)};
73 mDevice
->DeviceName
= name
;
76 bool OboePlayback::reset()
78 oboe::AudioStreamBuilder builder
;
79 builder
.setDirection(oboe::Direction::Output
);
80 builder
.setPerformanceMode(oboe::PerformanceMode::LowLatency
);
81 /* Don't let Oboe convert. We should be able to handle anything it gives
84 builder
.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None
);
85 builder
.setChannelConversionAllowed(false);
86 builder
.setFormatConversionAllowed(false);
87 builder
.setCallback(this);
89 if(mDevice
->Flags
.get
<FrequencyRequest
>())
90 builder
.setSampleRate(static_cast<int32_t>(mDevice
->Frequency
));
91 if(mDevice
->Flags
.get
<ChannelsRequest
>())
93 /* Only use mono or stereo at user request. There's no telling what
94 * other counts may be inferred as.
96 builder
.setChannelCount((mDevice
->FmtChans
==DevFmtMono
) ? oboe::ChannelCount::Mono
97 : (mDevice
->FmtChans
==DevFmtStereo
) ? oboe::ChannelCount::Stereo
98 : oboe::ChannelCount::Unspecified
);
100 if(mDevice
->Flags
.get
<SampleTypeRequest
>())
102 oboe::AudioFormat format
{oboe::AudioFormat::Unspecified
};
103 switch(mDevice
->FmtType
)
109 format
= oboe::AudioFormat::I16
;
114 format
= oboe::AudioFormat::Float
;
117 builder
.setFormat(format
);
120 oboe::Result result
{builder
.openManagedStream(mStream
)};
121 /* If the format failed, try asking for the defaults. */
122 while(result
== oboe::Result::ErrorInvalidFormat
)
124 if(builder
.getFormat() != oboe::AudioFormat::Unspecified
)
125 builder
.setFormat(oboe::AudioFormat::Unspecified
);
126 else if(builder
.getSampleRate() != oboe::kUnspecified
)
127 builder
.setSampleRate(oboe::kUnspecified
);
128 else if(builder
.getChannelCount() != oboe::ChannelCount::Unspecified
)
129 builder
.setChannelCount(oboe::ChannelCount::Unspecified
);
132 result
= builder
.openManagedStream(mStream
);
134 if(result
!= oboe::Result::OK
)
135 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to create stream: %s",
136 oboe::convertToText(result
)};
137 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream
.get()));
139 switch(mStream
->getChannelCount())
141 case oboe::ChannelCount::Mono
:
142 mDevice
->FmtChans
= DevFmtMono
;
144 case oboe::ChannelCount::Stereo
:
145 mDevice
->FmtChans
= DevFmtStereo
;
147 /* Other potential configurations. Could be wrong, but better than failing.
148 * Assume WFX channel order.
151 mDevice
->FmtChans
= DevFmtQuad
;
154 mDevice
->FmtChans
= DevFmtX51Rear
;
157 mDevice
->FmtChans
= DevFmtX61
;
160 mDevice
->FmtChans
= DevFmtX71
;
163 if(mStream
->getChannelCount() < 1)
164 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Got unhandled channel count: %d",
165 mStream
->getChannelCount()};
166 /* Assume first two channels are front left/right. We can do a stereo
167 * mix and keep the other channels silent.
169 mDevice
->FmtChans
= DevFmtStereo
;
172 setDefaultWFXChannelOrder();
174 switch(mStream
->getFormat())
176 case oboe::AudioFormat::I16
:
177 mDevice
->FmtType
= DevFmtShort
;
179 case oboe::AudioFormat::Float
:
180 mDevice
->FmtType
= DevFmtFloat
;
182 case oboe::AudioFormat::Unspecified
:
183 case oboe::AudioFormat::Invalid
:
184 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Got unhandled sample type: %s",
185 oboe::convertToText(mStream
->getFormat())};
187 mDevice
->Frequency
= static_cast<uint32_t>(mStream
->getSampleRate());
189 /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
190 * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
191 * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
194 mDevice
->UpdateSize
= maxu(mDevice
->Frequency
/ 100,
195 static_cast<uint32_t>(mStream
->getFramesPerBurst()));
196 mDevice
->BufferSize
= maxu(mDevice
->UpdateSize
* 2,
197 static_cast<uint32_t>(mStream
->getBufferSizeInFrames()));
202 void OboePlayback::start()
204 const oboe::Result result
{mStream
->start()};
205 if(result
!= oboe::Result::OK
)
206 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start stream: %s",
207 oboe::convertToText(result
)};
210 void OboePlayback::stop()
212 oboe::Result result
{mStream
->stop()};
213 if(result
!= oboe::Result::OK
)
214 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to stop stream: %s",
215 oboe::convertToText(result
)};
220 bool OboeBackendFactory::init() { return true; }
222 bool OboeBackendFactory::querySupport(BackendType type
)
223 { return type
== BackendType::Playback
; }
225 std::string
OboeBackendFactory::probe(BackendType type
)
229 case BackendType::Playback
:
230 /* Includes null char. */
231 return std::string
{device_name
, sizeof(device_name
)};
232 case BackendType::Capture
:
235 return std::string
{};
238 BackendPtr
OboeBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
240 if(type
== BackendType::Playback
)
241 return BackendPtr
{new OboePlayback
{device
}};
245 BackendFactory
&OboeBackendFactory::getFactory()
247 static OboeBackendFactory factory
{};