diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/audio_coding/neteq/decision_logic.cc | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/audio_coding/neteq/decision_logic.cc')
-rw-r--r-- | third_party/libwebrtc/modules/audio_coding/neteq/decision_logic.cc | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/audio_coding/neteq/decision_logic.cc b/third_party/libwebrtc/modules/audio_coding/neteq/decision_logic.cc new file mode 100644 index 0000000000..fd4f2f5a20 --- /dev/null +++ b/third_party/libwebrtc/modules/audio_coding/neteq/decision_logic.cc @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/audio_coding/neteq/decision_logic.h" + +#include <stdio.h> + +#include <cstdint> +#include <memory> +#include <string> + +#include "absl/types/optional.h" +#include "api/neteq/neteq.h" +#include "api/neteq/neteq_controller.h" +#include "modules/audio_coding/neteq/packet_arrival_history.h" +#include "modules/audio_coding/neteq/packet_buffer.h" +#include "rtc_base/checks.h" +#include "rtc_base/experiments/field_trial_parser.h" +#include "rtc_base/experiments/struct_parameters_parser.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { + +namespace { + +constexpr int kPostponeDecodingLevel = 50; +constexpr int kTargetLevelWindowMs = 100; +constexpr int kMaxWaitForPacketMs = 100; +// The granularity of delay adjustments (accelerate/preemptive expand) is 15ms, +// but round up since the clock has a granularity of 10ms. +constexpr int kDelayAdjustmentGranularityMs = 20; +constexpr int kReinitAfterExpandsMs = 1000; + +std::unique_ptr<DelayManager> CreateDelayManager( + const NetEqController::Config& neteq_config) { + DelayManager::Config config; + config.max_packets_in_buffer = neteq_config.max_packets_in_buffer; + config.base_minimum_delay_ms = neteq_config.base_min_delay_ms; + config.Log(); + return std::make_unique<DelayManager>(config, neteq_config.tick_timer); +} + +bool IsTimestretch(NetEq::Mode mode) { + return mode == NetEq::Mode::kAccelerateSuccess || + mode == NetEq::Mode::kAccelerateLowEnergy || + mode == NetEq::Mode::kPreemptiveExpandSuccess || + mode == NetEq::Mode::kPreemptiveExpandLowEnergy; +} + +bool IsCng(NetEq::Mode mode) { + return mode == NetEq::Mode::kRfc3389Cng || + mode == NetEq::Mode::kCodecInternalCng; +} + +bool IsExpand(NetEq::Mode mode) { + return mode == NetEq::Mode::kExpand || mode == NetEq::Mode::kCodecPlc; +} + +} // namespace + +DecisionLogic::Config::Config() { + StructParametersParser::Create( + "enable_stable_delay_mode", &enable_stable_delay_mode, // + "combine_concealment_decision", &combine_concealment_decision, // + "packet_history_size_ms", &packet_history_size_ms, // + "cng_timeout_ms", &cng_timeout_ms, // + "deceleration_target_level_offset_ms", + &deceleration_target_level_offset_ms) + ->Parse(webrtc::field_trial::FindFullName( + "WebRTC-Audio-NetEqDecisionLogicConfig")); + RTC_LOG(LS_INFO) << "NetEq decision logic config:" + << " enable_stable_delay_mode=" << enable_stable_delay_mode + << " combine_concealment_decision=" + << combine_concealment_decision + << " packet_history_size_ms=" << packet_history_size_ms + << " cng_timeout_ms=" << cng_timeout_ms.value_or(-1) + << " deceleration_target_level_offset_ms=" + << deceleration_target_level_offset_ms; +} + +DecisionLogic::DecisionLogic(NetEqController::Config config) + : DecisionLogic(config, + CreateDelayManager(config), + std::make_unique<BufferLevelFilter>()) {} + +DecisionLogic::DecisionLogic( + NetEqController::Config config, + std::unique_ptr<DelayManager> delay_manager, + std::unique_ptr<BufferLevelFilter> buffer_level_filter) + : delay_manager_(std::move(delay_manager)), + buffer_level_filter_(std::move(buffer_level_filter)), + packet_arrival_history_(config_.packet_history_size_ms), + tick_timer_(config.tick_timer), + disallow_time_stretching_(!config.allow_time_stretching), + timescale_countdown_( + tick_timer_->GetNewCountdown(kMinTimescaleInterval + 1)) {} + +DecisionLogic::~DecisionLogic() = default; + +void DecisionLogic::SoftReset() { + packet_length_samples_ = 0; + sample_memory_ = 0; + prev_time_scale_ = false; + timescale_countdown_ = + tick_timer_->GetNewCountdown(kMinTimescaleInterval + 1); + time_stretched_cn_samples_ = 0; + delay_manager_->Reset(); + buffer_level_filter_->Reset(); + packet_arrival_history_.Reset(); +} + +void DecisionLogic::SetSampleRate(int fs_hz, size_t output_size_samples) { + // TODO(hlundin): Change to an enumerator and skip assert. + RTC_DCHECK(fs_hz == 8000 || fs_hz == 16000 || fs_hz == 32000 || + fs_hz == 48000); + sample_rate_khz_ = fs_hz / 1000; + output_size_samples_ = output_size_samples; + packet_arrival_history_.set_sample_rate(fs_hz); +} + +NetEq::Operation DecisionLogic::GetDecision(const NetEqStatus& status, + bool* reset_decoder) { + prev_time_scale_ = prev_time_scale_ && IsTimestretch(status.last_mode); + if (prev_time_scale_) { + timescale_countdown_ = tick_timer_->GetNewCountdown(kMinTimescaleInterval); + } + if (!IsCng(status.last_mode) && + !(config_.combine_concealment_decision && IsExpand(status.last_mode))) { + FilterBufferLevel(status.packet_buffer_info.span_samples); + } + + // Guard for errors, to avoid getting stuck in error mode. + if (status.last_mode == NetEq::Mode::kError) { + if (!status.next_packet) { + return NetEq::Operation::kExpand; + } else { + // Use kUndefined to flag for a reset. + return NetEq::Operation::kUndefined; + } + } + + if (status.next_packet && status.next_packet->is_cng) { + return CngOperation(status); + } + + // Handle the case with no packet at all available (except maybe DTMF). + if (!status.next_packet) { + return NoPacket(status); + } + + // If the expand period was very long, reset NetEQ since it is likely that the + // sender was restarted. + if (!config_.combine_concealment_decision && IsExpand(status.last_mode) && + status.generated_noise_samples > + static_cast<size_t>(kReinitAfterExpandsMs * sample_rate_khz_)) { + *reset_decoder = true; + return NetEq::Operation::kNormal; + } + + if (PostponeDecode(status)) { + return NoPacket(status); + } + + const uint32_t five_seconds_samples = + static_cast<uint32_t>(5000 * sample_rate_khz_); + // Check if the required packet is available. + if (status.target_timestamp == status.next_packet->timestamp) { + return ExpectedPacketAvailable(status); + } + if (!PacketBuffer::IsObsoleteTimestamp(status.next_packet->timestamp, + status.target_timestamp, + five_seconds_samples)) { + return FuturePacketAvailable(status); + } + // This implies that available_timestamp < target_timestamp, which can + // happen when a new stream or codec is received. Signal for a reset. + return NetEq::Operation::kUndefined; +} + +int DecisionLogic::TargetLevelMs() const { + int target_delay_ms = delay_manager_->TargetDelayMs(); + if (!config_.enable_stable_delay_mode) { + target_delay_ms = + std::max(target_delay_ms, + static_cast<int>(packet_length_samples_ / sample_rate_khz_)); + } + return target_delay_ms; +} + +int DecisionLogic::UnlimitedTargetLevelMs() const { + return delay_manager_->UnlimitedTargetLevelMs(); +} + +int DecisionLogic::GetFilteredBufferLevel() const { + return buffer_level_filter_->filtered_current_level(); +} + +absl::optional<int> DecisionLogic::PacketArrived( + int fs_hz, + bool should_update_stats, + const PacketArrivedInfo& info) { + buffer_flush_ = buffer_flush_ || info.buffer_flush; + if (!should_update_stats || info.is_cng_or_dtmf) { + return absl::nullopt; + } + if (info.packet_length_samples > 0 && fs_hz > 0 && + info.packet_length_samples != packet_length_samples_) { + packet_length_samples_ = info.packet_length_samples; + delay_manager_->SetPacketAudioLength(packet_length_samples_ * 1000 / fs_hz); + } + int64_t time_now_ms = tick_timer_->ticks() * tick_timer_->ms_per_tick(); + packet_arrival_history_.Insert(info.main_timestamp, time_now_ms); + if (packet_arrival_history_.size() < 2) { + // No meaningful delay estimate unless at least 2 packets have arrived. + return absl::nullopt; + } + int arrival_delay_ms = + packet_arrival_history_.GetDelayMs(info.main_timestamp, time_now_ms); + bool reordered = + !packet_arrival_history_.IsNewestRtpTimestamp(info.main_timestamp); + delay_manager_->Update(arrival_delay_ms, reordered); + return arrival_delay_ms; +} + +void DecisionLogic::FilterBufferLevel(size_t buffer_size_samples) { + buffer_level_filter_->SetTargetBufferLevel(TargetLevelMs()); + + int time_stretched_samples = time_stretched_cn_samples_; + if (prev_time_scale_) { + time_stretched_samples += sample_memory_; + } + + if (buffer_flush_) { + buffer_level_filter_->SetFilteredBufferLevel(buffer_size_samples); + buffer_flush_ = false; + } else { + buffer_level_filter_->Update(buffer_size_samples, time_stretched_samples); + } + prev_time_scale_ = false; + time_stretched_cn_samples_ = 0; +} + +NetEq::Operation DecisionLogic::CngOperation( + NetEqController::NetEqStatus status) { + // Signed difference between target and available timestamp. + int32_t timestamp_diff = static_cast<int32_t>( + static_cast<uint32_t>(status.generated_noise_samples + + status.target_timestamp) - + status.next_packet->timestamp); + int optimal_level_samp = TargetLevelMs() * sample_rate_khz_; + const int64_t excess_waiting_time_samp = + -static_cast<int64_t>(timestamp_diff) - optimal_level_samp; + + if (excess_waiting_time_samp > optimal_level_samp / 2) { + // The waiting time for this packet will be longer than 1.5 + // times the wanted buffer delay. Apply fast-forward to cut the + // waiting time down to the optimal. + noise_fast_forward_ = rtc::saturated_cast<size_t>(noise_fast_forward_ + + excess_waiting_time_samp); + timestamp_diff = + rtc::saturated_cast<int32_t>(timestamp_diff + excess_waiting_time_samp); + } + + if (timestamp_diff < 0 && status.last_mode == NetEq::Mode::kRfc3389Cng) { + // Not time to play this packet yet. Wait another round before using this + // packet. Keep on playing CNG from previous CNG parameters. + return NetEq::Operation::kRfc3389CngNoPacket; + } else { + // Otherwise, go for the CNG packet now. + noise_fast_forward_ = 0; + return NetEq::Operation::kRfc3389Cng; + } +} + +NetEq::Operation DecisionLogic::NoPacket(NetEqController::NetEqStatus status) { + switch (status.last_mode) { + case NetEq::Mode::kRfc3389Cng: + return NetEq::Operation::kRfc3389CngNoPacket; + case NetEq::Mode::kCodecInternalCng: { + // Stop CNG after a timeout. + if (config_.cng_timeout_ms && + status.generated_noise_samples > + static_cast<size_t>(*config_.cng_timeout_ms * sample_rate_khz_)) { + return NetEq::Operation::kExpand; + } + return NetEq::Operation::kCodecInternalCng; + } + default: + return status.play_dtmf ? NetEq::Operation::kDtmf + : NetEq::Operation::kExpand; + } +} + +NetEq::Operation DecisionLogic::ExpectedPacketAvailable( + NetEqController::NetEqStatus status) { + if (!disallow_time_stretching_ && status.last_mode != NetEq::Mode::kExpand && + !status.play_dtmf) { + if (config_.enable_stable_delay_mode) { + const int playout_delay_ms = GetPlayoutDelayMs(status); + const int low_limit = TargetLevelMs(); + const int high_limit = low_limit + + packet_arrival_history_.GetMaxDelayMs() + + kDelayAdjustmentGranularityMs; + if (playout_delay_ms >= high_limit * 4) { + return NetEq::Operation::kFastAccelerate; + } + if (TimescaleAllowed()) { + if (playout_delay_ms >= high_limit) { + return NetEq::Operation::kAccelerate; + } + if (playout_delay_ms < low_limit) { + return NetEq::Operation::kPreemptiveExpand; + } + } + } else { + const int target_level_samples = TargetLevelMs() * sample_rate_khz_; + const int low_limit = std::max( + target_level_samples * 3 / 4, + target_level_samples - + config_.deceleration_target_level_offset_ms * sample_rate_khz_); + const int high_limit = std::max( + target_level_samples, + low_limit + kDelayAdjustmentGranularityMs * sample_rate_khz_); + + const int buffer_level_samples = + buffer_level_filter_->filtered_current_level(); + if (buffer_level_samples >= high_limit * 4) + return NetEq::Operation::kFastAccelerate; + if (TimescaleAllowed()) { + if (buffer_level_samples >= high_limit) + return NetEq::Operation::kAccelerate; + if (buffer_level_samples < low_limit) + return NetEq::Operation::kPreemptiveExpand; + } + } + } + return NetEq::Operation::kNormal; +} + +NetEq::Operation DecisionLogic::FuturePacketAvailable( + NetEqController::NetEqStatus status) { + // Required packet is not available, but a future packet is. + // Check if we should continue with an ongoing concealment because the new + // packet is too far into the future. + if (config_.combine_concealment_decision || IsCng(status.last_mode)) { + const int buffer_delay_samples = + config_.combine_concealment_decision + ? status.packet_buffer_info.span_samples_wait_time + : status.packet_buffer_info.span_samples; + const int buffer_delay_ms = buffer_delay_samples / sample_rate_khz_; + const int high_limit = TargetLevelMs() + kTargetLevelWindowMs / 2; + const int low_limit = + std::max(0, TargetLevelMs() - kTargetLevelWindowMs / 2); + const bool above_target_delay = buffer_delay_ms > high_limit; + const bool below_target_delay = buffer_delay_ms < low_limit; + if ((PacketTooEarly(status) && !above_target_delay) || + (below_target_delay && !config_.combine_concealment_decision)) { + return NoPacket(status); + } + uint32_t timestamp_leap = + status.next_packet->timestamp - status.target_timestamp; + if (config_.combine_concealment_decision) { + if (timestamp_leap != status.generated_noise_samples) { + // The delay was adjusted, reinitialize the buffer level filter. + buffer_level_filter_->SetFilteredBufferLevel(buffer_delay_samples); + } + } else { + time_stretched_cn_samples_ = + timestamp_leap - status.generated_noise_samples; + } + } else if (IsExpand(status.last_mode) && ShouldContinueExpand(status)) { + return NoPacket(status); + } + + // Time to play the next packet. + switch (status.last_mode) { + case NetEq::Mode::kExpand: + return NetEq::Operation::kMerge; + case NetEq::Mode::kCodecPlc: + case NetEq::Mode::kRfc3389Cng: + case NetEq::Mode::kCodecInternalCng: + return NetEq::Operation::kNormal; + default: + return status.play_dtmf ? NetEq::Operation::kDtmf + : NetEq::Operation::kExpand; + } +} + +bool DecisionLogic::UnderTargetLevel() const { + return buffer_level_filter_->filtered_current_level() < + TargetLevelMs() * sample_rate_khz_; +} + +bool DecisionLogic::PostponeDecode(NetEqController::NetEqStatus status) const { + // Make sure we don't restart audio too soon after CNG or expand to avoid + // running out of data right away again. + const size_t min_buffer_level_samples = + TargetLevelMs() * sample_rate_khz_ * kPostponeDecodingLevel / 100; + const size_t buffer_level_samples = + config_.combine_concealment_decision + ? status.packet_buffer_info.span_samples_wait_time + : status.packet_buffer_info.span_samples; + if (buffer_level_samples >= min_buffer_level_samples) { + return false; + } + // Don't postpone decoding if there is a future DTX packet in the packet + // buffer. + if (status.packet_buffer_info.dtx_or_cng) { + return false; + } + // Continue CNG until the buffer is at least at the minimum level. + if (config_.combine_concealment_decision && IsCng(status.last_mode)) { + return true; + } + // Only continue expand if the mute factor is low enough (otherwise the + // expansion was short enough to not be noticable). Note that the MuteFactor + // is in Q14, so a value of 16384 corresponds to 1. + if (IsExpand(status.last_mode) && status.expand_mutefactor < 16384 / 2) { + return true; + } + return false; +} + +bool DecisionLogic::ReinitAfterExpands( + NetEqController::NetEqStatus status) const { + const uint32_t timestamp_leap = + status.next_packet->timestamp - status.target_timestamp; + return timestamp_leap >= + static_cast<uint32_t>(kReinitAfterExpandsMs * sample_rate_khz_); +} + +bool DecisionLogic::PacketTooEarly(NetEqController::NetEqStatus status) const { + const uint32_t timestamp_leap = + status.next_packet->timestamp - status.target_timestamp; + return timestamp_leap > status.generated_noise_samples; +} + +bool DecisionLogic::MaxWaitForPacket( + NetEqController::NetEqStatus status) const { + return status.generated_noise_samples >= + static_cast<size_t>(kMaxWaitForPacketMs * sample_rate_khz_); +} + +bool DecisionLogic::ShouldContinueExpand( + NetEqController::NetEqStatus status) const { + return !ReinitAfterExpands(status) && !MaxWaitForPacket(status) && + PacketTooEarly(status) && UnderTargetLevel(); +} + +int DecisionLogic::GetPlayoutDelayMs( + NetEqController::NetEqStatus status) const { + uint32_t playout_timestamp = + status.target_timestamp - status.sync_buffer_samples; + return packet_arrival_history_.GetDelayMs( + playout_timestamp, tick_timer_->ticks() * tick_timer_->ms_per_tick()); +} + +} // namespace webrtc |