🛠️🐜 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.

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