| // Copyright 2024 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "quiche/quic/moqt/moqt_bitrate_adjuster.h" |
| |
| #include <cstdlib> |
| #include <optional> |
| |
| #include "quiche/quic/core/quic_bandwidth.h" |
| #include "quiche/quic/core/quic_time.h" |
| #include "quiche/quic/moqt/moqt_messages.h" |
| #include "quiche/common/platform/api/quiche_bug_tracker.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| #include "quiche/web_transport/web_transport.h" |
| |
| namespace moqt { |
| |
| namespace { |
| |
| using ::quic::QuicBandwidth; |
| using ::quic::QuicTime; |
| using ::quic::QuicTimeDelta; |
| |
| } // namespace |
| |
| void MoqtBitrateAdjuster::Start() { |
| if (start_time_.IsInitialized()) { |
| QUICHE_BUG(MoqtBitrateAdjuster_double_init) |
| << "MoqtBitrateAdjuster::Start() called more than once."; |
| return; |
| } |
| start_time_ = clock_->Now(); |
| outstanding_objects_.emplace( |
| /*max_out_of_order_objects=*/parameters_.max_out_of_order_objects); |
| } |
| |
| void MoqtBitrateAdjuster::OnObjectAckReceived( |
| Location location, QuicTimeDelta delta_from_deadline) { |
| if (!start_time_.IsInitialized() || !outstanding_objects_.has_value()) { |
| return; |
| } |
| |
| // Update the state. |
| int reordering_delta = outstanding_objects_->OnObjectAcked(location); |
| |
| // Decide whether to act based on the latest signal. |
| if (!ShouldUseAckAsActionSignal(location)) { |
| return; |
| } |
| if (ShouldAttemptAdjustingDown(reordering_delta, delta_from_deadline)) { |
| AttemptAdjustingDown(); |
| } |
| } |
| |
| bool MoqtBitrateAdjuster::ShouldUseAckAsActionSignal(Location location) { |
| // Allow for some time to pass for the connection to reach the point at which |
| // the rate adaptation signals can become useful. |
| const QuicTime earliest_action_time = start_time_ + parameters_.initial_delay; |
| const bool too_early_in_the_connection = clock_->Now() < earliest_action_time; |
| |
| // Ignore out-of-order acks for the purpose of deciding whether to adjust up |
| // or down. Generally, if an ack is out of order, the bitrate adjuster has |
| // already reacted to the later object appropriately. |
| const bool is_out_of_order_ack = location < last_acked_object_; |
| last_acked_object_ = location; |
| |
| return !too_early_in_the_connection && !is_out_of_order_ack; |
| } |
| |
| bool MoqtBitrateAdjuster::ShouldAttemptAdjustingDown( |
| int reordering_delta, quic::QuicTimeDelta delta_from_deadline) const { |
| const bool has_exceeded_max_out_of_order = |
| reordering_delta > parameters_.max_out_of_order_objects; |
| QUICHE_DLOG_IF(INFO, has_exceeded_max_out_of_order) |
| << "Adjusting connection down due to reordering, delta: " |
| << reordering_delta; |
| |
| const bool time_delta_too_close = |
| delta_from_deadline < parameters_.adjust_down_threshold * time_window_; |
| QUICHE_DLOG_IF(INFO, time_delta_too_close) |
| << "Adjusting connection down due to object arriving too late, time " |
| "delta: " |
| << delta_from_deadline; |
| |
| return has_exceeded_max_out_of_order || time_delta_too_close; |
| } |
| |
| void MoqtBitrateAdjuster::AttemptAdjustingDown() { |
| webtransport::SessionStats stats = session_->GetSessionStats(); |
| QuicBandwidth target_bandwidth = |
| parameters_.target_bitrate_multiplier_down * |
| QuicBandwidth::FromBitsPerSecond(stats.estimated_send_rate_bps); |
| QUICHE_DLOG(INFO) << "Adjusting the bitrate down to " << target_bandwidth; |
| SuggestNewBitrate(target_bandwidth, BitrateAdjustmentType::kDown); |
| } |
| |
| void MoqtBitrateAdjuster::OnObjectAckSupportKnown( |
| std::optional<quic::QuicTimeDelta> time_window) { |
| if (!time_window.has_value() || *time_window <= QuicTimeDelta::Zero()) { |
| QUICHE_DLOG(WARNING) |
| << "OBJECT_ACK not supported; bitrate adjustments will not work."; |
| return; |
| } |
| time_window_ = *time_window; |
| Start(); |
| } |
| |
| bool ShouldIgnoreBitrateAdjustment(quic::QuicBandwidth new_bitrate, |
| BitrateAdjustmentType type, |
| quic::QuicBandwidth old_bitrate, |
| float min_change) { |
| const float min_change_bps = old_bitrate.ToBitsPerSecond() * min_change; |
| const float change_bps = |
| new_bitrate.ToBitsPerSecond() - old_bitrate.ToBitsPerSecond(); |
| if (std::abs(change_bps) < min_change_bps) { |
| return true; |
| } |
| |
| switch (type) { |
| case moqt::BitrateAdjustmentType::kDown: |
| if (new_bitrate >= old_bitrate) { |
| return true; |
| } |
| break; |
| case moqt::BitrateAdjustmentType::kUp: |
| if (old_bitrate >= new_bitrate) { |
| return true; |
| } |
| break; |
| } |
| return false; |
| } |
| |
| void MoqtBitrateAdjuster::SuggestNewBitrate(quic::QuicBandwidth bitrate, |
| BitrateAdjustmentType type) { |
| adjustable_->ConsiderAdjustingBitrate(bitrate, type); |
| trace_recorder_.RecordTargetBitrateSet(bitrate); |
| } |
| |
| void MoqtBitrateAdjuster::OnNewObjectEnqueued(Location location) { |
| if (!start_time_.IsInitialized() || !outstanding_objects_.has_value()) { |
| return; |
| } |
| outstanding_objects_->OnObjectAdded(location); |
| } |
| |
| } // namespace moqt |