From a2c6cba7f4cb2f02918782d724bb94dac8bb9457 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 21 Mar 2021 22:39:10 +0100 Subject: [PATCH] Improved the fade in and fade out effect --- .../src/audio/AudioMerger.cpp | 16 +- .../src/audio/AudioOutput.cpp | 670 ++++++++++-------- .../serverconnection/src/audio/AudioOutput.h | 122 ++-- .../src/audio/AudioReframer.h | 4 +- .../src/audio/AudioResampler.h | 10 +- .../serverconnection/src/audio/AudioSamples.h | 39 +- .../src/audio/js/AudioOutputStream.cpp | 23 +- .../src/audio/sounds/SoundPlayer.cpp | 13 +- .../src/connection/audio/VoiceClient.cpp | 45 +- .../src/connection/audio/VoiceClient.h | 9 +- 10 files changed, 539 insertions(+), 412 deletions(-) diff --git a/native/serverconnection/src/audio/AudioMerger.cpp b/native/serverconnection/src/audio/AudioMerger.cpp index 338b426..4c30be4 100644 --- a/native/serverconnection/src/audio/AudioMerger.cpp +++ b/native/serverconnection/src/audio/AudioMerger.cpp @@ -51,18 +51,22 @@ bool merge::merge_n_sources(void *dest, void **srcs, size_t src_length, size_t c srcs++; src_length--; - if(src_length == 0) - return false; + if(src_length == 0) { + return false; + } } - if(srcs[0] != dest) - memcpy(dest, srcs[0], channels * samples * 4); + if(srcs[0] != dest) { + memcpy(dest, srcs[0], channels * samples * 4); + } + srcs++; src_length--; while(src_length > 0) { /* only invoke is srcs is not null! */ - if(srcs[0] && !merge::merge_sources(dest, srcs[0], dest, channels, samples)) - return false; + if(srcs[0] && !merge::merge_sources(dest, srcs[0], dest, channels, samples)) { + return false; + } srcs++; src_length--; diff --git a/native/serverconnection/src/audio/AudioOutput.cpp b/native/serverconnection/src/audio/AudioOutput.cpp index 63336fb..8898410 100644 --- a/native/serverconnection/src/audio/AudioOutput.cpp +++ b/native/serverconnection/src/audio/AudioOutput.cpp @@ -12,242 +12,309 @@ using namespace tc; using namespace tc::audio; void AudioOutputSource::clear() { + std::lock_guard buffer_lock{this->buffer_mutex}; this->buffer.clear(); - this->buffering = true; - this->fade_in_start = this->buffer.write_ptr(); + this->buffer_state = buffer_state::buffering; + this->fadeout_samples_left = 0; } -void AudioOutputSource::do_fade_out(size_t pop_count) { - if(this->will_buffer_in != -1) return; - - _test_for_fade: - const auto samples_left = this->current_latency(); - if(samples_left < this->fadeout_sample_length + pop_count) { - if(auto fn = this->on_underflow; fn && fn(0)) - goto _test_for_fade; - - auto total_samples = std::min(samples_left, this->fadeout_sample_length); - if(total_samples == 0) return; //TODO Test against min_buffered_samples - - auto wptr = (float*) this->buffer.calculate_backward_write_ptr(total_samples * this->channel_count * sizeof(float)); - for(size_t index{0}; index <= total_samples; index++) { - const auto offset = (float) ((float) index / (float) total_samples); - const auto volume = log10f(offset) / -2.71828182845904f; - for(int channel{0}; channel < this->channel_count; channel++) - *wptr++ *= volume; - } - - log_trace(category::audio, tr("Will buffer due to fade out ({} | {})"), total_samples, *(float*) this->buffer.write_ptr()); - this->will_buffer_in = total_samples; - } -} - -void AudioOutputSource::do_fade_in() { - if(!this->fade_in_start) - return; - - const auto samples_available = this->current_latency(); - auto wptr = (float*) this->fade_in_start; - auto total_samples = std::min(samples_available, this->fadeout_sample_length); - if(total_samples == 0) { - log_trace(category::audio, tr("Ignoring fade in 0: {} {}"), samples_available, this->fadeout_sample_length); +void AudioOutputSource::apply_fadeout() { + const auto samples_available = this->currently_buffered_samples(); + auto fade_samples = std::min(samples_available, this->fadeout_frame_samples_); + if(!fade_samples) { + this->fadeout_samples_left = 0; return; } - for(size_t index{0}; index < total_samples; index++) { - const auto offset = (float) ((float) index / (float) total_samples); - const auto volume = log10f(1 - offset) / -2.71828182845904f; - for(int channel{0}; channel < this->channel_count; channel++) - *wptr++ *= volume; - } + const auto sample_byte_size = this->channel_count * sizeof(float) * fade_samples; + assert(this->buffer.fill_count() >= sample_byte_size); + auto write_ptr = (float*) ((char*) this->buffer.read_ptr() + (this->buffer.fill_count() - sample_byte_size)); - log_trace(category::audio, tr("Fade in to new buffer ({})"), total_samples); + for(size_t index{0}; index < fade_samples; index++) { + const auto offset = (float) ((float) (index + 1) / (float) fade_samples); + const auto volume = std::min(log10f(offset) / -2.71828182845904f, 1.f); - this->fade_in_start = nullptr; -} - -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; - - if(this->buffering && available_samples < this->min_buffered_samples) return -2; - this->do_fade_in(); - this->do_fade_out(samples); /* will also call for underflow */ - //log_trace(category::audio, tr("Min: {}, Max: {}, Current: {}, Buffering: {} Required: {}, left: {}, will buffer in {}"), this->min_buffered_samples, this->max_buffered_samples, available_samples, this->buffering, samples, (int) available_samples - samples, this->will_buffer_in); - - if(this->will_buffer_in > 0) { - if(samples > (size_t) this->will_buffer_in) { - samples = this->will_buffer_in; - this->buffering = true; - this->fade_in_start = this->buffer.calculate_advanced_write_ptr(samples * sizeof(float) * this->channel_count); - this->will_buffer_in = -1; - log_trace(category::audio, tr("Start buffering due to fade out. Fade in ptr {}"), (void*) this->fade_in_start); - } else { - this->will_buffer_in -= samples; - } - } else { - 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); - - if(this->on_read) - this->on_read(); - - 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((char*) buffer + written_bytes, 0, (samples - written) * sizeof(float) * this->channel_count); - - this->buffering = true; - this->fade_in_start = this->buffer.write_ptr(); - log_trace(category::audio, tr("Start buffering due to underflow."), (void*) this->fade_in_start); - this->will_buffer_in = -1; - - if(this->on_read) - this->on_read(); - return written; /* return the written samples */ -} - -ssize_t AudioOutputSource::enqueue_silence(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; - memset(this->buffer.write_ptr(), 0, byte_length); - this->buffer.advance_write_ptr(byte_length); - return samples; - } else { - const auto byte_length = free_samples * sizeof(float) * this->channel_count; - memset(this->buffer.write_ptr(), 0, 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; - } - - this->fade_in_start = this->buffer.write_ptr(); /* so we fade in from silence */ - return enqueued; -} - -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}; - { //FIXME: This only works for two channels! - 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++; + for(int channel{0}; channel < this->channel_count; channel++) { + *write_ptr++ *= volume; } } - this->buffer.advance_write_ptr(enqueued * this->channel_count * sizeof(float)); - if(enqueued == samples) return enqueued; - 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; + this->fadeout_samples_left = fade_samples; } -AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) { } +void AudioOutputSource::apply_fadein() { + assert(this->currently_buffered_samples() >= this->fadeout_samples_left); + const auto samples_available = this->currently_buffered_samples(); + auto fade_samples = std::min(samples_available - this->fadeout_samples_left, this->fadein_frame_samples_); + if(!fade_samples) { + return; + } + + /* + * Note: We're using the read_ptr() here in order to correctly apply the effect. + * This isn't really best practice but works. + */ + auto write_ptr = (float*) this->buffer.read_ptr() + this->fadeout_samples_left * this->channel_count; + for(size_t index{0}; index < fade_samples; index++) { + const auto offset = (float) ((float) (index + 1) / (float) fade_samples); + const auto volume = std::min(log10f(1 - offset) / -2.71828182845904f, 1.f); + + for(int channel{0}; channel < this->channel_count; channel++) { + *write_ptr++ *= volume; + } + } +} + +bool AudioOutputSource::pop_samples(void *target_buffer, size_t target_sample_count) { + std::unique_lock buffer_lock{this->buffer_mutex}; + auto result = this->pop_samples_(target_buffer, target_sample_count); + buffer_lock.unlock(); + + if(auto callback{this->on_read}; callback) { + callback(); + } + return result; +} + +bool AudioOutputSource::pop_samples_(void *target_buffer, size_t target_sample_count) { + switch(this->buffer_state) { + case buffer_state::fadeout: { + /* Write as much we can */ + const auto write_samples = std::min(this->fadeout_samples_left, target_sample_count); + const auto write_byte_size = write_samples * this->channel_count * sizeof(float); + memcpy(target_buffer, this->buffer.read_ptr(), write_byte_size); + this->buffer.advance_read_ptr(write_byte_size); + + /* Fill the rest with silence */ + const auto empty_samples = target_sample_count - write_samples; + const auto empty_byte_size = empty_samples * this->channel_count * sizeof(float); + memset((char*) target_buffer + write_byte_size, 0, empty_byte_size); + + this->fadeout_samples_left -= write_samples; + if(!this->fadeout_samples_left) { + log_trace(category::audio, tr("{} Successfully replayed fadeout sequence."), (void*) this); + this->buffer_state = buffer_state::buffering; + } + return true; + } + + case buffer_state::playing: { + const auto buffered_samples = this->currently_buffered_samples(); + if(buffered_samples < target_sample_count + this->fadeout_frame_samples_) { + const auto missing_samples = target_sample_count + this->fadeout_frame_samples_ - buffered_samples; + if(auto callback{this->on_underflow}; callback) { + if(callback(missing_samples)) { + /* We've been filled up again. Trying again to fill the output buffer. */ + return this->pop_samples(target_buffer, target_sample_count); + } + } + + /* + * When consuming target_sample_count amount samples of our buffer we could not + * apply the fadeout effect any more. Instead we're applying it now and returning to buffering state. + */ + this->apply_fadeout(); + + /* Write the rest of unmodified buffer */ + const auto write_samples = buffered_samples - this->fadeout_samples_left; + assert(write_samples <= target_sample_count); + const auto write_byte_size = write_samples * this->channel_count * sizeof(float); + memcpy(target_buffer, this->buffer.read_ptr(), write_byte_size); + this->buffer.advance_read_ptr(write_byte_size); + + log_trace(category::audio, tr("{} Starting stream fadeout. Requested samples {}, Buffered samples: {}, Fadeout frame samples: {}, Returned normal samples: {}"), + (void*) this, target_sample_count, buffered_samples, this->fadeout_frame_samples_, write_samples + ); + + this->buffer_state = buffer_state::fadeout; + if(write_samples < target_sample_count) { + /* Fill the rest of the buffer with the fadeout content */ + this->pop_samples((char*) target_buffer + write_byte_size, target_sample_count - write_samples); + } + } else { + /* We can just normally copy the buffer */ + const auto write_byte_size = target_sample_count * this->channel_count * sizeof(float); + memcpy(target_buffer, this->buffer.read_ptr(), write_byte_size); + this->buffer.advance_read_ptr(write_byte_size); + } + + return true; + } + + case buffer_state::buffering: + /* Nothing to replay */ + return false; + + default: + assert(false); + return false; + } +} + +ssize_t AudioOutputSource::enqueue_samples(const void *source_buffer, size_t sample_count) { + std::lock_guard buffer_lock{this->buffer_mutex}; + return this->enqueue_samples_(source_buffer, sample_count); +} + +ssize_t AudioOutputSource::enqueue_samples_(const void *source_buffer, size_t sample_count) { + switch(this->buffer_state) { + case buffer_state::fadeout: + case buffer_state::buffering: { + assert(this->currently_buffered_samples() >= this->fadeout_samples_left); + assert(this->min_buffered_samples_ >= this->currently_buffered_samples() - this->fadeout_samples_left); + const auto missing_samples = this->min_buffered_samples_ - (this->currently_buffered_samples() - this->fadeout_samples_left); + const auto write_sample_count = std::min(missing_samples, sample_count); + const auto write_byte_size = write_sample_count * this->channel_count * sizeof(float); + + assert(write_sample_count <= this->max_supported_buffering()); + memcpy(this->buffer.write_ptr(), source_buffer, write_byte_size); + this->buffer.advance_write_ptr(write_byte_size); + + if(sample_count < missing_samples) { + /* we still need to buffer */ + return sample_count; + } + + /* + * Even though we still have fadeout samples left we don't declare them as such since we've already fulled + * our future buffer. + */ + this->fadeout_samples_left = 0; + + /* buffering finished */ + log_trace(category::audio, tr("{} Finished buffering {} samples. Fading them in."), (void*) this, this->min_buffered_samples_); + this->apply_fadein(); + this->buffer_state = buffer_state::playing; + if(sample_count > missing_samples) { + /* we've more data to write */ + return this->enqueue_samples((const char*) source_buffer + write_byte_size, sample_count - missing_samples) + write_sample_count; + } else { + return write_sample_count; + } + } + + case buffer_state::playing: { + const auto buffered_samples = this->currently_buffered_samples(); + + const auto write_sample_count = std::min(this->max_supported_buffering() - buffered_samples, sample_count); + const auto write_byte_size = write_sample_count * this->channel_count * sizeof(float); + + memcpy(this->buffer.write_ptr(), source_buffer, write_byte_size); + this->buffer.advance_write_ptr(write_byte_size); + + if(write_sample_count < sample_count) { + if(auto callback{this->on_overflow}; callback) { + callback(sample_count - write_sample_count); + } + + 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: + /* FIXME: This implementation is wrong! */ + this->buffer.advance_read_ptr(this->buffer.fill_count() / 2); + break; + + case overflow_strategy::ignore: + break; + } + } + + return write_sample_count; + } + + default: + assert(false); + return false; + } +} + +constexpr static auto kMaxStackBuffer{1024 * 8 * sizeof(float)}; +ssize_t AudioOutputSource::enqueue_samples_no_interleave(const void *source_buffer, size_t samples) { + if(this->channel_count == 1) { + return this->enqueue_samples(source_buffer, samples); + } else if(this->channel_count == 2) { + const auto buffer_byte_size = samples * this->channel_count * sizeof(float); + if(buffer_byte_size > kMaxStackBuffer) { + /* We can't convert to interleave */ + return 0; + } + + uint8_t stack_buffer[kMaxStackBuffer]; + { + auto src_buffer = (const float*) source_buffer; + auto target_buffer = (float*) stack_buffer; + + auto samples_to_write = samples; + while (samples_to_write-- > 0) { + *target_buffer = *src_buffer; + *(target_buffer + 1) = *(src_buffer + samples); + + target_buffer += 2; + src_buffer++; + } + } + + return this->enqueue_samples(stack_buffer, samples); + } else { + /* TODO: Generalize to interleave algo */ + return 0; + } +} + +bool AudioOutputSource::set_max_buffered_samples(size_t samples) { + samples = std::max(samples, (size_t) this->fadein_frame_samples_); + if(samples > this->max_supported_buffering()) { + return false; + } + + std::lock_guard buffer_lock{this->buffer_mutex}; + if(samples < this->min_buffered_samples_) { + return false; + } + + this->max_buffered_samples_ = samples; + return true; +} + +bool AudioOutputSource::set_min_buffered_samples(size_t samples) { + samples = std::max(samples, (size_t) this->fadein_frame_samples_); + + std::lock_guard buffer_lock{this->buffer_mutex}; + if(samples > this->max_buffered_samples_) { + return false; + } + + this->min_buffered_samples_ = samples; + switch(this->buffer_state) { + case buffer_state::fadeout: + case buffer_state::buffering: { + assert(this->currently_buffered_samples() >= this->fadeout_samples_left); + const auto buffered_samples = this->currently_buffered_samples() - this->fadeout_samples_left; + if(buffered_samples > this->min_buffered_samples_) { + log_trace(category::audio, tr("{} Finished buffering {} samples (due to min buffered sample reduce). Fading them in."), (void*) this, this->min_buffered_samples_); + this->apply_fadein(); + this->buffer_state = buffer_state::playing; + } + + return true; + } + + case buffer_state::playing: + return true; + + default: + assert(false); + return false; + } +} + +AudioOutput::AudioOutput(size_t channels, size_t rate) : channel_count_(channels), sample_rate_(rate) { } AudioOutput::~AudioOutput() { this->close_device(); @@ -255,25 +322,14 @@ AudioOutput::~AudioOutput() { } std::shared_ptr AudioOutput::create_source(ssize_t buf) { - auto result = shared_ptr(new AudioOutputSource(this, this->_channel_count, this->_sample_rate, buf)); + auto result = std::shared_ptr(new AudioOutputSource(this->channel_count_, this->sample_rate_, buf)); { - lock_guard lock(this->sources_lock); - this->_sources.push_back(result); + std::lock_guard source_lock{this->sources_mutex}; + 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); @@ -290,50 +346,67 @@ void AudioOutput::cleanup_buffers() { } void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_channels) { - if(out_channels != this->_channel_count) { - log_critical(category::audio, tr("Channel count miss match (output)! Expected: {} Received: {}. Fixme!"), this->_channel_count, out_channels); + if(out_channels != this->channel_count_) { + log_critical(category::audio, tr("Channel count miss match (output)! Expected: {} Received: {}. Fixme!"), this->channel_count_, out_channels); return; } - auto local_frame_count = this->_resampler ? this->_resampler->input_size(out_frame_count) : out_frame_count; + 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) * out_channels; - if(output) memcpy(output, this->resample_overhead_buffer, byte_length); + 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)); + 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; - local_frame_count -= this->_resampler ? this->_resampler->input_size(samples_to_write) : samples_to_write; + local_frame_count -= this->resampler_ ? this->resampler_->input_size(samples_to_write) : samples_to_write; } } if(!original_output) { - for(auto& source : this->_sources) + this->sources_.erase(std::remove_if(this->sources_.begin(), this->sources_.end(), [&](const std::weak_ptr& weak_source) { + auto source = weak_source.lock(); + if(!source) { + return true; + } + source->pop_samples(nullptr, local_frame_count); + return false; + }), this->sources_.end()); return; - } else if(this->_volume <= 0) { - for(auto& source : this->_sources) + } else if(this->volume_modifier <= 0) { + this->sources_.erase(std::remove_if(this->sources_.begin(), this->sources_.end(), [&](const std::weak_ptr& weak_source) { + auto source = weak_source.lock(); + if(!source) { + return true; + } + source->pop_samples(nullptr, local_frame_count); + return false; + }), this->sources_.end()); + memset(output, 0, local_frame_count * out_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; + 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; + size_t actual_sources; { - lock_guard lock(this->sources_lock); - sources = this->_sources.size(); - actual_sources = sources; + lock_guard sources_lock{this->sources_mutex}; + sources = this->sources_.size(); if(sources > 0) { /* allocate the required space */ @@ -343,59 +416,69 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_c { if(this->source_buffer_length < required_source_buffer_length || !this->source_buffer) { - if(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) + 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]; + size_t index{0}; + this->sources_.erase(std::remove_if(this->sources_.begin(), this->sources_.end(), [&](const std::weak_ptr& weak_source) { + auto source = weak_source.lock(); + if(!source) { + return true; + } 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); - } + if(!source->pop_samples(this->source_merge_buffer[index], local_frame_count)) { + this->source_merge_buffer[index] = nullptr; + return false; } - } - } else - goto clear_buffer_exit; + + index++; + return false; + }), this->sources_.end()); + actual_sources = index; + } 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)) + /* Output */ + 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)) + 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); + auto resampled_samples = this->resampler_->process(this->source_buffer, this->source_buffer, local_frame_count); if(resampled_samples <= 0) { log_warn(category::audio, tr("Failed to resample audio data for client ({})")); goto clear_buffer_exit; } + if(resampled_samples != out_frame_count) { if((size_t) resampled_samples > out_frame_count) { const auto diff_length = resampled_samples - out_frame_count; log_warn(category::audio, tr("enqueuing {} samples"), diff_length); - 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; + 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; @@ -407,22 +490,23 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_c } memcpy( (char*) this->resample_overhead_buffer + overhead_buffer_offset, - (char*) this->source_buffer + out_frame_count * sizeof(float) * this->_channel_count, + (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: {} <> {}. Sampled from {} to {}"), resampled_samples, out_frame_count, this->_resampler->input_rate(), this->_resampler->output_rate()); + log_warn(category::audio, tr("Resampled samples does not match requested sampeles: {} <> {}. Sampled from {} to {}"), resampled_samples, out_frame_count, this->resampler_->input_rate(), this->resampler_->output_rate()); } } - memcpy(output, this->source_buffer, out_frame_count * sizeof(float) * this->_channel_count); + + memcpy(output, this->source_buffer, out_frame_count * sizeof(float) * this->channel_count_); } /* lets apply the volume */ - audio::apply_gain(output, this->_channel_count, out_frame_count, this->_volume); + audio::apply_gain(output, this->channel_count_, out_frame_count, this->volume_modifier); } else { clear_buffer_exit: - memset(output, 0, this->_channel_count * sizeof(float) * out_frame_count); + memset(output, 0, this->channel_count_ * sizeof(float) * out_frame_count); return; } } @@ -437,13 +521,13 @@ void AudioOutput::set_device(const std::shared_ptr &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(); + if(this->playback_) { + this->playback_->remove_source(this); + this->playback_->stop_if_possible(); + this->playback_.reset(); } - this->_resampler = nullptr; + this->resampler_ = nullptr; this->device = nullptr; } @@ -453,23 +537,23 @@ bool AudioOutput::playback(std::string& error) { error = "invalid device handle"; return false; } - if(this->_playback) return true; + if(this->playback_) return true; - this->_playback = this->device->playback(); - if(!this->_playback) { + 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()) { + 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; + this->playback_ = nullptr; return false; } } - this->_playback->register_source(this); - return this->_playback->start(error); + this->playback_->register_source(this); + return this->playback_->start(error); } \ No newline at end of file diff --git a/native/serverconnection/src/audio/AudioOutput.h b/native/serverconnection/src/audio/AudioOutput.h index c51575e..7916e9d 100644 --- a/native/serverconnection/src/audio/AudioOutput.h +++ b/native/serverconnection/src/audio/AudioOutput.h @@ -25,61 +25,96 @@ namespace tc::audio { discard_input }; } + class AudioOutputSource { friend class AudioOutput; public: - AudioOutput* handle; + size_t const channel_count; + size_t const sample_rate; - size_t const channel_count = 0; - size_t const sample_rate = 0; - - [[nodiscard]] inline size_t max_supported_latency() const { + /** + * The maximum amount of samples which could be buffered. + * @return + */ + [[nodiscard]] inline size_t max_supported_buffering() const { return this->buffer.capacity() / this->channel_count / sizeof(float); } - [[nodiscard]] inline size_t max_latency() const { - const auto max_samples = this->max_supported_latency(); - if(this->max_buffered_samples && this->max_buffered_samples <= max_samples) return this->max_buffered_samples; + [[nodiscard]] inline size_t max_buffering() const { + const auto max_samples = this->max_supported_buffering(); + if(this->max_buffered_samples_ && this->max_buffered_samples_ <= max_samples) { + return this->max_buffered_samples_; + } return max_samples; } - /* samples which needs to be played*/ - [[nodiscard]] inline size_t current_latency() const { + /** + * Sample count which still need to be replayed before newly emplaced buffers will be played. + * @return + */ + [[nodiscard]] inline size_t currently_buffered_samples() const { return this->buffer.fill_count() / this->channel_count / sizeof(float); } - bool buffering{true}; - char* fade_in_start{nullptr}; - ssize_t will_buffer_in{-1}; - size_t min_buffered_samples{0}; - size_t max_buffered_samples{0}; - size_t fadeout_sample_length{360}; + [[nodiscard]] inline size_t min_buffered_samples() const { return this->min_buffered_samples_; } + [[nodiscard]] inline size_t max_buffered_samples() const { return this->max_buffered_samples_; } - overflow_strategy::value overflow_strategy = overflow_strategy::discard_buffer_half; + bool set_min_buffered_samples(size_t /* target samples */); + bool set_max_buffered_samples(size_t /* target samples */); + + overflow_strategy::value overflow_strategy{overflow_strategy::discard_buffer_half}; /* if it returns true then the it means that the buffer has been refilled, we have to test again */ std::function on_underflow; std::function on_overflow; std::function on_read; /* will be invoked after sample read, e.g. for buffer fullup */ - void clear(); - ssize_t pop_samples(void* /* output buffer */, size_t /* sample count */); - ssize_t enqueue_silence(size_t /* sample count */); + void clear(); ssize_t enqueue_samples(const void * /* input buffer */, size_t /* sample count */); ssize_t enqueue_samples_no_interleave(const void * /* input buffer */, size_t /* sample count */); + + /* Consume N samples */ + bool pop_samples(void* /* output buffer */, size_t /* sample count */); private: - AudioOutputSource(AudioOutput* handle, size_t channel_count, size_t sample_rate, ssize_t max_buffer_sample_count = -1) : - handle(handle), channel_count(channel_count), sample_rate(sample_rate), - buffer{max_buffer_sample_count == -1 ? channel_count * sample_rate * sizeof(float) : max_buffer_sample_count * channel_count * sizeof(float)} { + enum struct buffer_state { + /* Awaiting enough samples to replay and apply the fadein effect */ + buffering, + /* We have encountered a buffer underflow. Applying fadeout effect and changing state to buffering. */ + fadeout, + /* We're just normally replaying audio */ + playing + }; + + AudioOutputSource(size_t channel_count, size_t sample_rate, ssize_t max_buffer_sample_count = -1) : + channel_count{channel_count}, sample_rate{sample_rate}, + buffer{max_buffer_sample_count == -1 ? channel_count * sample_rate * sizeof(float) : max_buffer_sample_count * channel_count * sizeof(float)} + { this->clear(); } - void do_fade_out(size_t /* pop count */); - void do_fade_in(); - + std::recursive_mutex buffer_mutex{}; + enum buffer_state buffer_state{buffer_state::buffering}; tc::ring_buffer buffer; + + size_t min_buffered_samples_{0}; + size_t max_buffered_samples_{0}; + + /* + * Fadeout and fadein properties. + * The fadeout sample count should always be lower than the fade in sample count. + */ + size_t fadein_frame_samples_{960}; + size_t fadeout_frame_samples_{(size_t) (960 * .9)}; + size_t fadeout_samples_left{0}; + + /* Methods bellow do not acquire the buffer_mutex mutex */ + ssize_t enqueue_samples_(const void * /* input buffer */, size_t /* sample count */); + bool pop_samples_(void* /* output buffer */, size_t /* sample count */); + + void apply_fadeout(); + void apply_fadein(); }; class AudioOutput : public AudioDevicePlayback::Source { @@ -92,36 +127,35 @@ namespace tc::audio { void close_device(); std::shared_ptr current_device() { return this->device; } - std::deque> sources() { - std::lock_guard lock(this->sources_lock); - return this->_sources; + std::deque> sources() { + std::lock_guard sources_lock{this->sources_mutex}; + return this->sources_; } std::shared_ptr create_source(ssize_t /* buffer sample size */ = -1); - void delete_source(const std::shared_ptr& /* source */); - inline size_t channel_count() { return this->_channel_count; } - inline size_t sample_rate() { return this->_sample_rate; } + [[nodiscard]] inline size_t channel_count() const { return this->channel_count_; } + [[nodiscard]] inline size_t sample_rate() const { return this->sample_rate_; } - inline float volume() { return this->_volume; } - inline void set_volume(float value) { this->_volume = value; } + [[nodiscard]] inline float volume() const { return this->volume_modifier; } + inline void set_volume(float value) { this->volume_modifier = value; } private: void fill_buffer(void *, size_t out_frame_count, size_t out_channels) override; - size_t const _channel_count; - size_t const _sample_rate; + size_t const channel_count_; + size_t const sample_rate_; - std::mutex sources_lock; - std::deque> _sources; + std::mutex sources_mutex{}; + std::deque> sources_{}; - std::recursive_mutex device_lock; + std::recursive_mutex device_lock{}; std::shared_ptr device{nullptr}; - std::shared_ptr _playback{nullptr}; - std::unique_ptr _resampler{nullptr}; + std::shared_ptr playback_{nullptr}; + std::unique_ptr resampler_{nullptr}; /* only access there buffers within the audio loop! */ - void* source_buffer = nullptr; - void** source_merge_buffer = nullptr; + void* source_buffer{nullptr}; + void** source_merge_buffer{nullptr}; void* resample_overhead_buffer{nullptr}; size_t resample_overhead_buffer_length{0}; @@ -131,6 +165,6 @@ namespace tc::audio { size_t source_merge_buffer_length = 0; void cleanup_buffers(); - float _volume = 1.f; + float volume_modifier{1.f}; }; } \ No newline at end of file diff --git a/native/serverconnection/src/audio/AudioReframer.h b/native/serverconnection/src/audio/AudioReframer.h index a493054..b0c1a27 100644 --- a/native/serverconnection/src/audio/AudioReframer.h +++ b/native/serverconnection/src/audio/AudioReframer.h @@ -11,8 +11,8 @@ namespace tc::audio { void process(const void* /* source */, size_t /* samples */); - inline size_t channels() { return this->_channels; } - inline size_t frame_size() { return this->_frame_size; } + inline size_t channels() const { return this->_channels; } + inline size_t frame_size() const { return this->_frame_size; } std::function on_frame; private: diff --git a/native/serverconnection/src/audio/AudioResampler.h b/native/serverconnection/src/audio/AudioResampler.h index 66023d8..0127cd0 100644 --- a/native/serverconnection/src/audio/AudioResampler.h +++ b/native/serverconnection/src/audio/AudioResampler.h @@ -16,16 +16,16 @@ namespace tc::audio { AudioResampler(size_t /* input rate */, size_t /* output rate */, size_t /* channels */); virtual ~AudioResampler(); - [[nodiscard]] inline size_t channels() { return this->_channels; } - [[nodiscard]] inline size_t input_rate() { return this->_input_rate; } - [[nodiscard]] inline size_t output_rate() { return this->_output_rate; } + [[nodiscard]] inline size_t channels() const { return this->_channels; } + [[nodiscard]] inline size_t input_rate() const { return this->_input_rate; } + [[nodiscard]] inline size_t output_rate() const { return this->_output_rate; } - [[nodiscard]] inline long double io_ratio() { return (long double) this->_output_rate / (long double) this->_input_rate; } + [[nodiscard]] inline long double io_ratio() const { return (long double) this->_output_rate / (long double) this->_input_rate; } [[nodiscard]] inline size_t estimated_output_size(size_t input_length) { if(!this->soxr_handle) return input_length; /* no resembling needed */ return (size_t) ceill(this->io_ratio() * input_length + *soxr_num_clips(this->soxr_handle)) + 1; } - [[nodiscard]] inline size_t input_size(size_t output_length) { + [[nodiscard]] inline size_t input_size(size_t output_length) const { return (size_t) ceill((long double) this->_input_rate / (long double) this->_output_rate * output_length); } diff --git a/native/serverconnection/src/audio/AudioSamples.h b/native/serverconnection/src/audio/AudioSamples.h index 6ed93ed..600a71b 100644 --- a/native/serverconnection/src/audio/AudioSamples.h +++ b/native/serverconnection/src/audio/AudioSamples.h @@ -3,38 +3,35 @@ #include #include -namespace tc { - namespace audio { - +namespace tc::audio { #ifdef WIN32 - #pragma pack(push,1) - #define __attribute__packed_1 + #pragma pack(push,1) + #define __attribute__packed_1 #else - #define __attribute__packed_1 __attribute__((packed, aligned(1))) + #define __attribute__packed_1 __attribute__((packed, aligned(1))) #endif - /* Every sample is a float (4byte) */ - struct __attribute__packed_1 SampleBuffer { - static constexpr size_t HEAD_LENGTH = 4; + /* Every sample is a float (4byte) */ + struct __attribute__packed_1 SampleBuffer { + static constexpr size_t HEAD_LENGTH = 4; - uint16_t sample_size; - uint16_t sample_index; + uint16_t sample_size; + uint16_t sample_index; - char sample_data[ + char sample_data[ #ifndef WIN32 - 0 + 0 #else - 1 /* windows does not allow zero sized arrays */ + 1 /* windows does not allow zero sized arrays */ #endif - ]; + ]; - static std::shared_ptr allocate(uint8_t /* channels */, uint16_t /* samples */); - }; + static std::shared_ptr allocate(uint8_t /* channels */, uint16_t /* samples */); + }; #ifndef WIN32 - static_assert(sizeof(SampleBuffer) == 4, "Invalid SampleBuffer packaging!"); + static_assert(sizeof(SampleBuffer) == 4, "Invalid SampleBuffer packaging!"); #else - #pragma pack(pop) - static_assert(sizeof(SampleBuffer) == 5, "Invalid SampleBuffer packaging!"); + #pragma pack(pop) + static_assert(sizeof(SampleBuffer) == 5, "Invalid SampleBuffer packaging!"); #endif - } } \ No newline at end of file diff --git a/native/serverconnection/src/audio/js/AudioOutputStream.cpp b/native/serverconnection/src/audio/js/AudioOutputStream.cpp index 607f3db..8b66354 100644 --- a/native/serverconnection/src/audio/js/AudioOutputStream.cpp +++ b/native/serverconnection/src/audio/js/AudioOutputStream.cpp @@ -50,10 +50,6 @@ AudioOutputStreamWrapper::~AudioOutputStreamWrapper() { void AudioOutputStreamWrapper::drop_stream() { if(this->_own_handle) { - auto handle = this->_own_handle->handle; - if(handle) { - handle->delete_source(this->_own_handle); - } this->_own_handle->on_underflow = nullptr; this->_own_handle->on_overflow = nullptr; } @@ -112,7 +108,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_clear) { NAN_METHOD(AudioOutputStreamWrapper::_deleted) { auto client = ObjectWrap::Unwrap(info.Holder()); - info.GetReturnValue().Set(!client->_own_handle || !client->_own_handle->handle); + info.GetReturnValue().Set(!client->_own_handle); } NAN_METHOD(AudioOutputStreamWrapper::_delete) { @@ -121,10 +117,11 @@ NAN_METHOD(AudioOutputStreamWrapper::_delete) { } ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr& handle, void *source, size_t samples, bool interleaved) { - if(interleaved) - return handle->enqueue_samples(source, samples); - else - return handle->enqueue_samples_no_interleave(source, samples); + if(interleaved) { + return handle->enqueue_samples(source, samples); + } else { + return handle->enqueue_samples_no_interleave(source, samples); + } } NAN_METHOD(AudioOutputStreamWrapper::_write_data) { @@ -223,7 +220,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_latency) { return; } - info.GetReturnValue().Set((float) handle->min_buffered_samples / (float) handle->sample_rate); + info.GetReturnValue().Set((float) handle->min_buffered_samples() / (float) handle->sample_rate); } NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) { @@ -240,7 +237,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) { return; } - handle->min_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); + handle->set_min_buffered_samples((size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0))); } NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) { @@ -252,7 +249,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) { return; } - info.GetReturnValue().Set((float) handle->max_latency() / (float) handle->sample_rate); + info.GetReturnValue().Set((float) handle->max_buffering() / (float) handle->sample_rate); } NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) { @@ -269,7 +266,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) { return; } - handle->max_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); + handle->set_max_buffered_samples((size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0))); } NAN_METHOD(AudioOutputStreamWrapper::_flush_buffer) { diff --git a/native/serverconnection/src/audio/sounds/SoundPlayer.cpp b/native/serverconnection/src/audio/sounds/SoundPlayer.cpp index fa4eeac..dd269eb 100644 --- a/native/serverconnection/src/audio/sounds/SoundPlayer.cpp +++ b/native/serverconnection/src/audio/sounds/SoundPlayer.cpp @@ -72,8 +72,7 @@ namespace tc::audio::sounds { } state_{PLAYER_STATE_UNSET}; void finalize(bool is_destructor_call) { - if(this->output_source && global_audio_output) - global_audio_output->delete_source(this->output_source); + this->output_source = nullptr; if(this->file_handle) this->file_handle = nullptr; if(auto buffer{std::exchange(this->cache_buffer, nullptr)}; buffer) @@ -160,7 +159,7 @@ namespace tc::audio::sounds { this->state_ = PLAYER_STATE_UNSET; return; } - auto filled_samples = this->output_source->current_latency(); + auto filled_samples = this->output_source->currently_buffered_samples(); } void initialize_playback() { @@ -170,8 +169,8 @@ namespace tc::audio::sounds { const auto max_buffer = (size_t) ceil(global_audio_output->sample_rate() * kBufferChunkTimespan * 3); this->output_source = global_audio_output->create_source(max_buffer); this->output_source->overflow_strategy = audio::overflow_strategy::ignore; - this->output_source->max_buffered_samples = max_buffer; - this->output_source->min_buffered_samples = (size_t) floor(this->output_source->sample_rate * 0.04); + this->output_source->set_max_buffered_samples(max_buffer); + this->output_source->set_min_buffered_samples((size_t) floor(this->output_source->sample_rate * 0.04)); auto weak_this = this->weak_from_this(); this->output_source->on_underflow = [weak_this](size_t sample_count){ @@ -208,8 +207,8 @@ namespace tc::audio::sounds { [[nodiscard]] inline bool could_enqueue_next_buffer() const { if(!this->output_source) return false; - const auto current_size = this->output_source->current_latency(); - const auto max_size = this->output_source->max_buffered_samples; + const auto current_size = this->output_source->currently_buffered_samples(); + const auto max_size = this->output_source->max_buffered_samples(); if(current_size > max_size) return false; const auto size_left = max_size - current_size; diff --git a/native/serverconnection/src/connection/audio/VoiceClient.cpp b/native/serverconnection/src/connection/audio/VoiceClient.cpp index d1136bb..fb30a57 100644 --- a/native/serverconnection/src/connection/audio/VoiceClient.cpp +++ b/native/serverconnection/src/connection/audio/VoiceClient.cpp @@ -223,7 +223,7 @@ VoiceClient::~VoiceClient() { this->cancel_replay(); /* cleanup all buffers */ if(this->output_source) { this->output_source->on_underflow = nullptr; /* to ensure */ - global_audio_output->delete_source(this->output_source); + this->output_source = nullptr; } } @@ -232,19 +232,21 @@ void VoiceClient::initialize() { audio::initialize([weak_this]{ auto client = weak_this.lock(); - if(!client) return; + if(!client) { + return; + } assert(global_audio_output); client->output_source = global_audio_output->create_source(); client->output_source->overflow_strategy = audio::overflow_strategy::ignore; - client->output_source->max_buffered_samples = (size_t) ceil(client->output_source->sample_rate * 0.5); - client->output_source->min_buffered_samples = (size_t) ceil(client->output_source->sample_rate * 0.04); + client->output_source->set_max_buffered_samples((size_t) ceil(client->output_source->sample_rate * 0.5)); + client->output_source->set_min_buffered_samples((size_t) ceil(client->output_source->sample_rate * 0.04)); const auto client_ptr = &*client; client->output_source->on_underflow = [client_ptr](size_t sample_count){ /* this callback will never be called when the client has been deallocated */ - if(client_ptr->state_ == state::stopping) + if(client_ptr->state_ == state::stopping) { client_ptr->set_state(state::stopped); - else if(client_ptr->state_ != state::stopped) { + } else if(client_ptr->state_ != state::stopped) { if(client_ptr->_last_received_packet + chrono::seconds{1} < chrono::system_clock::now()) { client_ptr->set_state(state::stopped); log_warn(category::audio, tr("Client {} has a audio buffer underflow for {} samples and not received any data for one second. Stopping replay."), client_ptr->client_id_, sample_count); @@ -406,21 +408,25 @@ void VoiceClient::process_packet(uint16_t packet_id, const pipes::buffer_view& b void VoiceClient::cancel_replay() { log_trace(category::voice_connection, tr("Cancel replay for client {}"), this->client_id_); - if(output_source) this->output_source->clear(); + auto output = this->output_source; + if(output) { + output->clear(); + } + this->set_state(state::stopped); audio::decode_event_loop->cancel(static_pointer_cast(this->ref())); auto execute_lock = this->execute_lock(true); - for(auto& codec : this->codec) { - auto head = codec.pending_buffers; + for(auto& codec_entry : this->codec) { + auto head = codec_entry.pending_buffers; while(head) { auto tmp = head->next; delete head; head = tmp; } - codec.pending_buffers = nullptr; - codec.force_replay = nullptr; + codec_entry.pending_buffers = nullptr; + codec_entry.force_replay = nullptr; } } @@ -437,8 +443,9 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch auto timeout = chrono::system_clock::now() + max_time; for(auto& audio_codec : this->codec) { - if(!audio_codec.process_pending) - continue; + if(!audio_codec.process_pending) { + continue; + } unique_lock lock{audio_codec.pending_lock}; do { @@ -568,8 +575,9 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch log_warn(category::audio, tr("Failed to decode lost packets for client {}: {}"), this->_client_id, error); */ auto decoded = this->decode_buffer(audio_codec.codec, replay_head->buffer, true); - if(decoded) - this->output_source->enqueue_samples(decoded->sample_data, decoded->sample_size); + if(decoded) { + this->output_source->enqueue_samples(decoded->sample_data, decoded->sample_size); + } } const auto is_new_audio_stream = this->state_ != state::buffering && this->state_ != state::playing; @@ -716,7 +724,8 @@ std::shared_ptr VoiceClient::decode_buffer(const codec::val } void VoiceClient::event_execute_dropped(const std::chrono::system_clock::time_point &point) { - if(audio_decode_event_dropped.exchange(true)) - //Is not really a warning, it happens all the time and isn't really an issue - ;//log_warn(category::voice_connection, tr("Dropped auto enqueue event execution two or more times in a row for client {}"), this->_client_id); + if(audio_decode_event_dropped.exchange(true)) { + //Is not really a warning, it happens all the time and isn't really an issue + //log_warn(category::voice_connection, tr("Dropped auto enqueue event execution two or more times in a row for client {}"), this->_client_id); + } } diff --git a/native/serverconnection/src/connection/audio/VoiceClient.h b/native/serverconnection/src/connection/audio/VoiceClient.h index e1e8c5a..c6239cf 100644 --- a/native/serverconnection/src/connection/audio/VoiceClient.h +++ b/native/serverconnection/src/connection/audio/VoiceClient.h @@ -69,7 +69,7 @@ namespace tc::connection { void initialize(); - inline uint16_t client_id() { return this->client_id_; } + inline uint16_t client_id() const { return this->client_id_; } void initialize_js_object(); void finalize_js_object(); @@ -153,11 +153,14 @@ namespace tc::connection { std::chrono::system_clock::time_point _last_received_packet; state::value state_ = state::stopped; inline void set_state(state::value value) { - if(value == this->state_) + if(value == this->state_) { return; + } + this->state_ = value; - if(this->on_state_changed) + if(this->on_state_changed) { this->on_state_changed(); + } } std::atomic_bool audio_decode_event_dropped{false};