| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- /**
- * OpenAL cross platform audio library
- * Copyright (C) 2024 by authors.
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
- #include "config.h"
- #include "sdl3.h"
- #include <cassert>
- #include <cstdlib>
- #include <cstring>
- #include <functional>
- #include <string>
- #include <string_view>
- #include "almalloc.h"
- #include "core/device.h"
- #include "core/logging.h"
- _Pragma("GCC diagnostic push")
- _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
- #include "SDL3/SDL_audio.h"
- #include "SDL3/SDL_init.h"
- #include "SDL3/SDL_stdinc.h"
- _Pragma("GCC diagnostic pop")
- namespace {
- using namespace std::string_view_literals;
- _Pragma("GCC diagnostic push")
- _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
- constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
- _Pragma("GCC diagnostic pop")
- template<typename T>
- struct SdlDeleter {
- /* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */
- void operator()(gsl::owner<T*> ptr) const { SDL_free(ptr); }
- };
- template<typename T>
- using unique_sdl_ptr = std::unique_ptr<T,SdlDeleter<T>>;
- struct DeviceEntry {
- std::string mName;
- SDL_AudioDeviceID mPhysDeviceID{};
- };
- std::vector<DeviceEntry> gPlaybackDevices;
- void EnumeratePlaybackDevices()
- {
- auto numdevs = int{};
- auto devicelist = unique_sdl_ptr<SDL_AudioDeviceID>{SDL_GetAudioPlaybackDevices(&numdevs)};
- if(!devicelist || numdevs < 0)
- {
- ERR("Failed to get playback devices: {}", SDL_GetError());
- return;
- }
- auto devids = al::span{devicelist.get(), static_cast<uint>(numdevs)};
- auto newlist = std::vector<DeviceEntry>{};
- newlist.reserve(devids.size());
- std::transform(devids.begin(), devids.end(), std::back_inserter(newlist),
- [](SDL_AudioDeviceID id)
- {
- auto *name = SDL_GetAudioDeviceName(id);
- if(!name) return DeviceEntry{};
- TRACE("Got device \"{}\", ID {}", name, id);
- return DeviceEntry{name, id};
- });
- gPlaybackDevices.swap(newlist);
- }
- [[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view
- { return "Default Device"sv; }
- struct Sdl3Backend final : public BackendBase {
- explicit Sdl3Backend(DeviceBase *device) noexcept : BackendBase{device} { }
- ~Sdl3Backend() final;
- void audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept;
- void open(std::string_view name) final;
- auto reset() -> bool final;
- void start() final;
- void stop() final;
- SDL_AudioDeviceID mDeviceID{0};
- SDL_AudioStream *mStream{nullptr};
- uint mNumChannels{0};
- uint mFrameSize{0};
- std::vector<std::byte> mBuffer;
- };
- Sdl3Backend::~Sdl3Backend()
- {
- if(mStream)
- SDL_DestroyAudioStream(mStream);
- mStream = nullptr;
- }
- void Sdl3Backend::audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount)
- noexcept
- {
- if(additional_amount < 0)
- additional_amount = total_amount;
- if(additional_amount <= 0)
- return;
- const auto ulen = static_cast<unsigned int>(additional_amount);
- assert((ulen % mFrameSize) == 0);
- if(ulen > mBuffer.size())
- {
- mBuffer.resize(ulen);
- std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
- ? std::byte{0x80} : std::byte{});
- }
- mDevice->renderSamples(mBuffer.data(), ulen / mFrameSize, mNumChannels);
- SDL_PutAudioStreamData(stream, mBuffer.data(), additional_amount);
- }
- void Sdl3Backend::open(std::string_view name)
- {
- const auto defaultDeviceName = getDefaultDeviceName();
- if(name.empty() || name == defaultDeviceName)
- {
- name = defaultDeviceName;
- mDeviceID = DefaultPlaybackDeviceID;
- }
- else
- {
- if(gPlaybackDevices.empty())
- EnumeratePlaybackDevices();
- const auto iter = std::find_if(gPlaybackDevices.cbegin(), gPlaybackDevices.cend(),
- [name](const DeviceEntry &entry) { return name == entry.mName; });
- if(iter == gPlaybackDevices.cend())
- throw al::backend_exception{al::backend_error::NoDevice, "No device named {}", name};
- mDeviceID = iter->mPhysDeviceID;
- }
- mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, nullptr, nullptr);
- if(!mStream)
- throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
- auto have = SDL_AudioSpec{};
- auto update_size = int{};
- if(SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size))
- {
- auto devtype = mDevice->FmtType;
- switch(have.format)
- {
- case SDL_AUDIO_U8: devtype = DevFmtUByte; break;
- case SDL_AUDIO_S8: devtype = DevFmtByte; break;
- case SDL_AUDIO_S16: devtype = DevFmtShort; break;
- case SDL_AUDIO_S32: devtype = DevFmtInt; break;
- case SDL_AUDIO_F32: devtype = DevFmtFloat; break;
- default: break;
- }
- mDevice->FmtType = devtype;
- if(have.freq >= int{MinOutputRate} && have.freq <= int{MaxOutputRate})
- mDevice->mSampleRate = static_cast<uint>(have.freq);
- /* SDL guarantees these layouts for the given channel count. */
- if(have.channels == 8)
- mDevice->FmtChans = DevFmtX71;
- else if(have.channels == 7)
- mDevice->FmtChans = DevFmtX61;
- else if(have.channels == 6)
- mDevice->FmtChans = DevFmtX51;
- else if(have.channels == 4)
- mDevice->FmtChans = DevFmtQuad;
- else if(have.channels >= 2)
- mDevice->FmtChans = DevFmtStereo;
- else if(have.channels == 1)
- mDevice->FmtChans = DevFmtMono;
- mDevice->mAmbiOrder = 0;
- mNumChannels = static_cast<uint>(have.channels);
- mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
- if(update_size >= 64)
- {
- /* We have to assume the total buffer size is just twice the update
- * size. SDL doesn't tell us the full end-to-end buffer latency.
- */
- mDevice->mUpdateSize = static_cast<uint>(update_size);
- mDevice->mBufferSize = mDevice->mUpdateSize*2u;
- }
- else
- ERR("Invalid update size from SDL stream: {}", update_size);
- }
- else
- ERR("Failed to get format from SDL stream: {}", SDL_GetError());
- mDeviceName = name;
- }
- auto Sdl3Backend::reset() -> bool
- {
- static constexpr auto callback = [](void *ptr, SDL_AudioStream *stream, int additional_amount,
- int total_amount) noexcept
- {
- return static_cast<Sdl3Backend*>(ptr)->audioCallback(stream, additional_amount,
- total_amount);
- };
- if(mStream)
- SDL_DestroyAudioStream(mStream);
- mStream = nullptr;
- mBuffer.clear();
- mBuffer.shrink_to_fit();
- auto want = SDL_AudioSpec{};
- if(!SDL_GetAudioDeviceFormat(mDeviceID, &want, nullptr))
- ERR("Failed to get device format: {}", SDL_GetError());
- if(mDevice->Flags.test(FrequencyRequest) || want.freq < int{MinOutputRate})
- want.freq = static_cast<int>(mDevice->mSampleRate);
- if(mDevice->Flags.test(SampleTypeRequest)
- || !(want.format == SDL_AUDIO_U8 || want.format == SDL_AUDIO_S8
- || want.format == SDL_AUDIO_S16 || want.format == SDL_AUDIO_S32
- || want.format == SDL_AUDIO_F32))
- {
- switch(mDevice->FmtType)
- {
- case DevFmtUByte: want.format = SDL_AUDIO_U8; break;
- case DevFmtByte: want.format = SDL_AUDIO_S8; break;
- case DevFmtUShort: [[fallthrough]];
- case DevFmtShort: want.format = SDL_AUDIO_S16; break;
- case DevFmtUInt: [[fallthrough]];
- case DevFmtInt: want.format = SDL_AUDIO_S32; break;
- case DevFmtFloat: want.format = SDL_AUDIO_F32; break;
- }
- }
- if(mDevice->Flags.test(ChannelsRequest) || want.channels < 1)
- want.channels = static_cast<int>(std::min<uint>(mDevice->channelsFromFmt(),
- std::numeric_limits<int>::max()));
- mStream = SDL_OpenAudioDeviceStream(mDeviceID, &want, callback, this);
- if(!mStream)
- {
- /* If creating the stream failed, try again without a specific format. */
- mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, callback, this);
- if(!mStream)
- throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to recreate stream: {}", SDL_GetError()};
- }
- auto update_size = int{};
- auto have = SDL_AudioSpec{};
- SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size);
- have = SDL_AudioSpec{};
- if(!SDL_GetAudioStreamFormat(mStream, &have, nullptr))
- throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to get stream format: {}", SDL_GetError()};
- if(!mDevice->Flags.test(ChannelsRequest)
- || (static_cast<uint>(have.channels) != mDevice->channelsFromFmt()
- && !(mDevice->FmtChans == DevFmtStereo && have.channels >= 2)))
- {
- /* SDL guarantees these layouts for the given channel count. */
- if(have.channels == 8)
- mDevice->FmtChans = DevFmtX71;
- else if(have.channels == 7)
- mDevice->FmtChans = DevFmtX61;
- else if(have.channels == 6)
- mDevice->FmtChans = DevFmtX51;
- else if(have.channels == 4)
- mDevice->FmtChans = DevFmtQuad;
- else if(have.channels >= 2)
- mDevice->FmtChans = DevFmtStereo;
- else if(have.channels == 1)
- mDevice->FmtChans = DevFmtMono;
- else
- throw al::backend_exception{al::backend_error::DeviceError,
- "Unhandled SDL channel count: {}", have.channels};
- mDevice->mAmbiOrder = 0;
- }
- mNumChannels = static_cast<uint>(have.channels);
- switch(have.format)
- {
- case SDL_AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
- case SDL_AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
- case SDL_AUDIO_S16: mDevice->FmtType = DevFmtShort; break;
- case SDL_AUDIO_S32: mDevice->FmtType = DevFmtInt; break;
- case SDL_AUDIO_F32: mDevice->FmtType = DevFmtFloat; break;
- default:
- throw al::backend_exception{al::backend_error::DeviceError,
- "Unhandled SDL format: {:#04x}", al::to_underlying(have.format)};
- }
- mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
- if(have.freq < int{MinOutputRate})
- throw al::backend_exception{al::backend_error::DeviceError,
- "Unhandled SDL sample rate: {}", have.freq};
- mDevice->mSampleRate = static_cast<uint>(have.freq);
- if(update_size >= 64)
- {
- mDevice->mUpdateSize = static_cast<uint>(update_size);
- mDevice->mBufferSize = mDevice->mUpdateSize*2u;
- mBuffer.resize(size_t{mDevice->mUpdateSize} * mFrameSize);
- std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
- ? std::byte{0x80} : std::byte{});
- }
- else
- ERR("Invalid update size from SDL stream: {}", update_size);
- setDefaultWFXChannelOrder();
- return true;
- }
- void Sdl3Backend::start()
- { SDL_ResumeAudioStreamDevice(mStream); }
- void Sdl3Backend::stop()
- { SDL_PauseAudioStreamDevice(mStream); }
- } // namespace
- auto SDL3BackendFactory::getFactory() -> BackendFactory&
- {
- static SDL3BackendFactory factory{};
- return factory;
- }
- auto SDL3BackendFactory::init() -> bool
- {
- if(!SDL_InitSubSystem(SDL_INIT_AUDIO))
- return false;
- TRACE("Current SDL3 audio driver: \"{}\"", SDL_GetCurrentAudioDriver());
- return true;
- }
- auto SDL3BackendFactory::querySupport(BackendType type) -> bool
- { return type == BackendType::Playback; }
- auto SDL3BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
- {
- auto outnames = std::vector<std::string>{};
- if(type != BackendType::Playback)
- return outnames;
- EnumeratePlaybackDevices();
- outnames.reserve(gPlaybackDevices.size()+1);
- outnames.emplace_back(getDefaultDeviceName());
- std::transform(gPlaybackDevices.begin(), gPlaybackDevices.end(), std::back_inserter(outnames),
- std::mem_fn(&DeviceEntry::mName));
- return outnames;
- }
- auto SDL3BackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
- {
- if(type == BackendType::Playback)
- return BackendPtr{new Sdl3Backend{device}};
- return nullptr;
- }
|