#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; if(buffer)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; if(buffer)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(samples - written)) goto load_buffer; if(buffer)memset(buffer, 0, (samples - written) * sizeof(float) * this->channel_count); 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() { free(this->source_buffer); free(this->source_merge_buffer); free(this->resample_overhead_buffer); this->source_merge_buffer = nullptr; this->source_buffer = nullptr; this->resample_overhead_buffer = nullptr; this->source_merge_buffer_length = 0; this->source_buffer_length = 0; this->resample_overhead_buffer_length = 0; this->resample_overhead_samples = 0; } void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t channels) { if(channels != this->_channel_count) { log_critical(category::audio, tr("Channel count miss match (output)! Fixme!")); return; } const auto local_frame_count = this->_resampler ? this->_resampler->input_size(out_frame_count) : out_frame_count; void* const original_output{output}; if(this->resample_overhead_samples > 0) { const auto samples_to_write = this->resample_overhead_samples > out_frame_count ? out_frame_count : this->resample_overhead_samples; const auto byte_length = samples_to_write * sizeof(float) * channels; if(output) memcpy(output, this->resample_overhead_buffer, byte_length); if(samples_to_write == out_frame_count) { this->resample_overhead_samples -= samples_to_write; memcpy(this->resample_overhead_buffer, (char*) this->resample_overhead_buffer + byte_length, this->resample_overhead_samples * this->_channel_count * sizeof(float)); return; } else { this->resample_overhead_samples = 0; output = (char*) output + byte_length; out_frame_count -= samples_to_write; } } if(!original_output) { for(auto& source : this->_sources) source->pop_samples(nullptr, local_frame_count); return; } else if(this->_volume <= 0) { for(auto& source : this->_sources) source->pop_samples(nullptr, local_frame_count); memset(output, 0, local_frame_count * channels * sizeof(float)); return; } const size_t local_buffer_length = local_frame_count * 4 * this->_channel_count; const size_t out_buffer_length = out_frame_count * 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 */ const auto required_source_buffer_length = (out_buffer_length > local_buffer_length ? out_buffer_length : local_buffer_length) * sources; /* ensure enough space for later resample */ const auto required_source_merge_buffer_length = sizeof(void*) * sources; { if(this->source_buffer_length < required_source_buffer_length || !this->source_buffer) { if(this->source_buffer) free(this->source_buffer); this->source_buffer = malloc(required_source_buffer_length); this->source_buffer_length = required_source_buffer_length; } if(this->source_merge_buffer_length < required_source_merge_buffer_length || !this->source_merge_buffer) { if (this->source_merge_buffer) free(this->source_merge_buffer); this->source_merge_buffer = (void **) malloc(required_source_merge_buffer_length); this->source_merge_buffer_length = required_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 + (local_buffer_length * index); auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], local_frame_count); if(written_frames != local_frame_count) { 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, (local_frame_count - written_frames) * this->_channel_count * 4); } } } } else goto clear_buffer_exit; } if(actual_sources > 0) { if(local_frame_count == out_frame_count) { if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, local_frame_count)) log_warn(category::audio, tr("failed to merge buffers!")); } else { if(!merge::merge_n_sources(this->source_buffer, this->source_merge_buffer, sources, this->_channel_count, local_frame_count)) log_warn(category::audio, tr("failed to merge buffers!")); /* this->source_buffer could hold the amount of resampled data (checked above) */ auto resampled_samples = this->_resampler->process(this->source_buffer, this->source_buffer, local_frame_count); if(resampled_samples != out_frame_count) { if(resampled_samples > out_frame_count) { const auto diff_length = resampled_samples - out_frame_count; const auto overhead_buffer_offset = this->resample_overhead_samples * sizeof(float) * this->_channel_count; const auto diff_byte_length = diff_length * sizeof(float) * this->_channel_count; if(this->resample_overhead_buffer_length < diff_byte_length + overhead_buffer_offset) { this->resample_overhead_buffer_length = diff_byte_length + overhead_buffer_offset; auto new_buffer = malloc(this->resample_overhead_buffer_length); if(this->resample_overhead_buffer) memcpy(new_buffer, this->resample_overhead_buffer, overhead_buffer_offset); free(this->resample_overhead_buffer); this->resample_overhead_buffer = new_buffer; } memcpy( (char*) this->resample_overhead_buffer + overhead_buffer_offset, (char*) this->source_buffer + out_frame_count * sizeof(float) * this->_channel_count, diff_byte_length ); this->resample_overhead_samples += diff_length; } else { log_warn(category::audio, tr("Resampled samples does not match requested sampeles: {} <> {}"), resampled_samples, out_frame_count); } } memcpy(output, this->source_buffer, out_frame_count * sizeof(float) * this->_channel_count); } /* lets apply the volume */ auto volume = this->_volume; if(volume != 1) { auto float_length = this->_channel_count * out_frame_count; auto data = (float*) output; while(float_length-- > 0) *data++ *= volume; } } else { clear_buffer_exit: memset(output, 0, this->_channel_count * sizeof(float) * out_frame_count); } } void AudioOutput::set_device(const std::shared_ptr &new_device) { lock_guard lock(this->device_lock); if(this->device == new_device) return; this->close_device(); this->device = new_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->_resampler = nullptr; 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; } if(this->_playback->sample_rate() != this->sample_rate()) { this->_resampler = std::make_unique(this->sample_rate(), this->_playback->sample_rate(), this->channel_count()); if(!this->_resampler->valid()) { error = "failed to allocate a resampler"; this->_playback = nullptr; return false; } } this->_playback->register_source(this); return this->_playback->start(error); }