psemek/libs/sdl2/source/audio_engine.cpp
2023-11-03 12:59:37 +03:00

115 lines
2.8 KiB
C++

#include <psemek/audio/engine.hpp>
#include <psemek/audio/constants.hpp>
#include <psemek/sdl2/init.hpp>
#include <psemek/log/log.hpp>
#include <psemek/prof/profiler.hpp>
#include <psemek/util/at_scope_exit.hpp>
#include <psemek/util/to_string.hpp>
#include <SDL2/SDL.h>
#include <mutex>
#include <atomic>
#include <vector>
#include <optional>
#include <cmath>
#include <iomanip>
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<void> sdl_init_;
SDL_AudioDeviceID device_;
std::vector<float> buffer_;
bool thread_registered_ = false;
audio::channel_ptr output_;
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_S16SYS;
desired.samples = 256;
desired.callback = &callback;
desired.userdata = this;
if (device_ = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0); device_ == 0)
fail("SDL_OpenAudioDevice failed:");
log::info() << "Initialized audio: " << static_cast<int>(obtained.channels) << " channels, " << obtained.freq << " Hz, " << obtained.samples << " samples";
buffer_.resize(obtained.samples * obtained.channels);
output_ = std::make_shared<audio::channel>();
SDL_PauseAudioDevice(device_, 0);
}
audio_engine_impl::~audio_engine_impl()
{
SDL_CloseAudioDevice(device_);
}
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<audio_engine_impl *>(userdata);
auto stream = self->output()->stream();
std::int16_t * dst = reinterpret_cast<std::int16_t *>(dst_u8);
if (!self->thread_registered_)
{
log::register_thread("audio");
self->thread_registered_ = true;
}
std::size_t const size = len / 2;
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::int16_t>(std::max(std::min((65535.f * s - 1.f) / 2.f, 32767.f), -32768.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<engine> make_engine()
{
return std::make_unique<sdl2::audio_engine_impl>();
}
}