oboe.cpp 12 KB

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