164 lines
4.9 KiB
C++
164 lines
4.9 KiB
C++
#include <cstring>
|
|
#include <string>
|
|
#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<Reframer>(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) {
|
|
error = "Pa_OpenStream returned " + to_string(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)
|
|
Pa_StopStream(this->input_stream);
|
|
}
|
|
|
|
void AudioInput::close_device() {
|
|
lock_guard lock(this->input_stream_lock);
|
|
if(this->input_stream) {
|
|
/* TODO: Test for errors */
|
|
Pa_StopStream(this->input_stream);
|
|
Pa_CloseStream(this->input_stream);
|
|
|
|
this->input_stream = nullptr;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<AudioConsumer> AudioInput::create_consumer(size_t frame_length) {
|
|
auto result = shared_ptr<AudioConsumer>(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<AudioConsumer> &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<AudioInput*>(_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<chrono::milliseconds>(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<chrono::milliseconds>(end - begin).count());
|
|
}
|
|
return 0;
|
|
} |