#include #include #include #include #include #include #include #include #include #include #include namespace psemek::sdl2 { namespace { struct audio_engine_impl : audio::engine { audio_engine_impl(); ~audio_engine_impl(); audio::channel_ptr output() override; private: std::shared_ptr sdl_init_; SDL_AudioDeviceID device_; std::vector buffer_; bool thread_registered_ = false; audio::channel_ptr output_; template static void callback(void * userdata, std::uint8_t * stream, int len); }; audio_engine_impl::audio_engine_impl() : sdl_init_(sdl2::init(SDL_INIT_AUDIO)) { SDL_AudioSpec desired, obtained; desired.freq = audio::frequency; desired.channels = 2; desired.format = AUDIO_F32SYS; desired.samples = 256; desired.callback = &callback; desired.userdata = this; if (device_ = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0); device_ == 0) { log::error() << "SDL_OpenAudioDevice failed: " << SDL_GetError(); log::error() << "Fallback to int16 audio output"; desired.format = AUDIO_S16SYS; desired.callback = &callback; if (device_ = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0); device_ == 0) { fail("SDL_OpenAudioDevice failed:"); } } log::info() << "Initialized audio: " << static_cast(obtained.channels) << " channels, " << obtained.freq << " Hz, " << obtained.samples << " samples " << (desired.format == AUDIO_F32SYS ? "f32" : "i16"); buffer_.resize(obtained.samples * obtained.channels); output_ = std::make_shared(); SDL_PauseAudioDevice(device_, 0); } audio_engine_impl::~audio_engine_impl() { SDL_CloseAudioDevice(device_); } template void audio_engine_impl::callback(void * userdata, std::uint8_t * dst_u8, int len) { static std::string const profiler_str = "audio"; prof::profiler prof(profiler_str); auto self = static_cast(userdata); auto stream = self->output()->stream(); SampleType * dst = reinterpret_cast(dst_u8); if (!self->thread_registered_) { log::register_thread("audio"); self->thread_registered_ = true; } std::size_t const size = len / sizeof(SampleType); if constexpr (std::is_same_v) { std::size_t read = 0; if (stream) read = stream->read({self->buffer_.data(), size}); std::fill(self->buffer_.data() + read, self->buffer_.data() + size, 0.f); for (auto s : self->buffer_) *dst++ = static_cast(std::max(std::min((65535.f * s - 1.f) / 2.f, 32767.f), -32768.f)); } else { std::size_t read = 0; if (stream) read = stream->read({dst, size}); std::fill(dst + read, dst + size, 0.f); } if (auto duration = prof.duration(); duration > (size * audio::inv_frequency / 2)) log::warning() << "Audio can't keep up, callback took " << std::setprecision(5) << (1000.0 * duration) << " ms"; } audio::channel_ptr audio_engine_impl::output() { return output_; } } } namespace psemek::audio { std::unique_ptr make_engine() { return std::make_unique(); } }