#include "AudioOutput.h" #include "AudioMerger.h" #include "../logger.h" #include #include #include 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 &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 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; } int AudioOutput::_audio_callback(const void *a, void *b, unsigned long c, const PaStreamCallbackTimeInfo* d, PaStreamCallbackFlags e, void *_ptr_audio_output) { return reinterpret_cast(_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; } }