2019-10-26 01:51:40 +02:00
|
|
|
#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() {
|
2020-02-08 16:50:48 +01:00
|
|
|
this->buffer.clear();
|
|
|
|
this->buffering = true;
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) {
|
2020-02-08 16:50:48 +01:00
|
|
|
size_t written{0}, written_bytes{0};
|
|
|
|
|
|
|
|
load_buffer:
|
|
|
|
auto available_bytes = this->buffer.fill_count();
|
|
|
|
if(available_bytes < sizeof(float) * this->channel_count) return written;
|
|
|
|
|
|
|
|
auto available_samples = available_bytes / sizeof(float) / this->channel_count;
|
|
|
|
//log_trace(category::audio, tr("Min: {}, Max: {}, Current: {}, Buffering: {}"), this->min_buffered_samples, this->max_buffered_samples, available_samples, this->buffering);
|
|
|
|
if(this->buffering && available_samples < this->min_buffered_samples) return -2;
|
|
|
|
|
|
|
|
this->buffering = false;
|
|
|
|
if(available_samples >= samples - written) {
|
|
|
|
const auto byte_length = (samples - written) * sizeof(float) * this->channel_count;
|
|
|
|
memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length);
|
|
|
|
this->buffer.advance_read_ptr(byte_length);
|
|
|
|
return samples;
|
|
|
|
} else {
|
|
|
|
const auto byte_length = available_samples * sizeof(float) * this->channel_count;
|
|
|
|
memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length);
|
|
|
|
this->buffer.advance_read_ptr(byte_length);
|
|
|
|
written += available_samples;
|
|
|
|
written_bytes += byte_length;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(auto fn = this->on_underflow; fn)
|
|
|
|
if(fn())
|
|
|
|
goto load_buffer;
|
|
|
|
|
|
|
|
this->buffering = true;
|
2019-10-26 01:51:40 +02:00
|
|
|
if(this->on_read)
|
|
|
|
this->on_read();
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
return written; /* return the written samples */
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) {
|
2020-02-08 16:50:48 +01:00
|
|
|
size_t enqueued{0};
|
|
|
|
|
|
|
|
auto free_bytes = this->buffer.free_count();
|
|
|
|
auto free_samples = free_bytes / sizeof(float) / this->channel_count;
|
|
|
|
if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples;
|
|
|
|
|
|
|
|
if(free_samples >= samples) {
|
|
|
|
const auto byte_length = samples * sizeof(float) * this->channel_count;
|
|
|
|
memcpy(this->buffer.write_ptr(), buffer, byte_length);
|
|
|
|
this->buffer.advance_write_ptr(byte_length);
|
|
|
|
return samples;
|
|
|
|
} else {
|
|
|
|
const auto byte_length = free_samples * sizeof(float) * this->channel_count;
|
|
|
|
memcpy(this->buffer.write_ptr(), buffer, byte_length);
|
|
|
|
this->buffer.advance_write_ptr(byte_length);
|
|
|
|
enqueued += free_samples;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(auto fn = this->on_overflow; fn)
|
|
|
|
fn(samples - enqueued);
|
|
|
|
|
|
|
|
switch (this->overflow_strategy) {
|
|
|
|
case overflow_strategy::discard_input:
|
|
|
|
return -2;
|
|
|
|
case overflow_strategy::discard_buffer_all:
|
|
|
|
this->buffer.clear();
|
|
|
|
break;
|
|
|
|
case overflow_strategy::discard_buffer_half:
|
|
|
|
this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
|
|
|
|
break;
|
|
|
|
case overflow_strategy::ignore:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return enqueued;
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
ssize_t AudioOutputSource::enqueue_samples_no_interleave(const void *buffer, size_t samples) {
|
|
|
|
auto free_bytes = this->buffer.free_count();
|
|
|
|
auto free_samples = free_bytes / sizeof(float) / this->channel_count;
|
|
|
|
if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples;
|
|
|
|
|
|
|
|
auto samples_to_write{samples};
|
|
|
|
if(samples_to_write > free_samples) samples_to_write = free_samples;
|
|
|
|
const auto enqueued{samples_to_write};
|
|
|
|
{
|
|
|
|
auto src_buffer = (const float*) buffer;
|
|
|
|
auto target_buffer = (float*) this->buffer.write_ptr();
|
|
|
|
|
|
|
|
while (samples_to_write-- > 0) {
|
|
|
|
*target_buffer = *src_buffer;
|
|
|
|
*(target_buffer + 1) = *(src_buffer + samples);
|
|
|
|
|
|
|
|
target_buffer += 2;
|
|
|
|
src_buffer++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(auto fn = this->on_overflow; fn)
|
|
|
|
fn(samples - enqueued);
|
|
|
|
|
|
|
|
switch (this->overflow_strategy) {
|
|
|
|
case overflow_strategy::discard_input:
|
|
|
|
return -2;
|
|
|
|
case overflow_strategy::discard_buffer_all:
|
|
|
|
this->buffer.clear();
|
|
|
|
break;
|
|
|
|
case overflow_strategy::discard_buffer_half:
|
|
|
|
this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
|
|
|
|
break;
|
|
|
|
case overflow_strategy::ignore:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return enqueued;
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) { }
|
|
|
|
|
2019-10-26 01:51:40 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-02-08 20:45:04 +01:00
|
|
|
void AudioOutput::fill_buffer(void *output, size_t frameCount, size_t channels) {
|
2019-10-26 01:51:40 +02:00
|
|
|
lock_guard buffer_lock(this->buffer_lock);
|
2020-02-08 16:50:48 +01:00
|
|
|
if(this->_volume <= 0) {
|
|
|
|
for(auto& source : this->_sources)
|
|
|
|
source->pop_samples(nullptr, frameCount);
|
|
|
|
memset(output, 0, sizeof(frameCount) * channels * sizeof(float));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-26 01:51:40 +02:00
|
|
|
size_t buffer_length = frameCount * 4 * this->_channel_count;
|
|
|
|
size_t sources = 0;
|
|
|
|
size_t actual_sources = 0;
|
|
|
|
|
|
|
|
{
|
|
|
|
lock_guard lock(this->sources_lock);
|
|
|
|
sources = this->_sources.size();
|
|
|
|
actual_sources = sources;
|
|
|
|
|
|
|
|
if(sources > 0) {
|
2020-02-08 16:50:48 +01:00
|
|
|
/* allocate the required space */
|
|
|
|
auto source_buffer_length = buffer_length * sources;
|
|
|
|
auto source_merge_buffer_length = sizeof(void*) * sources;
|
|
|
|
|
|
|
|
//TODO: Move this out of the loop?
|
|
|
|
{
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2019-10-26 01:51:40 +02:00
|
|
|
|
|
|
|
for(size_t index = 0; index < sources; index++) {
|
|
|
|
auto& source = this->_sources[index];
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
if(actual_sources > 0) {
|
|
|
|
if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, frameCount))
|
2019-10-26 01:51:40 +02:00
|
|
|
log_warn(category::audio, tr("failed to merge buffers!"));
|
2020-02-08 16:50:48 +01:00
|
|
|
|
|
|
|
auto volume = this->_volume;
|
|
|
|
if(volume != 1) {
|
|
|
|
auto float_length = this->_channel_count * frameCount;
|
|
|
|
auto data = (float*) output;
|
|
|
|
while(float_length-- > 0)
|
|
|
|
*data++ *= volume;
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2020-02-08 16:50:48 +01:00
|
|
|
memset(output, 0, this->_channel_count * sizeof(float) * frameCount);
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
void AudioOutput::set_device(const std::shared_ptr<AudioDevice> &device) {
|
|
|
|
lock_guard lock(this->device_lock);
|
|
|
|
if(this->device == device) return;
|
2019-10-26 01:51:40 +02:00
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
this->close_device();
|
|
|
|
this->device = device;
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
void AudioOutput::close_device() {
|
|
|
|
lock_guard lock(this->device_lock);
|
|
|
|
if(this->_playback) {
|
|
|
|
this->_playback->remove_source(this);
|
|
|
|
this->_playback->stop_if_possible();
|
|
|
|
this->_playback.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this->device = nullptr;
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
bool AudioOutput::playback(std::string& error) {
|
|
|
|
lock_guard lock(this->device_lock);
|
|
|
|
if(!this->device) {
|
|
|
|
error = "invalid device handle";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if(this->_playback) return true;
|
2019-10-26 01:51:40 +02:00
|
|
|
|
2020-02-08 16:50:48 +01:00
|
|
|
this->_playback = this->device->playback();
|
|
|
|
if(!this->_playback) {
|
|
|
|
error = "failed to allocate memory";
|
|
|
|
return false;
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|
2020-02-08 16:50:48 +01:00
|
|
|
|
|
|
|
this->_playback->register_source(this);
|
|
|
|
return this->_playback->start(error);
|
2019-10-26 01:51:40 +02:00
|
|
|
}
|