#include #include #include "AudioInput.h" #include "AudioReframer.h" #include "../logger.h" using namespace std; using namespace tc; using namespace tc::audio; AudioConsumer::AudioConsumer(tc::audio::AudioInput *handle, size_t channel_count, size_t sample_rate, size_t frame_size) : handle(handle), channel_count(channel_count), sample_rate(sample_rate) , frame_size(frame_size) { if(this->frame_size > 0) { this->reframer = make_unique(channel_count, frame_size); this->reframer->on_frame = [&](const void* buffer) { this->handle_framed_data(buffer, this->frame_size); }; } } void AudioConsumer::handle_framed_data(const void *buffer, size_t samples) { unique_lock read_callback_lock(this->on_read_lock); auto function = this->on_read; /* copy */ read_callback_lock.unlock(); if(!function) return; function(buffer, samples); } void AudioConsumer::process_data(const void *buffer, size_t samples) { if(this->reframer) this->reframer->process(buffer, samples); else this->handle_framed_data(buffer, samples); } AudioInput::AudioInput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) {} AudioInput::~AudioInput() { this->close_device(); lock_guard lock(this->consumers_lock); for(const auto& consumer : this->_consumers) consumer->handle = nullptr; } bool AudioInput::open_device(std::string& error, PaDeviceIndex index) { lock_guard lock(this->input_stream_lock); if(index == this->_current_device_index) return true; this->close_device(); this->_current_device_index = index; if(index == paNoDevice) return true; this->_current_device = Pa_GetDeviceInfo(index); if(!this->_current_device) { this->_current_device_index = paNoDevice; error = "failed to get device info"; return false; } PaStreamParameters parameters{}; memset(¶meters, 0, sizeof(parameters)); parameters.channelCount = (int) this->_channel_count; parameters.device = this->_current_device_index; parameters.sampleFormat = paFloat32; parameters.suggestedLatency = this->_current_device->defaultLowOutputLatency; auto err = Pa_OpenStream( &this->input_stream, ¶meters, nullptr, (double) this->_sample_rate, paFramesPerBufferUnspecified, paClipOff, &AudioInput::_audio_callback, this); if(err != paNoError) { this->input_stream = nullptr; error = to_string(err) + "/" + Pa_GetErrorText(err); return false; } return true; } bool AudioInput::record() { lock_guard lock(this->input_stream_lock); if(!this->input_stream) return false; if(Pa_IsStreamActive(this->input_stream)) return true; auto err = Pa_StartStream(this->input_stream); if(err != paNoError && err != paStreamIsNotStopped) { log_error(category::audio, tr("Pa_StartStream returned {}"), err); return false; } return true; } bool AudioInput::recording() { lock_guard lock(this->input_stream_lock); return this->input_stream && Pa_IsStreamActive(this->input_stream); } void AudioInput::stop() { lock_guard lock(this->input_stream_lock); if(this->input_stream) { if(Pa_IsStreamActive(this->input_stream)) Pa_StopStream(this->input_stream); } } void AudioInput::close_device() { lock_guard lock(this->input_stream_lock); if(this->input_stream) { if(Pa_IsStreamActive(this->input_stream)) Pa_StopStream(this->input_stream); auto error = Pa_CloseStream(this->input_stream); if(error != paNoError) log_error(category::audio, tr("Failed to close PA stream: {}"), error); this->input_stream = nullptr; this_thread::sleep_for(chrono::seconds{1}); } } std::shared_ptr AudioInput::create_consumer(size_t frame_length) { auto result = shared_ptr(new AudioConsumer(this, this->_channel_count, this->_sample_rate, frame_length)); { lock_guard lock(this->consumers_lock); this->_consumers.push_back(result); } return result; } void AudioInput::delete_consumer(const std::shared_ptr &source) { { lock_guard lock(this->consumers_lock); auto it = find(this->_consumers.begin(), this->_consumers.end(), source); if(it != this->_consumers.end()) this->_consumers.erase(it); } source->handle = nullptr; } int AudioInput::_audio_callback(const void *a, void *b, unsigned long c, const PaStreamCallbackTimeInfo* d, PaStreamCallbackFlags e, void *_ptr_audio_output) { return reinterpret_cast(_ptr_audio_output)->audio_callback(a, b, c, d, e); } int AudioInput::audio_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) { if (!input) /* hmmm.. suspicious */ return 0; if(this->_volume != 1) { auto ptr = (float*) input; auto left = frameCount * this->_channel_count; while(left-- > 0) *(ptr++) *= this->_volume; } auto begin = chrono::system_clock::now(); for(const auto& consumer : this->consumers()) { consumer->process_data(input, frameCount); } auto end = chrono::system_clock::now(); auto ms = chrono::duration_cast(end - begin).count(); if(ms > 5) { log_warn(category::audio, tr("Processing of audio input needed {}ms. This could be an issue!"), chrono::duration_cast(end - begin).count()); } return 0; }