Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/cast/congestion_control/congestion_control.h" | 5 #include "media/cast/congestion_control/congestion_control.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "media/cast/cast_config.h" | 8 #include "media/cast/cast_config.h" |
| 9 #include "media/cast/cast_defines.h" | 9 #include "media/cast/cast_defines.h" |
| 10 | 10 |
| 11 namespace media { | 11 namespace media { |
| 12 namespace cast { | 12 namespace cast { |
| 13 | 13 |
| 14 static const int64 kCongestionControlMinChangeIntervalMs = 10; | 14 // This means that we *try* to keep our buffer 90% empty. |
|
Alpha Left Google
2014/06/10 22:20:39
Where is this "buffer"? Please elaborate. I think
hubbe
2014/06/11 00:04:15
Done.
| |
| 15 static const int64 kCongestionControlMaxChangeIntervalMs = 100; | 15 // If it is less full, we increase the bandwidth, if it is more |
| 16 // we decrease the bandwidth. Making this smaller makes the | |
| 17 // congestion control more aggressive. | |
| 18 static const double kTargetEmptyBufferFraction = 0.9; | |
| 16 | 19 |
| 17 // At 10 ms RTT TCP Reno would ramp 1500 * 8 * 100 = 1200 Kbit/s. | 20 // This is the size of our history. Larger values makes the |
| 18 // NACK is sent after a maximum of 10 ms. | 21 // congestion control adapt slower. |
| 19 static const int kCongestionControlMaxBitrateIncreasePerMillisecond = 1200; | 22 static const size_t kHistorySize = 100; |
| 20 | 23 |
| 21 static const int64 kMaxElapsedTimeMs = kCongestionControlMaxChangeIntervalMs; | 24 CongestionControl::FrameStats::FrameStats() : frame_size(0) { |
| 25 } | |
| 22 | 26 |
| 23 CongestionControl::CongestionControl(base::TickClock* clock, | 27 CongestionControl::CongestionControl(base::TickClock* clock, |
| 24 float congestion_control_back_off, | |
| 25 uint32 max_bitrate_configured, | 28 uint32 max_bitrate_configured, |
| 26 uint32 min_bitrate_configured, | 29 uint32 min_bitrate_configured, |
| 27 uint32 start_bitrate) | 30 size_t max_unacked_frames) |
| 28 : clock_(clock), | 31 : clock_(clock), |
| 29 congestion_control_back_off_(congestion_control_back_off), | |
| 30 max_bitrate_configured_(max_bitrate_configured), | 32 max_bitrate_configured_(max_bitrate_configured), |
| 31 min_bitrate_configured_(min_bitrate_configured), | 33 min_bitrate_configured_(min_bitrate_configured), |
| 32 bitrate_(start_bitrate) { | 34 last_frame_stats_(static_cast<uint32>(-1)), |
| 33 DCHECK_GT(congestion_control_back_off, 0.0f) << "Invalid config"; | 35 last_acked_frame_(static_cast<uint32>(-1)), |
| 34 DCHECK_LT(congestion_control_back_off, 1.0f) << "Invalid config"; | 36 last_encoded_frame_(static_cast<uint32>(-1)), |
| 37 history_size_(max_unacked_frames + kHistorySize), | |
| 38 acked_bits_in_history_(0) { | |
| 35 DCHECK_GE(max_bitrate_configured, min_bitrate_configured) << "Invalid config"; | 39 DCHECK_GE(max_bitrate_configured, min_bitrate_configured) << "Invalid config"; |
| 36 DCHECK_GE(max_bitrate_configured, start_bitrate) << "Invalid config"; | 40 frame_stats_.resize(2); |
| 37 DCHECK_GE(start_bitrate, min_bitrate_configured) << "Invalid config"; | 41 base::TimeTicks now = clock->NowTicks(); |
| 42 frame_stats_[0].ack_time = now; | |
| 43 frame_stats_[0].sent_time = now; | |
| 44 frame_stats_[1].ack_time = now; | |
| 45 DCHECK(!frame_stats_[0].ack_time.is_null()); | |
| 38 } | 46 } |
| 39 | 47 |
| 40 CongestionControl::~CongestionControl() {} | 48 CongestionControl::~CongestionControl() {} |
| 41 | 49 |
| 42 bool CongestionControl::OnAck(base::TimeDelta rtt, uint32* new_bitrate) { | 50 void CongestionControl::UpdateRTT(base::TimeDelta rtt) { |
| 43 base::TimeTicks now = clock_->NowTicks(); | 51 rtt_ = base::TimeDelta::FromSecondsD( |
| 44 | 52 (rtt_.InSecondsF() * 7 + rtt.InSecondsF()) / 8); |
| 45 // First feedback? | |
| 46 if (time_last_increase_.is_null()) { | |
| 47 time_last_increase_ = now; | |
| 48 time_last_decrease_ = now; | |
| 49 return false; | |
| 50 } | |
| 51 // Are we at the max bitrate? | |
| 52 if (max_bitrate_configured_ == bitrate_) | |
| 53 return false; | |
| 54 | |
| 55 // Make sure RTT is never less than 1 ms. | |
| 56 rtt = std::max(rtt, base::TimeDelta::FromMilliseconds(1)); | |
| 57 | |
| 58 base::TimeDelta elapsed_time = | |
| 59 std::min(now - time_last_increase_, | |
| 60 base::TimeDelta::FromMilliseconds(kMaxElapsedTimeMs)); | |
| 61 base::TimeDelta change_interval = std::max( | |
| 62 rtt, | |
| 63 base::TimeDelta::FromMilliseconds(kCongestionControlMinChangeIntervalMs)); | |
| 64 change_interval = std::min( | |
| 65 change_interval, | |
| 66 base::TimeDelta::FromMilliseconds(kCongestionControlMaxChangeIntervalMs)); | |
| 67 | |
| 68 // Have enough time have passed? | |
| 69 if (elapsed_time < change_interval) | |
| 70 return false; | |
| 71 | |
| 72 time_last_increase_ = now; | |
| 73 | |
| 74 // One packet per RTT multiplied by the elapsed time fraction. | |
| 75 // 1500 * 8 * (1000 / rtt_ms) * (elapsed_time_ms / 1000) => | |
| 76 // 1500 * 8 * elapsed_time_ms / rtt_ms. | |
| 77 uint32 bitrate_increase = | |
| 78 (1500 * 8 * elapsed_time.InMilliseconds()) / rtt.InMilliseconds(); | |
| 79 uint32 max_bitrate_increase = | |
| 80 kCongestionControlMaxBitrateIncreasePerMillisecond * | |
| 81 elapsed_time.InMilliseconds(); | |
| 82 bitrate_increase = std::min(max_bitrate_increase, bitrate_increase); | |
| 83 *new_bitrate = std::min(bitrate_increase + bitrate_, max_bitrate_configured_); | |
| 84 bitrate_ = *new_bitrate; | |
| 85 return true; | |
| 86 } | 53 } |
| 87 | 54 |
| 88 bool CongestionControl::OnNack(base::TimeDelta rtt, uint32* new_bitrate) { | 55 // Calculate how much "dead air" there is between two frames. |
| 89 base::TimeTicks now = clock_->NowTicks(); | 56 base::TimeDelta CongestionControl::DeadTime(const FrameStats& a, |
| 57 const FrameStats& b) { | |
| 58 if (b.sent_time > a.ack_time) { | |
| 59 return b.sent_time - a.ack_time; | |
| 60 } else { | |
| 61 return base::TimeDelta(); | |
| 62 } | |
| 63 } | |
| 90 | 64 |
| 91 // First feedback? | 65 double CongestionControl::CalculateSafeBitrate() { |
| 92 if (time_last_decrease_.is_null()) { | 66 double transmit_time = |
| 93 time_last_increase_ = now; | 67 (GetFrameStats(last_acked_frame_)->ack_time - |
| 94 time_last_decrease_ = now; | 68 frame_stats_.front().sent_time - dead_time_in_history_).InSecondsF(); |
| 95 return false; | 69 |
| 70 if (acked_bits_in_history_ == 0 || transmit_time <= 0.0) { | |
| 71 return min_bitrate_configured_; | |
| 96 } | 72 } |
| 97 base::TimeDelta elapsed_time = | 73 return acked_bits_in_history_ / transmit_time; |
|
Alpha Left Google
2014/06/10 22:20:39
What happens if transmit_time is very close to zer
hubbe
2014/06/11 00:04:14
It's pretty unlikely, except possibly for the firs
Alpha Left Google
2014/06/11 01:16:28
Okay.
| |
| 98 std::min(now - time_last_decrease_, | 74 } |
| 99 base::TimeDelta::FromMilliseconds(kMaxElapsedTimeMs)); | |
| 100 base::TimeDelta change_interval = std::max( | |
| 101 rtt, | |
| 102 base::TimeDelta::FromMilliseconds(kCongestionControlMinChangeIntervalMs)); | |
| 103 change_interval = std::min( | |
| 104 change_interval, | |
| 105 base::TimeDelta::FromMilliseconds(kCongestionControlMaxChangeIntervalMs)); | |
| 106 | 75 |
| 107 // Have enough time have passed? | 76 CongestionControl::FrameStats* CongestionControl::GetFrameStats( |
| 108 if (elapsed_time < change_interval) | 77 uint32 frame_id) { |
| 109 return false; | 78 int32 offset = static_cast<int32>(frame_id - last_frame_stats_); |
| 79 DCHECK_LT(offset, 100); | |
|
Alpha Left Google
2014/06/10 22:20:39
Should be kHistorySize instead of 100.
hubbe
2014/06/11 00:04:15
Done.
| |
| 80 if (offset > 0) { | |
| 81 frame_stats_.resize(frame_stats_.size() + offset); | |
| 82 last_frame_stats_ += offset; | |
| 83 offset = 0; | |
| 84 } | |
| 85 while (frame_stats_.size() > history_size_) { | |
| 86 DCHECK_GT(frame_stats_.size(), 1UL); | |
| 87 DCHECK(!frame_stats_[0].ack_time.is_null()); | |
| 88 acked_bits_in_history_ -= frame_stats_[0].frame_size; | |
| 89 dead_time_in_history_ -= DeadTime(frame_stats_[0], frame_stats_[1]); | |
| 90 DCHECK_GE(acked_bits_in_history_, 0UL); | |
| 91 VLOG(2) << "DT: " << dead_time_in_history_.InSecondsF(); | |
| 92 DCHECK_GE(dead_time_in_history_.InSecondsF(), 0.0); | |
| 93 frame_stats_.pop_front(); | |
| 94 } | |
| 95 offset += frame_stats_.size() - 1; | |
| 96 if (offset < 0) { | |
| 97 return NULL; | |
| 98 } | |
| 99 DCHECK_LT(offset, static_cast<int32>(frame_stats_.size())); | |
|
Alpha Left Google
2014/06/10 22:20:38
This should be an if statement to prevent out of b
hubbe
2014/06/11 00:04:15
I don't think so. If an out-of-bounds indexing hap
Alpha Left Google
2014/06/11 01:16:28
If |offset| is trivial then I think CHECK would be
hubbe
2014/06/11 07:11:47
Done.
| |
| 100 return &frame_stats_[offset]; | |
| 101 } | |
| 110 | 102 |
| 111 time_last_decrease_ = now; | 103 void CongestionControl::AckFrame(uint32 frame_id, base::TimeTicks when) { |
| 112 time_last_increase_ = now; | 104 FrameStats* frame_stats = GetFrameStats(last_acked_frame_); |
| 105 while (IsNewerFrameId(frame_id, last_acked_frame_)) { | |
| 106 FrameStats* last_frame_stats = frame_stats; | |
| 107 last_acked_frame_++; | |
| 108 frame_stats = GetFrameStats(last_acked_frame_); | |
| 109 DCHECK(frame_stats); | |
| 110 frame_stats->ack_time = when; | |
| 111 acked_bits_in_history_ += frame_stats->frame_size; | |
| 112 dead_time_in_history_ += DeadTime(*last_frame_stats, *frame_stats); | |
| 113 } | |
| 114 } | |
| 113 | 115 |
| 114 *new_bitrate = | 116 void CongestionControl::SendFrameToTransport(uint32 frame_id, |
| 115 std::max(static_cast<uint32>(bitrate_ * congestion_control_back_off_), | 117 size_t frame_size, |
| 116 min_bitrate_configured_); | 118 base::TimeTicks when) { |
| 119 last_encoded_frame_ = frame_id; | |
| 120 FrameStats* frame_stats = GetFrameStats(frame_id); | |
| 121 DCHECK(frame_stats); | |
| 122 frame_stats->frame_size = frame_size; | |
| 123 frame_stats->sent_time = when; | |
| 124 } | |
| 117 | 125 |
| 118 bitrate_ = *new_bitrate; | 126 base::TimeTicks CongestionControl::EstimatedAckTime(uint32 frame_id, |
| 119 return true; | 127 double bitrate) { |
| 128 FrameStats* frame_stats = GetFrameStats(frame_id); | |
| 129 DCHECK(frame_stats); | |
| 130 if (frame_stats->ack_time.is_null()) { | |
| 131 DCHECK(frame_stats->frame_size) << "frame_id: " << frame_id; | |
| 132 base::TimeTicks ret = EstimatedSendingTime(frame_id, bitrate); | |
| 133 ret += base::TimeDelta::FromSecondsD(frame_stats->frame_size / bitrate); | |
| 134 ret += rtt_; | |
| 135 base::TimeTicks now = clock_->NowTicks(); | |
| 136 if (ret < now) { | |
| 137 // Compromise: We estimated that this frame should have been acked by | |
| 138 // now, but it has in fact not been acked. We assume that some of the | |
| 139 // information has been sent and received, but not all of it. For now | |
| 140 // we simply assume half. | |
| 141 return now + (now - ret) / 2; | |
|
Alpha Left Google
2014/06/10 22:20:39
I don't think this is doing what the comments sugg
hubbe
2014/06/11 00:04:15
Changed the comment instead.
On 2014/06/10 22:20:
| |
| 142 } else { | |
| 143 return ret; | |
| 144 } | |
| 145 } else { | |
| 146 return frame_stats->ack_time; | |
| 147 } | |
| 148 } | |
| 149 | |
| 150 base::TimeTicks CongestionControl::EstimatedSendingTime(uint32 frame_id, | |
| 151 double bitrate) { | |
| 152 FrameStats* frame_stats = GetFrameStats(frame_id); | |
|
Alpha Left Google
2014/06/10 22:20:39
We should be able to get this from the pacer, why
hubbe
2014/06/11 00:04:15
Sending time is when we start sending data for a f
| |
| 153 DCHECK(frame_stats); | |
| 154 base::TimeTicks ret = EstimatedAckTime(frame_id - 1, bitrate) - rtt_; | |
| 155 if (frame_stats->sent_time.is_null()) { | |
|
Alpha Left Google
2014/06/10 22:20:38
How is this possible?
hubbe
2014/06/11 00:04:15
It hasn't been sent yet.
Note: "sent time" is when
| |
| 156 // Not sent yet, but we can't start sending it in the past. | |
| 157 return std::max(ret, clock_->NowTicks()); | |
| 158 } else { | |
| 159 return std::max(ret, frame_stats->sent_time); | |
|
Alpha Left Google
2014/06/10 22:20:39
This assumes there is a black box that sends the n
hubbe
2014/06/11 00:04:15
In simulation, it works even with high latencies.
| |
| 160 } | |
| 161 } | |
| 162 | |
| 163 uint32 CongestionControl::GetBitrate(base::TimeTicks playout_time, | |
| 164 base::TimeDelta playout_delay) { | |
| 165 double safe_bitrate = CalculateSafeBitrate(); | |
| 166 // Estimate when we might start sending the next frame. | |
| 167 base::TimeDelta time_to_catch_up = | |
| 168 playout_time - | |
| 169 EstimatedSendingTime(last_encoded_frame_ + 1, safe_bitrate); | |
| 170 | |
| 171 double empty_buffer_fraction = | |
| 172 time_to_catch_up.InSecondsF() / playout_delay.InSecondsF(); | |
| 173 empty_buffer_fraction = std::min(empty_buffer_fraction, 1.0); | |
| 174 empty_buffer_fraction = std::max(empty_buffer_fraction, 0.0); | |
| 175 | |
| 176 uint32 bits_per_second = static_cast<uint32>( | |
| 177 safe_bitrate * empty_buffer_fraction / kTargetEmptyBufferFraction); | |
| 178 VLOG(3) << " FBR:" << (bits_per_second / 1E6) | |
| 179 << " EBF:" << empty_buffer_fraction | |
| 180 << " SBR:" << (safe_bitrate / 1E6); | |
| 181 bits_per_second = std::max(bits_per_second, min_bitrate_configured_); | |
| 182 bits_per_second = std::min(bits_per_second, max_bitrate_configured_); | |
| 183 return bits_per_second; | |
| 120 } | 184 } |
| 121 | 185 |
| 122 } // namespace cast | 186 } // namespace cast |
| 123 } // namespace media | 187 } // namespace media |
| OLD | NEW |