mirror of https://github.com/AxioDL/boo.git
Add SDL2 audio backend
This commit is contained in:
parent
49364a152f
commit
6e4030212c
|
@ -107,14 +107,16 @@ if (NOT MSVC)
|
||||||
target_compile_options(boo PRIVATE -Wno-narrowing)
|
target_compile_options(boo PRIVATE -Wno-narrowing)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(IPP)
|
if (NOT NINTENDO_SWITCH)
|
||||||
if (IPP_FOUND)
|
find_package(IPP)
|
||||||
target_compile_definitions(boo PUBLIC -DINTEL_IPP=1)
|
if (IPP_FOUND)
|
||||||
target_include_directories(boo PUBLIC ${IPP_INCLUDE_DIRS})
|
target_compile_definitions(boo PUBLIC -DINTEL_IPP=1)
|
||||||
target_link_libraries(boo PUBLIC ${IPP_LIBRARIES})
|
target_include_directories(boo PUBLIC ${IPP_INCLUDE_DIRS})
|
||||||
message(STATUS "Building with IPP support")
|
target_link_libraries(boo PUBLIC ${IPP_LIBRARIES})
|
||||||
else()
|
message(STATUS "Building with IPP support")
|
||||||
message(WARNING "IPP not found; skipping support")
|
else()
|
||||||
|
message(WARNING "IPP not found; skipping support")
|
||||||
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(_EXTRA_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR})
|
set(_EXTRA_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
@ -131,7 +133,16 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64
|
||||||
set(AudioMatrix_SRC lib/audiodev/AudioMatrixSSE.cpp)
|
set(AudioMatrix_SRC lib/audiodev/AudioMatrixSSE.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WINDOWS_STORE)
|
option(USE_SDL2_AUDIO "Use SDL2 audio backend" ON)
|
||||||
|
if (USE_SDL2_AUDIO)
|
||||||
|
if (NOT TARGET SDL2::SDL2-static)
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
endif ()
|
||||||
|
target_sources(boo PRIVATE
|
||||||
|
${AudioMatrix_SRC}
|
||||||
|
lib/audiodev/SDL2.cpp)
|
||||||
|
target_link_libraries(boo PRIVATE SDL2::SDL2-static)
|
||||||
|
elseif (WINDOWS_STORE)
|
||||||
target_sources(boo PRIVATE
|
target_sources(boo PRIVATE
|
||||||
${AudioMatrix_SRC}
|
${AudioMatrix_SRC}
|
||||||
lib/audiodev/WASAPI.cpp
|
lib/audiodev/WASAPI.cpp
|
||||||
|
@ -260,12 +271,10 @@ else(NOT GEKKO)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT NX)
|
target_link_libraries(boo
|
||||||
target_link_libraries(boo
|
PUBLIC
|
||||||
PUBLIC
|
soxr
|
||||||
soxr
|
)
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_link_libraries(boo PUBLIC logvisor OptickCore)
|
target_link_libraries(boo PUBLIC logvisor OptickCore)
|
||||||
target_include_directories(boo
|
target_include_directories(boo
|
||||||
|
|
|
@ -100,6 +100,18 @@ struct IAudioVoiceEngine {
|
||||||
|
|
||||||
/** Get canonical count of frames for each 5ms output block */
|
/** Get canonical count of frames for each 5ms output block */
|
||||||
virtual size_t get5MsFrames() const = 0;
|
virtual size_t get5MsFrames() const = 0;
|
||||||
|
|
||||||
|
/** Stops the async audio pump */
|
||||||
|
virtual void stopPump() const {}
|
||||||
|
|
||||||
|
/** Starts the async audio pump */
|
||||||
|
virtual void startPump() const {}
|
||||||
|
|
||||||
|
/** Lock the async audio pump for updates */
|
||||||
|
virtual void lockPump() const {}
|
||||||
|
|
||||||
|
/** Unlock the async audio pump */
|
||||||
|
virtual void unlockPump() const {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Construct host platform's voice engine */
|
/** Construct host platform's voice engine */
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
#include "lib/audiodev/AudioVoiceEngine.hpp"
|
||||||
|
#include "logvisor/logvisor.hpp"
|
||||||
|
|
||||||
|
#include <SDL_audio.h>
|
||||||
|
#include <SDL_hints.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
[[noreturn]] inline __attribute__((always_inline)) void unreachable() { __builtin_unreachable(); }
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
[[noreturn]] __forceinline void unreachable() { __assume(false); }
|
||||||
|
#else
|
||||||
|
#error Unknown compiler
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace boo {
|
||||||
|
static logvisor::Module Log("boo::SDL2AudioVoiceEngine");
|
||||||
|
|
||||||
|
static void SDLAudioCallback(void* userdata, Uint8* stream, int len);
|
||||||
|
|
||||||
|
static inline soxr_datatype_t _toSoxrFormat(SDL_AudioFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case AUDIO_F32SYS:
|
||||||
|
return SOXR_FLOAT32_I;
|
||||||
|
case AUDIO_S32SYS:
|
||||||
|
return SOXR_INT32_I;
|
||||||
|
case AUDIO_S16SYS:
|
||||||
|
return SOXR_INT16_I;
|
||||||
|
default:
|
||||||
|
Log.report(logvisor::Fatal, FMT_STRING("Unhandled audio format {}"), format);
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned int _bitsPerSample(SDL_AudioFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case AUDIO_F32SYS:
|
||||||
|
case AUDIO_S32SYS:
|
||||||
|
return 32;
|
||||||
|
case AUDIO_S16SYS:
|
||||||
|
return 16;
|
||||||
|
default:
|
||||||
|
Log.report(logvisor::Fatal, FMT_STRING("Unhandled audio format {}"), format);
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SDL2AudioVoiceEngine : BaseAudioVoiceEngine {
|
||||||
|
SDL_AudioDeviceID dev = 0;
|
||||||
|
|
||||||
|
bool _openAudioDevice(const char* name) {
|
||||||
|
if (dev != 0) {
|
||||||
|
SDL_CloseAudioDevice(dev);
|
||||||
|
}
|
||||||
|
SDL_AudioSpec desired;
|
||||||
|
SDL_AudioSpec obtained;
|
||||||
|
SDL_zero(desired);
|
||||||
|
SDL_zero(obtained);
|
||||||
|
desired.freq = 48000;
|
||||||
|
desired.format = AUDIO_F32SYS;
|
||||||
|
desired.channels = 2;
|
||||||
|
desired.samples = 800; // 16.67ms @ 48000hz
|
||||||
|
desired.callback = &SDLAudioCallback;
|
||||||
|
desired.userdata = this;
|
||||||
|
dev = SDL_OpenAudioDevice(name, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||||
|
if (dev == 0) {
|
||||||
|
Log.report(logvisor::Error, FMT_STRING("Failed to open audio device: {}"), SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_mixInfo.m_sampleRate = obtained.freq;
|
||||||
|
m_mixInfo.m_sampleFormat = _toSoxrFormat(obtained.format);
|
||||||
|
m_mixInfo.m_bitsPerSample = _bitsPerSample(obtained.format);
|
||||||
|
m_mixInfo.m_channels = AudioChannelSet::Stereo; // TODO
|
||||||
|
m_mixInfo.m_periodFrames = obtained.samples;
|
||||||
|
m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL2AudioVoiceEngine(const char* uniqueName, const char* friendlyName) {
|
||||||
|
Log.report(logvisor::Info, FMT_STRING("Using audio driver {}"), SDL_GetCurrentAudioDriver());
|
||||||
|
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, friendlyName);
|
||||||
|
_openAudioDevice(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SDL2AudioVoiceEngine() override { SDL_CloseAudioDevice(dev); }
|
||||||
|
|
||||||
|
std::string getCurrentAudioOutput() const override {
|
||||||
|
// TODO
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setCurrentAudioOutput(const char* name) override { return _openAudioDevice(name); }
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> enumerateAudioOutputs() const override {
|
||||||
|
int numDevices = SDL_GetNumAudioDevices(0);
|
||||||
|
if (numDevices == -1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<std::pair<std::string, std::string>> ret;
|
||||||
|
for (int i = 0; i < numDevices; ++i) {
|
||||||
|
ret.emplace_back(SDL_GetAudioDeviceName(i, 0), std::string{});
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from SDL audio thread; synchronization is handled via lockPump/unlockPump
|
||||||
|
void _asyncPumpAndMixVoices(Uint8* stream, int len) {
|
||||||
|
const auto channels = m_mixInfo.m_channelMap.m_channelCount;
|
||||||
|
switch (m_mixInfo.m_sampleFormat) {
|
||||||
|
case SOXR_INT16_I:
|
||||||
|
_pumpAndMixVoices(len / (sizeof(int16_t) * channels), reinterpret_cast<int16_t*>(stream));
|
||||||
|
break;
|
||||||
|
case SOXR_INT32_I:
|
||||||
|
_pumpAndMixVoices(len / (sizeof(int32_t) * channels), reinterpret_cast<int32_t*>(stream));
|
||||||
|
break;
|
||||||
|
case SOXR_FLOAT32_I:
|
||||||
|
_pumpAndMixVoices(len / (sizeof(float) * channels), reinterpret_cast<float*>(stream));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.report(logvisor::Fatal, FMT_STRING("Unhandled audio format {}"), m_mixInfo.m_sampleFormat);
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopPump() const override { SDL_PauseAudioDevice(dev, 1); }
|
||||||
|
void startPump() const override { SDL_PauseAudioDevice(dev, 0); }
|
||||||
|
void lockPump() const override { SDL_LockAudioDevice(dev); }
|
||||||
|
void unlockPump() const override { SDL_UnlockAudioDevice(dev); }
|
||||||
|
|
||||||
|
// MIDI unsupported
|
||||||
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const override { return {}; }
|
||||||
|
bool supportsVirtualMIDIIn() const override { return false; }
|
||||||
|
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver) override { return {}; }
|
||||||
|
std::unique_ptr<IMIDIOut> newVirtualMIDIOut() override { return {}; }
|
||||||
|
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver) override { return {}; }
|
||||||
|
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override { return {}; }
|
||||||
|
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name) override { return {}; }
|
||||||
|
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver) override { return {}; }
|
||||||
|
bool useMIDILock() const override { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void SDLAudioCallback(void* userdata, Uint8* stream, int len) {
|
||||||
|
auto* engine = static_cast<SDL2AudioVoiceEngine*>(userdata);
|
||||||
|
engine->_asyncPumpAndMixVoices(stream, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine(const char* uniqueName, const char* friendlyName) {
|
||||||
|
return std::make_unique<SDL2AudioVoiceEngine>(uniqueName, friendlyName);
|
||||||
|
}
|
||||||
|
} // namespace boo
|
Loading…
Reference in New Issue