TeaSpeak-Client/native/serverconnection/src/audio/AudioInput.cpp

179 lines
5.2 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(&parameters, 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,
&parameters,
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<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;
}