sdl3.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 2024 by authors.
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Library General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2 of the License, or (at your option) any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Library General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Library General Public
  15. * License along with this library; if not, write to the
  16. * Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. * Or go to http://www.gnu.org/copyleft/lgpl.html
  19. */
  20. #include "config.h"
  21. #include "sdl3.h"
  22. #include <cassert>
  23. #include <cstdlib>
  24. #include <cstring>
  25. #include <functional>
  26. #include <string>
  27. #include <string_view>
  28. #include "almalloc.h"
  29. #include "core/device.h"
  30. #include "core/logging.h"
  31. _Pragma("GCC diagnostic push")
  32. _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
  33. #include "SDL3/SDL_audio.h"
  34. #include "SDL3/SDL_init.h"
  35. #include "SDL3/SDL_stdinc.h"
  36. _Pragma("GCC diagnostic pop")
  37. namespace {
  38. using namespace std::string_view_literals;
  39. _Pragma("GCC diagnostic push")
  40. _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
  41. constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
  42. _Pragma("GCC diagnostic pop")
  43. template<typename T>
  44. struct SdlDeleter {
  45. /* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */
  46. void operator()(gsl::owner<T*> ptr) const { SDL_free(ptr); }
  47. };
  48. template<typename T>
  49. using unique_sdl_ptr = std::unique_ptr<T,SdlDeleter<T>>;
  50. struct DeviceEntry {
  51. std::string mName;
  52. SDL_AudioDeviceID mPhysDeviceID{};
  53. };
  54. std::vector<DeviceEntry> gPlaybackDevices;
  55. void EnumeratePlaybackDevices()
  56. {
  57. auto numdevs = int{};
  58. auto devicelist = unique_sdl_ptr<SDL_AudioDeviceID>{SDL_GetAudioPlaybackDevices(&numdevs)};
  59. if(!devicelist || numdevs < 0)
  60. {
  61. ERR("Failed to get playback devices: {}", SDL_GetError());
  62. return;
  63. }
  64. auto devids = al::span{devicelist.get(), static_cast<uint>(numdevs)};
  65. auto newlist = std::vector<DeviceEntry>{};
  66. newlist.reserve(devids.size());
  67. std::transform(devids.begin(), devids.end(), std::back_inserter(newlist),
  68. [](SDL_AudioDeviceID id)
  69. {
  70. auto *name = SDL_GetAudioDeviceName(id);
  71. if(!name) return DeviceEntry{};
  72. TRACE("Got device \"{}\", ID {}", name, id);
  73. return DeviceEntry{name, id};
  74. });
  75. gPlaybackDevices.swap(newlist);
  76. }
  77. [[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view
  78. { return "Default Device"sv; }
  79. struct Sdl3Backend final : public BackendBase {
  80. explicit Sdl3Backend(DeviceBase *device) noexcept : BackendBase{device} { }
  81. ~Sdl3Backend() final;
  82. void audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept;
  83. void open(std::string_view name) final;
  84. auto reset() -> bool final;
  85. void start() final;
  86. void stop() final;
  87. SDL_AudioDeviceID mDeviceID{0};
  88. SDL_AudioStream *mStream{nullptr};
  89. uint mNumChannels{0};
  90. uint mFrameSize{0};
  91. std::vector<std::byte> mBuffer;
  92. };
  93. Sdl3Backend::~Sdl3Backend()
  94. {
  95. if(mStream)
  96. SDL_DestroyAudioStream(mStream);
  97. mStream = nullptr;
  98. }
  99. void Sdl3Backend::audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount)
  100. noexcept
  101. {
  102. if(additional_amount < 0)
  103. additional_amount = total_amount;
  104. if(additional_amount <= 0)
  105. return;
  106. const auto ulen = static_cast<unsigned int>(additional_amount);
  107. assert((ulen % mFrameSize) == 0);
  108. if(ulen > mBuffer.size())
  109. {
  110. mBuffer.resize(ulen);
  111. std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
  112. ? std::byte{0x80} : std::byte{});
  113. }
  114. mDevice->renderSamples(mBuffer.data(), ulen / mFrameSize, mNumChannels);
  115. SDL_PutAudioStreamData(stream, mBuffer.data(), additional_amount);
  116. }
  117. void Sdl3Backend::open(std::string_view name)
  118. {
  119. const auto defaultDeviceName = getDefaultDeviceName();
  120. if(name.empty() || name == defaultDeviceName)
  121. {
  122. name = defaultDeviceName;
  123. mDeviceID = DefaultPlaybackDeviceID;
  124. }
  125. else
  126. {
  127. if(gPlaybackDevices.empty())
  128. EnumeratePlaybackDevices();
  129. const auto iter = std::find_if(gPlaybackDevices.cbegin(), gPlaybackDevices.cend(),
  130. [name](const DeviceEntry &entry) { return name == entry.mName; });
  131. if(iter == gPlaybackDevices.cend())
  132. throw al::backend_exception{al::backend_error::NoDevice, "No device named {}", name};
  133. mDeviceID = iter->mPhysDeviceID;
  134. }
  135. mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, nullptr, nullptr);
  136. if(!mStream)
  137. throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
  138. auto have = SDL_AudioSpec{};
  139. auto update_size = int{};
  140. if(SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size))
  141. {
  142. auto devtype = mDevice->FmtType;
  143. switch(have.format)
  144. {
  145. case SDL_AUDIO_U8: devtype = DevFmtUByte; break;
  146. case SDL_AUDIO_S8: devtype = DevFmtByte; break;
  147. case SDL_AUDIO_S16: devtype = DevFmtShort; break;
  148. case SDL_AUDIO_S32: devtype = DevFmtInt; break;
  149. case SDL_AUDIO_F32: devtype = DevFmtFloat; break;
  150. default: break;
  151. }
  152. mDevice->FmtType = devtype;
  153. if(have.freq >= int{MinOutputRate} && have.freq <= int{MaxOutputRate})
  154. mDevice->mSampleRate = static_cast<uint>(have.freq);
  155. /* SDL guarantees these layouts for the given channel count. */
  156. if(have.channels == 8)
  157. mDevice->FmtChans = DevFmtX71;
  158. else if(have.channels == 7)
  159. mDevice->FmtChans = DevFmtX61;
  160. else if(have.channels == 6)
  161. mDevice->FmtChans = DevFmtX51;
  162. else if(have.channels == 4)
  163. mDevice->FmtChans = DevFmtQuad;
  164. else if(have.channels >= 2)
  165. mDevice->FmtChans = DevFmtStereo;
  166. else if(have.channels == 1)
  167. mDevice->FmtChans = DevFmtMono;
  168. mDevice->mAmbiOrder = 0;
  169. mNumChannels = static_cast<uint>(have.channels);
  170. mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
  171. if(update_size >= 64)
  172. {
  173. /* We have to assume the total buffer size is just twice the update
  174. * size. SDL doesn't tell us the full end-to-end buffer latency.
  175. */
  176. mDevice->mUpdateSize = static_cast<uint>(update_size);
  177. mDevice->mBufferSize = mDevice->mUpdateSize*2u;
  178. }
  179. else
  180. ERR("Invalid update size from SDL stream: {}", update_size);
  181. }
  182. else
  183. ERR("Failed to get format from SDL stream: {}", SDL_GetError());
  184. mDeviceName = name;
  185. }
  186. auto Sdl3Backend::reset() -> bool
  187. {
  188. static constexpr auto callback = [](void *ptr, SDL_AudioStream *stream, int additional_amount,
  189. int total_amount) noexcept
  190. {
  191. return static_cast<Sdl3Backend*>(ptr)->audioCallback(stream, additional_amount,
  192. total_amount);
  193. };
  194. if(mStream)
  195. SDL_DestroyAudioStream(mStream);
  196. mStream = nullptr;
  197. mBuffer.clear();
  198. mBuffer.shrink_to_fit();
  199. auto want = SDL_AudioSpec{};
  200. if(!SDL_GetAudioDeviceFormat(mDeviceID, &want, nullptr))
  201. ERR("Failed to get device format: {}", SDL_GetError());
  202. if(mDevice->Flags.test(FrequencyRequest) || want.freq < int{MinOutputRate})
  203. want.freq = static_cast<int>(mDevice->mSampleRate);
  204. if(mDevice->Flags.test(SampleTypeRequest)
  205. || !(want.format == SDL_AUDIO_U8 || want.format == SDL_AUDIO_S8
  206. || want.format == SDL_AUDIO_S16 || want.format == SDL_AUDIO_S32
  207. || want.format == SDL_AUDIO_F32))
  208. {
  209. switch(mDevice->FmtType)
  210. {
  211. case DevFmtUByte: want.format = SDL_AUDIO_U8; break;
  212. case DevFmtByte: want.format = SDL_AUDIO_S8; break;
  213. case DevFmtUShort: [[fallthrough]];
  214. case DevFmtShort: want.format = SDL_AUDIO_S16; break;
  215. case DevFmtUInt: [[fallthrough]];
  216. case DevFmtInt: want.format = SDL_AUDIO_S32; break;
  217. case DevFmtFloat: want.format = SDL_AUDIO_F32; break;
  218. }
  219. }
  220. if(mDevice->Flags.test(ChannelsRequest) || want.channels < 1)
  221. want.channels = static_cast<int>(std::min<uint>(mDevice->channelsFromFmt(),
  222. std::numeric_limits<int>::max()));
  223. mStream = SDL_OpenAudioDeviceStream(mDeviceID, &want, callback, this);
  224. if(!mStream)
  225. {
  226. /* If creating the stream failed, try again without a specific format. */
  227. mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, callback, this);
  228. if(!mStream)
  229. throw al::backend_exception{al::backend_error::DeviceError,
  230. "Failed to recreate stream: {}", SDL_GetError()};
  231. }
  232. auto update_size = int{};
  233. auto have = SDL_AudioSpec{};
  234. SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size);
  235. have = SDL_AudioSpec{};
  236. if(!SDL_GetAudioStreamFormat(mStream, &have, nullptr))
  237. throw al::backend_exception{al::backend_error::DeviceError,
  238. "Failed to get stream format: {}", SDL_GetError()};
  239. if(!mDevice->Flags.test(ChannelsRequest)
  240. || (static_cast<uint>(have.channels) != mDevice->channelsFromFmt()
  241. && !(mDevice->FmtChans == DevFmtStereo && have.channels >= 2)))
  242. {
  243. /* SDL guarantees these layouts for the given channel count. */
  244. if(have.channels == 8)
  245. mDevice->FmtChans = DevFmtX71;
  246. else if(have.channels == 7)
  247. mDevice->FmtChans = DevFmtX61;
  248. else if(have.channels == 6)
  249. mDevice->FmtChans = DevFmtX51;
  250. else if(have.channels == 4)
  251. mDevice->FmtChans = DevFmtQuad;
  252. else if(have.channels >= 2)
  253. mDevice->FmtChans = DevFmtStereo;
  254. else if(have.channels == 1)
  255. mDevice->FmtChans = DevFmtMono;
  256. else
  257. throw al::backend_exception{al::backend_error::DeviceError,
  258. "Unhandled SDL channel count: {}", have.channels};
  259. mDevice->mAmbiOrder = 0;
  260. }
  261. mNumChannels = static_cast<uint>(have.channels);
  262. switch(have.format)
  263. {
  264. case SDL_AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
  265. case SDL_AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
  266. case SDL_AUDIO_S16: mDevice->FmtType = DevFmtShort; break;
  267. case SDL_AUDIO_S32: mDevice->FmtType = DevFmtInt; break;
  268. case SDL_AUDIO_F32: mDevice->FmtType = DevFmtFloat; break;
  269. default:
  270. throw al::backend_exception{al::backend_error::DeviceError,
  271. "Unhandled SDL format: {:#04x}", al::to_underlying(have.format)};
  272. }
  273. mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
  274. if(have.freq < int{MinOutputRate})
  275. throw al::backend_exception{al::backend_error::DeviceError,
  276. "Unhandled SDL sample rate: {}", have.freq};
  277. mDevice->mSampleRate = static_cast<uint>(have.freq);
  278. if(update_size >= 64)
  279. {
  280. mDevice->mUpdateSize = static_cast<uint>(update_size);
  281. mDevice->mBufferSize = mDevice->mUpdateSize*2u;
  282. mBuffer.resize(size_t{mDevice->mUpdateSize} * mFrameSize);
  283. std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
  284. ? std::byte{0x80} : std::byte{});
  285. }
  286. else
  287. ERR("Invalid update size from SDL stream: {}", update_size);
  288. setDefaultWFXChannelOrder();
  289. return true;
  290. }
  291. void Sdl3Backend::start()
  292. { SDL_ResumeAudioStreamDevice(mStream); }
  293. void Sdl3Backend::stop()
  294. { SDL_PauseAudioStreamDevice(mStream); }
  295. } // namespace
  296. auto SDL3BackendFactory::getFactory() -> BackendFactory&
  297. {
  298. static SDL3BackendFactory factory{};
  299. return factory;
  300. }
  301. auto SDL3BackendFactory::init() -> bool
  302. {
  303. if(!SDL_InitSubSystem(SDL_INIT_AUDIO))
  304. return false;
  305. TRACE("Current SDL3 audio driver: \"{}\"", SDL_GetCurrentAudioDriver());
  306. return true;
  307. }
  308. auto SDL3BackendFactory::querySupport(BackendType type) -> bool
  309. { return type == BackendType::Playback; }
  310. auto SDL3BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
  311. {
  312. auto outnames = std::vector<std::string>{};
  313. if(type != BackendType::Playback)
  314. return outnames;
  315. EnumeratePlaybackDevices();
  316. outnames.reserve(gPlaybackDevices.size()+1);
  317. outnames.emplace_back(getDefaultDeviceName());
  318. std::transform(gPlaybackDevices.begin(), gPlaybackDevices.end(), std::back_inserter(outnames),
  319. std::mem_fn(&DeviceEntry::mName));
  320. return outnames;
  321. }
  322. auto SDL3BackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
  323. {
  324. if(type == BackendType::Playback)
  325. return BackendPtr{new Sdl3Backend{device}};
  326. return nullptr;
  327. }