🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

342 lines
11 KiB

  1. #include "config.h"
  2. #include "oboe.h"
  3. #include <cassert>
  4. #include <cstring>
  5. #include <stdint.h>
  6. #include "alnumeric.h"
  7. #include "core/device.h"
  8. #include "core/logging.h"
  9. #include "ringbuffer.h"
  10. #include "oboe/Oboe.h"
  11. namespace {
  12. constexpr char device_name[] = "Oboe Default";
  13. struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
  14. OboePlayback(DeviceBase *device) : BackendBase{device} { }
  15. oboe::ManagedStream mStream;
  16. oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
  17. int32_t numFrames) override;
  18. void open(const char *name) override;
  19. bool reset() override;
  20. void start() override;
  21. void stop() override;
  22. };
  23. oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
  24. int32_t numFrames)
  25. {
  26. assert(numFrames > 0);
  27. const int32_t numChannels{oboeStream->getChannelCount()};
  28. mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
  29. static_cast<uint32_t>(numChannels));
  30. return oboe::DataCallbackResult::Continue;
  31. }
  32. void OboePlayback::open(const char *name)
  33. {
  34. if(!name)
  35. name = device_name;
  36. else if(std::strcmp(name, device_name) != 0)
  37. throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
  38. name};
  39. /* Open a basic output stream, just to ensure it can work. */
  40. oboe::ManagedStream stream;
  41. oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
  42. ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
  43. ->openManagedStream(stream)};
  44. if(result != oboe::Result::OK)
  45. throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
  46. oboe::convertToText(result)};
  47. mDevice->DeviceName = name;
  48. }
  49. bool OboePlayback::reset()
  50. {
  51. oboe::AudioStreamBuilder builder;
  52. builder.setDirection(oboe::Direction::Output);
  53. builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
  54. /* Don't let Oboe convert. We should be able to handle anything it gives
  55. * back.
  56. */
  57. builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
  58. builder.setChannelConversionAllowed(false);
  59. builder.setFormatConversionAllowed(false);
  60. builder.setCallback(this);
  61. if(mDevice->Flags.test(FrequencyRequest))
  62. builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
  63. if(mDevice->Flags.test(ChannelsRequest))
  64. {
  65. /* Only use mono or stereo at user request. There's no telling what
  66. * other counts may be inferred as.
  67. */
  68. builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
  69. : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
  70. : oboe::ChannelCount::Unspecified);
  71. }
  72. if(mDevice->Flags.test(SampleTypeRequest))
  73. {
  74. oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
  75. switch(mDevice->FmtType)
  76. {
  77. case DevFmtByte:
  78. case DevFmtUByte:
  79. case DevFmtShort:
  80. case DevFmtUShort:
  81. format = oboe::AudioFormat::I16;
  82. break;
  83. case DevFmtInt:
  84. case DevFmtUInt:
  85. case DevFmtFloat:
  86. format = oboe::AudioFormat::Float;
  87. break;
  88. }
  89. builder.setFormat(format);
  90. }
  91. oboe::Result result{builder.openManagedStream(mStream)};
  92. /* If the format failed, try asking for the defaults. */
  93. while(result == oboe::Result::ErrorInvalidFormat)
  94. {
  95. if(builder.getFormat() != oboe::AudioFormat::Unspecified)
  96. builder.setFormat(oboe::AudioFormat::Unspecified);
  97. else if(builder.getSampleRate() != oboe::kUnspecified)
  98. builder.setSampleRate(oboe::kUnspecified);
  99. else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
  100. builder.setChannelCount(oboe::ChannelCount::Unspecified);
  101. else
  102. break;
  103. result = builder.openManagedStream(mStream);
  104. }
  105. if(result != oboe::Result::OK)
  106. throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
  107. oboe::convertToText(result)};
  108. mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
  109. mStream->getBufferCapacityInFrames()));
  110. TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
  111. if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
  112. {
  113. if(mStream->getChannelCount() >= 2)
  114. mDevice->FmtChans = DevFmtStereo;
  115. else if(mStream->getChannelCount() == 1)
  116. mDevice->FmtChans = DevFmtMono;
  117. else
  118. throw al::backend_exception{al::backend_error::DeviceError,
  119. "Got unhandled channel count: %d", mStream->getChannelCount()};
  120. }
  121. setDefaultWFXChannelOrder();
  122. switch(mStream->getFormat())
  123. {
  124. case oboe::AudioFormat::I16:
  125. mDevice->FmtType = DevFmtShort;
  126. break;
  127. case oboe::AudioFormat::Float:
  128. mDevice->FmtType = DevFmtFloat;
  129. break;
  130. case oboe::AudioFormat::Unspecified:
  131. case oboe::AudioFormat::Invalid:
  132. throw al::backend_exception{al::backend_error::DeviceError,
  133. "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
  134. }
  135. mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
  136. /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
  137. * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
  138. * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
  139. * update size.
  140. */
  141. mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
  142. static_cast<uint32_t>(mStream->getFramesPerBurst()));
  143. mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
  144. static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
  145. return true;
  146. }
  147. void OboePlayback::start()
  148. {
  149. const oboe::Result result{mStream->start()};
  150. if(result != oboe::Result::OK)
  151. throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
  152. oboe::convertToText(result)};
  153. }
  154. void OboePlayback::stop()
  155. {
  156. oboe::Result result{mStream->stop()};
  157. if(result != oboe::Result::OK)
  158. throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
  159. oboe::convertToText(result)};
  160. }
  161. struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
  162. OboeCapture(DeviceBase *device) : BackendBase{device} { }
  163. oboe::ManagedStream mStream;
  164. RingBufferPtr mRing{nullptr};
  165. oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
  166. int32_t numFrames) override;
  167. void open(const char *name) override;
  168. void start() override;
  169. void stop() override;
  170. void captureSamples(al::byte *buffer, uint samples) override;
  171. uint availableSamples() override;
  172. };
  173. oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
  174. int32_t numFrames)
  175. {
  176. mRing->write(audioData, static_cast<uint32_t>(numFrames));
  177. return oboe::DataCallbackResult::Continue;
  178. }
  179. void OboeCapture::open(const char *name)
  180. {
  181. if(!name)
  182. name = device_name;
  183. else if(std::strcmp(name, device_name) != 0)
  184. throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
  185. name};
  186. oboe::AudioStreamBuilder builder;
  187. builder.setDirection(oboe::Direction::Input)
  188. ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
  189. ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
  190. ->setChannelConversionAllowed(true)
  191. ->setFormatConversionAllowed(true)
  192. ->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
  193. ->setCallback(this);
  194. /* Only use mono or stereo at user request. There's no telling what
  195. * other counts may be inferred as.
  196. */
  197. switch(mDevice->FmtChans)
  198. {
  199. case DevFmtMono:
  200. builder.setChannelCount(oboe::ChannelCount::Mono);
  201. break;
  202. case DevFmtStereo:
  203. builder.setChannelCount(oboe::ChannelCount::Stereo);
  204. break;
  205. case DevFmtQuad:
  206. case DevFmtX51:
  207. case DevFmtX61:
  208. case DevFmtX71:
  209. case DevFmtX3D71:
  210. case DevFmtAmbi3D:
  211. throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
  212. DevFmtChannelsString(mDevice->FmtChans)};
  213. }
  214. /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
  215. * convert.
  216. */
  217. switch(mDevice->FmtType)
  218. {
  219. case DevFmtShort:
  220. builder.setFormat(oboe::AudioFormat::I16);
  221. break;
  222. case DevFmtFloat:
  223. builder.setFormat(oboe::AudioFormat::Float);
  224. break;
  225. case DevFmtByte:
  226. case DevFmtUByte:
  227. case DevFmtUShort:
  228. case DevFmtInt:
  229. case DevFmtUInt:
  230. throw al::backend_exception{al::backend_error::DeviceError,
  231. "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
  232. }
  233. oboe::Result result{builder.openManagedStream(mStream)};
  234. if(result != oboe::Result::OK)
  235. throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
  236. oboe::convertToText(result)};
  237. TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
  238. /* Ensure a minimum ringbuffer size of 100ms. */
  239. mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
  240. static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
  241. mDevice->DeviceName = name;
  242. }
  243. void OboeCapture::start()
  244. {
  245. const oboe::Result result{mStream->start()};
  246. if(result != oboe::Result::OK)
  247. throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
  248. oboe::convertToText(result)};
  249. }
  250. void OboeCapture::stop()
  251. {
  252. const oboe::Result result{mStream->stop()};
  253. if(result != oboe::Result::OK)
  254. throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
  255. oboe::convertToText(result)};
  256. }
  257. uint OboeCapture::availableSamples()
  258. { return static_cast<uint>(mRing->readSpace()); }
  259. void OboeCapture::captureSamples(al::byte *buffer, uint samples)
  260. { mRing->read(buffer, samples); }
  261. } // namespace
  262. bool OboeBackendFactory::init() { return true; }
  263. bool OboeBackendFactory::querySupport(BackendType type)
  264. { return type == BackendType::Playback || type == BackendType::Capture; }
  265. std::string OboeBackendFactory::probe(BackendType type)
  266. {
  267. switch(type)
  268. {
  269. case BackendType::Playback:
  270. case BackendType::Capture:
  271. /* Includes null char. */
  272. return std::string{device_name, sizeof(device_name)};
  273. }
  274. return std::string{};
  275. }
  276. BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
  277. {
  278. if(type == BackendType::Playback)
  279. return BackendPtr{new OboePlayback{device}};
  280. if(type == BackendType::Capture)
  281. return BackendPtr{new OboeCapture{device}};
  282. return BackendPtr{};
  283. }
  284. BackendFactory &OboeBackendFactory::getFactory()
  285. {
  286. static OboeBackendFactory factory{};
  287. return factory;
  288. }