193 lines
6.1 KiB
C++
193 lines
6.1 KiB
C++
#include <cstring>
|
|
#include <string>
|
|
#include "./AudioInput.h"
|
|
#include "./AudioReframer.h"
|
|
#include "./AudioResampler.h"
|
|
#include "./AudioMerger.h"
|
|
#include "../logger.h"
|
|
#include "AudioGain.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 = std::make_unique<InputReframer>(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) {
|
|
std::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();
|
|
{
|
|
std::lock_guard lock(this->consumers_lock);
|
|
for(const auto& consumer : this->_consumers)
|
|
consumer->handle = nullptr;
|
|
}
|
|
free(this->resample_buffer);
|
|
}
|
|
|
|
void AudioInput::set_device(const std::shared_ptr<AudioDevice> &device) {
|
|
std::lock_guard lock{this->input_source_lock};
|
|
if(device == this->input_device) {
|
|
return;
|
|
}
|
|
|
|
this->close_device();
|
|
this->input_device = device;
|
|
}
|
|
|
|
void AudioInput::close_device() {
|
|
std::lock_guard lock{this->input_source_lock};
|
|
if(this->input_recorder) {
|
|
this->input_recorder->remove_consumer(this);
|
|
this->input_recorder->stop_if_possible();
|
|
this->input_recorder.reset();
|
|
}
|
|
this->_resampler = nullptr;
|
|
this->input_device = nullptr;
|
|
}
|
|
|
|
bool AudioInput::record(std::string& error) {
|
|
std::lock_guard lock{this->input_source_lock};
|
|
if(!this->input_device) {
|
|
error = "no device";
|
|
return false;
|
|
}
|
|
|
|
if(this->input_recorder) {
|
|
return true;
|
|
}
|
|
|
|
this->input_recorder = this->input_device->record();
|
|
if(!this->input_recorder) {
|
|
error = "failed to get recorder";
|
|
return false;
|
|
}
|
|
|
|
if(this->input_recorder->sample_rate() != this->sample_rate()) {
|
|
this->_resampler = std::make_unique<AudioResampler>(this->input_recorder->sample_rate(), this->sample_rate(), this->_channel_count);
|
|
}
|
|
|
|
this->input_recorder->register_consumer(this);
|
|
if(!this->input_recorder->start(error)) {
|
|
this->input_recorder->remove_consumer(this);
|
|
this->input_recorder.reset();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AudioInput::recording() {
|
|
return !!this->input_recorder;
|
|
}
|
|
|
|
void AudioInput::stop() {
|
|
if(!this->input_recorder) return;
|
|
|
|
this->input_recorder->remove_consumer(this);
|
|
this->input_recorder->stop_if_possible();
|
|
this->input_recorder.reset();
|
|
}
|
|
|
|
std::shared_ptr<AudioConsumer> AudioInput::create_consumer(size_t frame_length) {
|
|
auto result = std::shared_ptr<AudioConsumer>(new AudioConsumer(this, this->_channel_count, this->_sample_rate, frame_length));
|
|
{
|
|
std::lock_guard lock(this->consumers_lock);
|
|
this->_consumers.push_back(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void AudioInput::delete_consumer(const std::shared_ptr<AudioConsumer> &source) {
|
|
{
|
|
std::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;
|
|
}
|
|
|
|
void AudioInput::consume(const void *input, size_t frameCount, size_t channels) {
|
|
if(channels != this->_channel_count) {
|
|
if(channels < 1 || channels > 2) {
|
|
log_critical(category::audio, tr("Channel count miss match (Received: {})!"), channels);
|
|
}
|
|
|
|
this->ensure_resample_buffer_capacity(frameCount * this->_channel_count * sizeof(float));
|
|
audio::merge::merge_channels_interleaved(this->resample_buffer, this->_channel_count, input, channels, frameCount);
|
|
input = this->resample_buffer;
|
|
}
|
|
|
|
if(this->_resampler) {
|
|
const auto expected_size = this->_resampler->estimated_output_size(frameCount);
|
|
const auto expected_byte_size = expected_size * this->_channel_count * sizeof(float);
|
|
this->ensure_resample_buffer_capacity(expected_byte_size);
|
|
|
|
size_t sample_count{expected_size};
|
|
if(!this->_resampler->process(this->resample_buffer, input, frameCount, sample_count)) {
|
|
log_error(category::audio, tr("Failed to resample input audio."));
|
|
return;
|
|
}
|
|
|
|
frameCount = sample_count;
|
|
input = this->resample_buffer;
|
|
|
|
audio::apply_gain(this->resample_buffer, this->_channel_count, frameCount, this->_volume);
|
|
} else if(this->_volume != 1) {
|
|
const auto byte_size = frameCount * this->_channel_count * sizeof(float);
|
|
this->ensure_resample_buffer_capacity(byte_size);
|
|
|
|
if(this->resample_buffer != input) {
|
|
memcpy(this->resample_buffer, input, byte_size);
|
|
input = this->resample_buffer;
|
|
}
|
|
|
|
audio::apply_gain(this->resample_buffer, this->_channel_count, frameCount, this->_volume);
|
|
}
|
|
|
|
auto begin = std::chrono::system_clock::now();
|
|
for(const auto& consumer : this->consumers()) {
|
|
consumer->process_data(input, frameCount);
|
|
}
|
|
|
|
auto end = std::chrono::system_clock::now();
|
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
|
|
if(ms > 5) {
|
|
log_warn(category::audio, tr("Processing of audio input needed {}ms. This could be an issue!"), std::chrono::duration_cast<chrono::milliseconds>(end - begin).count());
|
|
}
|
|
}
|
|
|
|
void AudioInput::ensure_resample_buffer_capacity(size_t size) {
|
|
if(this->resample_buffer_size < size) {
|
|
free(this->resample_buffer);
|
|
this->resample_buffer = malloc(size);
|
|
this->resample_buffer_size = size;
|
|
}
|
|
} |