#include "./AudioOutput.h" #include "./AudioMerger.h" #include "./AudioResampler.h" #include "../logger.h" #include #include #include using namespace std; using namespace tc; using namespace tc::audio; void AudioOutputSource::clear() { this->buffer.clear(); this->buffering = true; } ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) { 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; if(this->on_read) this->on_read(); return written; /* return the written samples */ } ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) { 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; } 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; } 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 AudioOutput::create_source() { auto result = shared_ptr(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 &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; } void AudioOutput::fill_buffer(void *output, size_t frameCount, size_t channels) { const auto local_frame_count = this->_resampler ? this->_resampler-> lock_guard buffer_lock(this->buffer_lock); if(this->_volume <= 0) { for(auto& source : this->_sources) source->pop_samples(nullptr, frameCount); memset(output, 0, sizeof(frameCount) * channels * sizeof(float)); return; } 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) { /* 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; } } for(size_t index = 0; index < sources; index++) { auto& source = this->_sources[index]; 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); } } } } } if(actual_sources > 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 volume = this->_volume; if(volume != 1) { auto float_length = this->_channel_count * frameCount; auto data = (float*) output; while(float_length-- > 0) *data++ *= volume; } } else { memset(output, 0, this->_channel_count * sizeof(float) * frameCount); } } void AudioOutput::set_device(const std::shared_ptr &device) { lock_guard lock(this->device_lock); if(this->device == device) return; this->close_device(); this->device = device; } 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; } 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; this->_playback = this->device->playback(); if(!this->_playback) { error = "failed to allocate memory"; return false; } this->_playback->register_source(this); return this->_playback->start(error); }