#include <ftl/audio/speaker.hpp> #include <ftl/audio/audio.hpp> #include <ftl/audio/portaudio.hpp> #define LOGURU_REPLACE_GLOG 1 #include <loguru.hpp> using ftl::audio::Speaker; using ftl::audio::Frame; using ftl::audio::FrameSet; using ftl::audio::Audio; using ftl::codecs::Channel; #ifdef HAVE_PORTAUDIO /* Portaudio callback to receive audio data. */ template <typename BUFFER> static int pa_speaker_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { auto *buffer = (BUFFER*)userData; // ftl::audio::MonoBuffer16<2000> float *out = (float*)output; buffer->readFrame(out); return 0; } #endif Speaker::Speaker(nlohmann::json &config) : ftl::Configurable(config), buffer_(nullptr), stream_(nullptr) { #ifdef HAVE_PORTAUDIO ftl::audio::pa_init(); #else // No portaudio LOG(ERROR) << "No audio support"; #endif volume_ = 1.0f; active_ = false; extra_delay_ = value("delay",0.1f); on("delay", [this]() { extra_delay_ = value("delay",0.1f); setDelay(0); }); setDelay(0); } Speaker::~Speaker() { if (active_) { active_ = false; #ifdef HAVE_PORTAUDIO auto err = Pa_AbortStream(stream_); if (err != paNoError) { LOG(ERROR) << "Portaudio stop stream error: " << Pa_GetErrorText(err); //active_ = false; } err = Pa_CloseStream(stream_); if (err != paNoError) { LOG(ERROR) << "Portaudio close stream error: " << Pa_GetErrorText(err); } #endif } #ifdef HAVE_PORTAUDIO ftl::audio::pa_final(); #endif } void Speaker::_open(int fsize, int sample, int channels) { #ifdef HAVE_PORTAUDIO if (buffer_) delete buffer_; LOG(INFO) << "Create speaker: " << sample << "," << channels; if (sample == 0 || channels == 0) return; if (channels >= 2) { buffer_ = new ftl::audio::StereoBufferF<2000>(sample); } else { buffer_ = new ftl::audio::MonoBufferF<2000>(sample); } PaStreamParameters outputParameters; //bzero( &inputParameters, sizeof( inputParameters ) ); outputParameters.channelCount = channels; outputParameters.device = Pa_GetDefaultOutputDevice(); outputParameters.sampleFormat = paFloat32; outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; //LOG(INFO) << "OUTPUT LATENCY: " << outputParameters.suggestedLatency; latency_ = int64_t(outputParameters.suggestedLatency * 1000.0); auto err = Pa_OpenStream( &stream_, NULL, &outputParameters, sample, // Sample rate 960, // Size of single frame paNoFlag, (channels == 1) ? pa_speaker_callback<ftl::audio::MonoBufferF<2000>> : pa_speaker_callback<ftl::audio::StereoBufferF<2000>>, this->buffer_ ); if (err != paNoError) { LOG(ERROR) << "Portaudio open stream error: " << Pa_GetErrorText(err); active_ = false; return; } else { active_ = true; } err = Pa_StartStream(stream_); if (err != paNoError) { LOG(ERROR) << "Portaudio start stream error: " << Pa_GetErrorText(err); //active_ = false; return; } LOG(INFO) << "Speaker ready."; #else LOG(INFO) << "Built without portaudio (no sound)"; #endif } void Speaker::queue(int64_t ts, ftl::audio::Frame &frame) { const auto &audio = frame.get<std::list<ftl::audio::Audio>> ((frame.hasChannel(Channel::AudioStereo)) ? Channel::AudioStereo : Channel::AudioMono); if (!buffer_) { _open(960, 48000, (frame.hasChannel(Channel::AudioStereo)) ? 2 : 1); } if (!buffer_) return; //LOG(INFO) << "Buffer Fullness (" << ts << "): " << buffer_->size() << " - " << audio.size(); for (const auto &d : audio) { buffer_->write(d.data()); } //LOG(INFO) << "Audio delay: " << buffer_.delay() << "s"; } void Speaker::queue(int64_t ts, const ftl::audio::Audio &d) { if (!buffer_) { _open(960, 48000, 2); } if (!buffer_) return; //LOG(INFO) << "Buffer Fullness (" << ts << "): " << buffer_->size() << " - " << audio.size(); buffer_->write(d.data()); //LOG(INFO) << "Audio delay: " << buffer_.delay() << "s"; } void Speaker::setDelay(int64_t ms) { ms -= latency_; float d = static_cast<float>(ms) / 1000.0f + extra_delay_; if (d < 0.0f) d = 0.001f; // Clamp to 0 delay (not ideal to be exactly 0) if (buffer_) { buffer_->setDelay(d); LOG(INFO) << "Audio delay: " << buffer_->delay(); } } void Speaker::setVolume(float value) { // TODO: adjust volume using system mixer volume_ = std::max(0.0f, std::min(1.0f, value)); if (buffer_) buffer_->setGain(volume_); } float Speaker::volume() { return volume_; }