1 2 #include "config.h" 3 4 #include "oboe.h" 5 6 #include <cassert> 7 #include <cstring> 8 9 #include "alu.h" 10 #include "core/logging.h" 11 12 #include "oboe/Oboe.h" 13 14 15 namespace { 16 17 constexpr char device_name[] = "Oboe Default"; 18 19 20 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { 21 OboePlayback(ALCdevice *device) : BackendBase{device} { } 22 23 oboe::ManagedStream mStream; 24 25 oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, 26 int32_t numFrames) override; 27 28 void open(const char *name) override; 29 bool reset() override; 30 void start() override; 31 void stop() override; 32 }; 33 34 35 oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, 36 int32_t numFrames) 37 { 38 assert(numFrames > 0); 39 const int32_t numChannels{oboeStream->getChannelCount()}; 40 41 if UNLIKELY(numChannels > 2 && mDevice->FmtChans == DevFmtStereo) 42 { 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. 45 */ 46 if(mStream->getFormat() == oboe::AudioFormat::Float) 47 memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(float)); 48 else 49 memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(int16_t)); 50 } 51 52 mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames), 53 static_cast<uint32_t>(numChannels)); 54 return oboe::DataCallbackResult::Continue; 55 } 56 57 58 void OboePlayback::open(const char *name) 59 { 60 if(!name) 61 name = device_name; 62 else if(std::strcmp(name, device_name) != 0) 63 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", 64 name}; 65 66 /* Open a basic output stream, just to ensure it can work. */ 67 oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) 68 ->setPerformanceMode(oboe::PerformanceMode::LowLatency) 69 ->openManagedStream(mStream)}; 70 if(result != oboe::Result::OK) 71 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", 72 oboe::convertToText(result)}; 73 74 mDevice->DeviceName = name; 75 } 76 77 bool OboePlayback::reset() 78 { 79 oboe::AudioStreamBuilder builder; 80 builder.setDirection(oboe::Direction::Output); 81 builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); 82 /* Don't let Oboe convert. We should be able to handle anything it gives 83 * back. 84 */ 85 builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None); 86 builder.setChannelConversionAllowed(false); 87 builder.setFormatConversionAllowed(false); 88 builder.setCallback(this); 89 90 if(mDevice->Flags.test(FrequencyRequest)) 91 builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency)); 92 if(mDevice->Flags.test(ChannelsRequest)) 93 { 94 /* Only use mono or stereo at user request. There's no telling what 95 * other counts may be inferred as. 96 */ 97 builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono 98 : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo 99 : oboe::ChannelCount::Unspecified); 100 } 101 if(mDevice->Flags.test(SampleTypeRequest)) 102 { 103 oboe::AudioFormat format{oboe::AudioFormat::Unspecified}; 104 switch(mDevice->FmtType) 105 { 106 case DevFmtByte: 107 case DevFmtUByte: 108 case DevFmtShort: 109 case DevFmtUShort: 110 format = oboe::AudioFormat::I16; 111 break; 112 case DevFmtInt: 113 case DevFmtUInt: 114 case DevFmtFloat: 115 format = oboe::AudioFormat::Float; 116 break; 117 } 118 builder.setFormat(format); 119 } 120 121 oboe::Result result{builder.openManagedStream(mStream)}; 122 /* If the format failed, try asking for the defaults. */ 123 while(result == oboe::Result::ErrorInvalidFormat) 124 { 125 if(builder.getFormat() != oboe::AudioFormat::Unspecified) 126 builder.setFormat(oboe::AudioFormat::Unspecified); 127 else if(builder.getSampleRate() != oboe::kUnspecified) 128 builder.setSampleRate(oboe::kUnspecified); 129 else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified) 130 builder.setChannelCount(oboe::ChannelCount::Unspecified); 131 else 132 break; 133 result = builder.openManagedStream(mStream); 134 } 135 if(result != oboe::Result::OK) 136 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", 137 oboe::convertToText(result)}; 138 mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize), 139 mStream->getBufferCapacityInFrames())); 140 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); 141 142 switch(mStream->getChannelCount()) 143 { 144 case oboe::ChannelCount::Mono: 145 mDevice->FmtChans = DevFmtMono; 146 break; 147 case oboe::ChannelCount::Stereo: 148 mDevice->FmtChans = DevFmtStereo; 149 break; 150 /* Other potential configurations. Could be wrong, but better than failing. 151 * Assume WFX channel order. 152 */ 153 case 4: 154 mDevice->FmtChans = DevFmtQuad; 155 break; 156 case 6: 157 mDevice->FmtChans = DevFmtX51Rear; 158 break; 159 case 7: 160 mDevice->FmtChans = DevFmtX61; 161 break; 162 case 8: 163 mDevice->FmtChans = DevFmtX71; 164 break; 165 default: 166 if(mStream->getChannelCount() < 1) 167 throw al::backend_exception{al::backend_error::DeviceError, 168 "Got unhandled channel count: %d", mStream->getChannelCount()}; 169 /* Assume first two channels are front left/right. We can do a stereo 170 * mix and keep the other channels silent. 171 */ 172 mDevice->FmtChans = DevFmtStereo; 173 break; 174 } 175 setDefaultWFXChannelOrder(); 176 177 switch(mStream->getFormat()) 178 { 179 case oboe::AudioFormat::I16: 180 mDevice->FmtType = DevFmtShort; 181 break; 182 case oboe::AudioFormat::Float: 183 mDevice->FmtType = DevFmtFloat; 184 break; 185 case oboe::AudioFormat::Unspecified: 186 case oboe::AudioFormat::Invalid: 187 throw al::backend_exception{al::backend_error::DeviceError, 188 "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())}; 189 } 190 mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate()); 191 192 /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 193 * indicating variable updates, but OpenAL should have a reasonable minimum update size set. 194 * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum 195 * update size. 196 */ 197 mDevice->UpdateSize = maxu(mDevice->Frequency / 100, 198 static_cast<uint32_t>(mStream->getFramesPerBurst())); 199 mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, 200 static_cast<uint32_t>(mStream->getBufferSizeInFrames())); 201 202 return true; 203 } 204 205 void OboePlayback::start() 206 { 207 const oboe::Result result{mStream->start()}; 208 if(result != oboe::Result::OK) 209 throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", 210 oboe::convertToText(result)}; 211 } 212 213 void OboePlayback::stop() 214 { 215 oboe::Result result{mStream->stop()}; 216 if(result != oboe::Result::OK) 217 throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", 218 oboe::convertToText(result)}; 219 } 220 221 222 struct OboeCapture final : public BackendBase { 223 OboeCapture(ALCdevice *device) : BackendBase{device} { } 224 225 oboe::ManagedStream mStream; 226 227 void open(const char *name) override; 228 void start() override; 229 void stop() override; 230 void captureSamples(al::byte *buffer, uint samples) override; 231 uint availableSamples() override; 232 }; 233 234 void OboeCapture::open(const char *name) 235 { 236 if(!name) 237 name = device_name; 238 else if(std::strcmp(name, device_name) != 0) 239 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", 240 name}; 241 242 oboe::AudioStreamBuilder builder; 243 builder.setDirection(oboe::Direction::Input) 244 ->setPerformanceMode(oboe::PerformanceMode::LowLatency) 245 ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) 246 ->setChannelConversionAllowed(true) 247 ->setFormatConversionAllowed(true) 248 ->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize)) 249 ->setSampleRate(static_cast<int32_t>(mDevice->Frequency)); 250 /* Only use mono or stereo at user request. There's no telling what 251 * other counts may be inferred as. 252 */ 253 switch(mDevice->FmtChans) 254 { 255 case DevFmtMono: 256 builder.setChannelCount(oboe::ChannelCount::Mono); 257 break; 258 case DevFmtStereo: 259 builder.setChannelCount(oboe::ChannelCount::Stereo); 260 break; 261 case DevFmtQuad: 262 case DevFmtX51: 263 case DevFmtX51Rear: 264 case DevFmtX61: 265 case DevFmtX71: 266 case DevFmtAmbi3D: 267 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", 268 DevFmtChannelsString(mDevice->FmtChans)}; 269 } 270 271 /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to 272 * use a temp buffer and convert. 273 */ 274 switch(mDevice->FmtType) 275 { 276 case DevFmtShort: 277 builder.setFormat(oboe::AudioFormat::I16); 278 break; 279 case DevFmtFloat: 280 builder.setFormat(oboe::AudioFormat::Float); 281 break; 282 case DevFmtByte: 283 case DevFmtUByte: 284 case DevFmtUShort: 285 case DevFmtInt: 286 case DevFmtUInt: 287 throw al::backend_exception{al::backend_error::DeviceError, 288 "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; 289 } 290 291 oboe::Result result{builder.openManagedStream(mStream)}; 292 if(result != oboe::Result::OK) 293 throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", 294 oboe::convertToText(result)}; 295 if(static_cast<int32_t>(mDevice->BufferSize) > mStream->getBufferCapacityInFrames()) 296 throw al::backend_exception{al::backend_error::DeviceError, 297 "Buffer size too large (%u > %d)", mDevice->BufferSize, 298 mStream->getBufferCapacityInFrames()}; 299 auto buffer_result = mStream->setBufferSizeInFrames(static_cast<int32_t>(mDevice->BufferSize)); 300 if(!buffer_result) 301 throw al::backend_exception{al::backend_error::DeviceError, 302 "Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())}; 303 else if(buffer_result.value() < static_cast<int32_t>(mDevice->BufferSize)) 304 throw al::backend_exception{al::backend_error::DeviceError, 305 "Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize, 306 buffer_result.value()}; 307 mDevice->BufferSize = static_cast<uint>(buffer_result.value()); 308 309 TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); 310 311 mDevice->DeviceName = name; 312 } 313 314 void OboeCapture::start() 315 { 316 const oboe::Result result{mStream->start()}; 317 if(result != oboe::Result::OK) 318 throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", 319 oboe::convertToText(result)}; 320 } 321 322 void OboeCapture::stop() 323 { 324 const oboe::Result result{mStream->stop()}; 325 if(result != oboe::Result::OK) 326 throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", 327 oboe::convertToText(result)}; 328 } 329 330 uint OboeCapture::availableSamples() 331 { 332 auto result = mStream->getAvailableFrames(); 333 /* FIXME: This shouldn't report less samples than have been previously 334 * reported and not captured. 335 */ 336 if(!result) return 0; 337 return static_cast<uint>(result.value()); 338 } 339 340 void OboeCapture::captureSamples(al::byte *buffer, uint samples) 341 { 342 auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0); 343 uint got{bool{result} ? static_cast<uint>(result.value()) : 0u}; 344 if(got < samples) 345 { 346 auto frame_size = static_cast<uint>(mStream->getBytesPerFrame()); 347 std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{}); 348 } 349 } 350 351 } // namespace 352 353 bool OboeBackendFactory::init() { return true; } 354 355 bool OboeBackendFactory::querySupport(BackendType type) 356 { return type == BackendType::Playback || type == BackendType::Capture; } 357 358 std::string OboeBackendFactory::probe(BackendType type) 359 { 360 switch(type) 361 { 362 case BackendType::Playback: 363 case BackendType::Capture: 364 /* Includes null char. */ 365 return std::string{device_name, sizeof(device_name)}; 366 } 367 return std::string{}; 368 } 369 370 BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type) 371 { 372 if(type == BackendType::Playback) 373 return BackendPtr{new OboePlayback{device}}; 374 if(type == BackendType::Capture) 375 return BackendPtr{new OboeCapture{device}}; 376 return BackendPtr{}; 377 } 378 379 BackendFactory &OboeBackendFactory::getFactory() 380 { 381 static OboeBackendFactory factory{}; 382 return factory; 383 } 384