115 lines
2.8 KiB
C++
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>();
|
|
}
|
|
|
|
}
|