oboe.cpp 12 KB

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