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

287 lines
8.4 KiB
C++

#include "AudioOutput.h"
#include "AudioMerger.h"
#include "../logger.h"
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
using namespace tc;
using namespace tc::audio;
void AudioOutputSource::clear() {
lock_guard lock(this->buffer_lock);
this->sample_buffers.clear();
this->buffered_samples = 0;
}
ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) {
auto sample_count = samples;
_retest:
{
lock_guard lock(this->buffer_lock);
if(this->buffering) {
if(this->buffered_samples > this->min_buffer) {
this->buffering = false;
} else {
return 0;
}
}
while(sample_count > 0 && !this->sample_buffers.empty()) {
auto buf = this->sample_buffers[0];
auto sc = min((size_t) (buf->sample_size - buf->sample_index), (size_t) sample_count);
if(sc > 0 && buffer) { /* just to ensure */
memcpy(buffer, (char *) buf->sample_data + this->channel_count * buf->sample_index * 4, sc * this->channel_count * 4);
} else {
#ifndef WIN32
/* for my debugger */
__asm__("nop");
#endif
}
sample_count -= sc;
buf->sample_index += (uint16_t) sc;
if(buf->sample_index == buf->sample_size)
this->sample_buffers.pop_front();
if(buffer)
buffer = (char*) buffer + sc * this->channel_count * 4;
}
this->buffered_samples -= samples - sample_count;
}
if(sample_count > 0) {
if(this->on_underflow) {
if(this->on_underflow()) {
goto _retest;
}
}
this->buffering = true;
}
if(this->on_read)
this->on_read();
return samples - sample_count; /* return the written samples */
}
ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) {
auto buf = SampleBuffer::allocate((uint8_t) this->channel_count, (uint16_t) samples);
if(!buf)
return -1;
buf->sample_index = 0;
memcpy(buf->sample_data, buffer, this->channel_count * samples * 4);
return this->enqueue_samples(buf);
}
ssize_t AudioOutputSource::enqueue_samples(const std::shared_ptr<tc::audio::SampleBuffer> &buf) {
if(!buf) return 0;
{
unique_lock lock(this->buffer_lock);
if(this->max_latency > 0 && this->buffered_samples + buf->sample_size > this->max_latency) {
/* overflow! */
auto overflow_length = this->buffered_samples + buf->sample_size - this->max_latency;
if(this->on_overflow) {
lock.unlock();
this->on_overflow(overflow_length);
lock.lock();
}
switch (this->overflow_strategy) {
case overflow_strategy::discard_input:
return -2;
case overflow_strategy::discard_buffer_all:
this->sample_buffers.clear();
break;
case overflow_strategy::discard_buffer_half:
this->sample_buffers.erase(this->sample_buffers.begin(), this->sample_buffers.begin() + (int) ceil(this->sample_buffers.size() / 2));
break;
case overflow_strategy::ignore:
break;
}
}
this->sample_buffers.push_back(buf);
this->buffered_samples += buf->sample_size;
}
return buf->sample_size;
}
AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) {}
AudioOutput::~AudioOutput() {
this->close_device();
this->cleanup_buffers();
}
std::shared_ptr<AudioOutputSource> AudioOutput::create_source() {
auto result = shared_ptr<AudioOutputSource>(new AudioOutputSource(this, this->_channel_count, this->_sample_rate));
{
lock_guard lock(this->sources_lock);
this->_sources.push_back(result);
}
return result;
}
void AudioOutput::delete_source(const std::shared_ptr<tc::audio::AudioOutputSource> &source) {
{
lock_guard lock(this->sources_lock);
auto it = find(this->_sources.begin(), this->_sources.end(), source);
if(it != this->_sources.end())
this->_sources.erase(it);
}
source->handle = nullptr;
}
void AudioOutput::cleanup_buffers() {
lock_guard buffer_lock(this->buffer_lock);
if(this->source_buffer)
free(this->source_buffer);
if(this->source_merge_buffer)
free(this->source_merge_buffer);
this->source_merge_buffer = nullptr;
this->source_buffer = nullptr;
this->source_merge_buffer_length = 0;
this->source_buffer_length = 0;
}
int AudioOutput::_audio_callback(const void *a, void *b, unsigned long c, const PaStreamCallbackTimeInfo* d, PaStreamCallbackFlags e, void *_ptr_audio_output) {
return reinterpret_cast<AudioOutput*>(_ptr_audio_output)->audio_callback(a, b, c, d, e);
}
int AudioOutput::audio_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
if(!output) /* hmmm.. suspicious */
return 0;
lock_guard buffer_lock(this->buffer_lock);
size_t buffer_length = frameCount * 4 * this->_channel_count;
size_t sources = 0;
size_t actual_sources = 0;
auto volume = this->_volume;
{
lock_guard lock(this->sources_lock);
sources = this->_sources.size();
actual_sources = sources;
if(sources > 0) {
if(volume > 0) { /* allocate the required space */
auto source_buffer_length = buffer_length * sources;
auto source_merge_buffer_length = sizeof(void*) * sources;
if(this->source_buffer_length < source_buffer_length || !this->source_buffer) {
if(this->source_buffer)
free(this->source_buffer);
this->source_buffer = malloc(source_buffer_length);
this->source_buffer_length = source_buffer_length;
}
if(this->source_merge_buffer_length < source_merge_buffer_length || !this->source_merge_buffer) {
if(this->source_merge_buffer)
free(this->source_merge_buffer);
this->source_merge_buffer = (void**) malloc(source_merge_buffer_length);
this->source_merge_buffer_length = source_merge_buffer_length;
}
}
for(size_t index = 0; index < sources; index++) {
auto& source = this->_sources[index];
if(volume > 0) {
this->source_merge_buffer[index] = (char*) this->source_buffer + (buffer_length * index);
auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], frameCount);
if(written_frames != frameCount) {
if(written_frames == 0) {
this->source_merge_buffer[index] = nullptr;
actual_sources--;
} else {
/* fill up the rest with silence (0) */
auto written = written_frames * this->_channel_count * 4;
memset((char*) this->source_merge_buffer[index] + written, 0, (frameCount - written_frames) * this->_channel_count * 4);
}
}
} else {
this->_sources[index]->pop_samples(nullptr, frameCount);
}
}
}
}
if(actual_sources > 0 && volume > 0) {
if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, frameCount)) {
log_warn(category::audio, tr("failed to merge buffers!"));
}
auto float_length = this->_channel_count * frameCount;
auto data = (float*) output;
while(float_length-- > 0)
*data++ *= volume;
} else {
memset(output, 0, this->_channel_count * 4 * frameCount);
}
return 0;
}
bool AudioOutput::open_device(std::string& error, PaDeviceIndex index) {
lock_guard lock(this->output_stream_lock);
if(index == this->_current_device_index)
return true;
this->close_device();
this->_current_device_index = index;
this->_current_device = Pa_GetDeviceInfo(index);
if(!this->_current_device) {
this->_current_device_index = paNoDevice;
error = "failed to get device info";
return false;
}
PaStreamParameters output_parameters{};
memset(&output_parameters, 0, sizeof(output_parameters));
output_parameters.channelCount = (int) this->_channel_count;
output_parameters.device = this->_current_device_index;
output_parameters.sampleFormat = paFloat32;
output_parameters.suggestedLatency = this->_current_device->defaultLowOutputLatency;
auto err = Pa_OpenStream(&output_stream, nullptr, &output_parameters, (double) this->_sample_rate, paFramesPerBufferUnspecified, paClipOff, &AudioOutput::_audio_callback, this);
if(err != paNoError) {
error = to_string(err) + "/" + Pa_GetErrorText(err);
return false;
}
return true;
}
bool AudioOutput::playback() {
lock_guard lock(this->output_stream_lock);
if(!this->output_stream)
return false;
auto err = Pa_StartStream(this->output_stream);
if(err != paNoError && err != paStreamIsNotStopped) {
log_error(category::audio, tr("Pa_StartStream returned {}"), err);
return false;
}
return true;
}
void AudioOutput::close_device() {
lock_guard lock(this->output_stream_lock);
if(this->output_stream) {
/* TODO: Test for errors */
Pa_StopStream(this->output_stream);
Pa_CloseStream(this->output_stream);
this->output_stream = nullptr;
}
}