RFC 8298

Self-Clocked Rate Adaptation for Multimedia

๐Ÿ“… published December 2017, from ietf

RFC 8298: Self-Clocked Rate Adaptation for Multimedia ๐ŸŽ€

okay bestie, this is RFC 8298 ๐Ÿ’– It's published by the IETF (Internet Engineering Task Force) and written by I. Johansson and Z. Sarker from Ericsson AB. It came out in December 2017, it's ISSN 2070-1721, and it's marked as Experimental ๐Ÿงช The doc doesn't say it obsoletes or updates any other RFC, so it stands on its own โœจ


Abstract ๐ŸŒˆ

this memo walks through a rate adaptation algorithm built for conversational media, like interactive video ๐Ÿ“น The solution follows the packet conservation principle and uses a hybrid loss-and-delay-based congestion control algorithm ๐Ÿง  They tested it on simulated Internet bottleneck scenarios AND in a Long Term Evolution (LTE) system simulator, and it reached both low latency and high video throughput ๐Ÿ”ฅ literally iconic.

Status of This Memo ๐Ÿ“‹

this document is not an Internet Standards Track specification ๐Ÿ™…โ€โ™€๏ธ It's getting published so people can examine it, try out experimental implementations, and evaluate it ๐Ÿ”ฌ

It defines an Experimental Protocol for the Internet community. It's a product of the IETF and represents the consensus of the IETF community ๐Ÿซถ It got public review and was approved for publication by the Internet Engineering Steering Group (IESG). Not every IESG-approved document is a candidate for any level of Internet Standard, so see Section 2 of RFC 7841 for the details ๐Ÿ‘€

Info about the current status of this document, any errata, and how to send feedback is at https://www.rfc-editor.org/info/rfc8298 ๐Ÿ’Œ

Copyright (c) 2017 IETF Trust and the persons identified as the document authors. All rights reserved ยฉ๏ธ

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication. Please read these carefully, because they explain your rights and restrictions for this document ๐Ÿ“œ Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions, and they're provided without warranty as described in the Simplified BSD License ๐ŸŽ€

Table of Contents ๐Ÿ—บ๏ธ

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   4
     1.1.  Wireless (LTE) Access Properties  . . . . . . . . . . . .   4
     1.2.  Why is it a self-clocked algorithm? . . . . . . . . . . .   5
   2.  Requirements Language . . . . . . . . . . . . . . . . . . . .   5
   3.  Overview of SCReAM Algorithm  . . . . . . . . . . . . . . . .   6
     3.1.  Network Congestion Control  . . . . . . . . . . . . . . .   8
     3.2.  Sender Transmission Control . . . . . . . . . . . . . . .   9
     3.3.  Media Rate Control  . . . . . . . . . . . . . . . . . . .   9
   4.  Detailed Description of SCReAM  . . . . . . . . . . . . . . .  10
     4.1.  SCReAM Sender . . . . . . . . . . . . . . . . . . . . . .  10
       4.1.1.  Constants and Parameter Values  . . . . . . . . . . .  10
         4.1.1.1.  Constants . . . . . . . . . . . . . . . . . . . .  11
         4.1.1.2.  State Variables . . . . . . . . . . . . . . . . .  12
       4.1.2.  Network Congestion Control  . . . . . . . . . . . . .  14
         4.1.2.1.  Reaction to Packet Loss and ECN . . . . . . . . .  17
         4.1.2.2.  Congestion Window Update  . . . . . . . . . . . .  17
         4.1.2.3.  Competing Flows Compensation  . . . . . . . . . .  20
         4.1.2.4.  Lost Packet Detection . . . . . . . . . . . . . .  22
         4.1.2.5.  Send Window Calculation . . . . . . . . . . . . .  23
         4.1.2.6.  Packet Pacing . . . . . . . . . . . . . . . . . .  24
         4.1.2.7.  Resuming Fast Increase Mode . . . . . . . . . . .  24
         4.1.2.8.  Stream Prioritization . . . . . . . . . . . . . .  24
       4.1.3.  Media Rate Control  . . . . . . . . . . . . . . . . .  25
     4.2.  SCReAM Receiver . . . . . . . . . . . . . . . . . . . . .  28
       4.2.1.  Requirements on Feedback Elements . . . . . . . . . .  28
       4.2.2.  Requirements on Feedback Intensity  . . . . . . . . .  30
   5.  Discussion  . . . . . . . . . . . . . . . . . . . . . . . . .  31
   6.  Suggested Experiments . . . . . . . . . . . . . . . . . . . .  31
   7.  IANA Considerations . . . . . . . . . . . . . . . . . . . . .  32
   8.  Security Considerations . . . . . . . . . . . . . . . . . . .  32
   9.  References  . . . . . . . . . . . . . . . . . . . . . . . . .  33
     9.1.  Normative References  . . . . . . . . . . . . . . . . . .  33
     9.2.  Informative References  . . . . . . . . . . . . . . . . .  34
   Acknowledgements  . . . . . . . . . . . . . . . . . . . . . . . .  36
   Authors' Addresses  . . . . . . . . . . . . . . . . . . . . . . .  36

1. Introduction ๐ŸŒธ

okay so congestion on the Internet happens when the bitrate you're sending is higher than the capacity the path can actually handle ๐Ÿ˜ญ Apps on the Internet have to use congestion control to perform robustly and to avoid congestion collapse, periodt. Interactive real-time communication puts a LOT of demands on the transport, so a robust, efficient rate adaptation for all access types is a big deal, since the transmission channel bandwidth can change over time ๐Ÿ’…

Wireless access like LTE is an integral part of the current Internet, and it makes rate adaptation even more important because the channel bandwidth of a default LTE bearer [QoS-3GPP] can change a lot in a super short time frame ๐Ÿ™ˆ So a rate adaptation solution for interactive real-time media, like WebRTC [RFC7478], should be both quick and able to operate over a large range of channel capacity โœจ

This memo describes Self-Clocked Rate Adaptation for Multimedia (SCReAM) ๐Ÿฆ‹, a solution that implements congestion control for RTP streams [RFC3550]. SCReAM was originally devised for WebRTC, but it can also be used for other applications where congestion control of RTP streams is necessary. It's based on the self-clocking principle of TCP and uses techniques similar to the rate adaptation algorithm based on Low Extra Delay Background Transport (LEDBAT) [RFC6817] ๐Ÿงš

SCReAM isn't totally self-clocked, though, because it adds pacing and a minimum send rate on top ๐Ÿ“ It can take advantage of Explicit Congestion Notification (ECN) in cases where ECN is supported by the network and the hosts. However, ECN is not required for the basic congestion control functionality in SCReAM ๐Ÿซถ

1.1. Wireless (LTE) Access Properties ๐Ÿ“ถ

[WIRELESS-TESTS] describes the complications you can observe in wireless environments ๐Ÿ‘€ Wireless access like LTE typically can't guarantee a given bandwidth, and that's especially true for default bearers. The network throughput can vary a lot, for instance when the wireless terminal is moving around. Even though LTE can support bitrates well above 100 Mbps, there are cases when the available bitrate can be way lower, like during high network load and poor coverage ๐Ÿ˜ฉ Another wrinkle is that the network throughput can drop for short time intervals (e.g., at handover), and these short glitches are super hard to tell apart from more permanent reductions in throughput at first.

Unlike wireline bottlenecks with large statistical multiplexing, you can't just try to hold a given bitrate when congestion is detected and hope other flows will yield ๐Ÿ™ƒ That's because there are generally only a few other flows competing for the same bottleneck. Each user gets their own variable throughput bottleneck, where the throughput depends on things like channel quality, network load, and historical throughput. The bottom line is, if the throughput drops, the sender has no other option than to reduce the bitrate ๐Ÿ’… Once the radio scheduler has reduced the resource allocation for a bearer, a flow (which is using RTP Media Congestion Avoidance Techniques (RMCAT)) in that bearer aims to reduce the sending rate quite quickly (within one RTT) so it avoids excessive queuing delay or packet loss ๐Ÿ›Ÿ

1.2. Why is it a self-clocked algorithm? ๐Ÿค”

Self-clocked congestion control algorithms have a perk over their rate-based counterparts, because the former has two adaptation mechanisms:

  • A congestion window computation that evolves over a longer timescale (several RTTs), especially when the congestion window evolution is dictated by estimated delay (to minimize vulnerability to, e.g., short-term delay variations) ๐ŸŒ™
  • A fine-grained congestion control given by the self-clocking. It operates on a shorter timescale (1 RTT). The benefits of self-clocking are also elaborated upon in [TFWC] โšก

A rate-based congestion control algorithm typically adjusts the rate based on delay and loss. The congestion detection needs to happen with a certain time lag so it doesn't overreact to spurious congestion events, like delay spikes ๐Ÿซ  Even though there are two or more congestion indications, the outcome is still only one mechanism to adjust the sending rate. That makes it hard to reach the goals of high throughput and prompt reaction to congestion at the same time ๐Ÿ’€

2. Requirements Language ๐Ÿ“œ

the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are meant to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, just like shown here ๐Ÿซถ

3. Overview of SCReAM Algorithm โœจ

okay so the core SCReAM algorithm is giving cousin energy to the self-clocking stuff in TCP-friendly window-based congestion control [TFWC] ๐Ÿ’–, and it follows the packet conservation principle. Packet conservation is described as a key factor in protecting networks from congestion [Packet-conservation]. Iconic, honestly ๐Ÿ’…

Here's the vibe: the receiver of the media echoes back a list of received RTP packets, plus the timestamp of the RTP packet with the highest sequence number, in feedback packets ๐ŸŽ€. The sender keeps a list of transmitted packets, their sizes, and the time each one was sent. That info tells it how many bytes it can send at any given moment โฐ. A congestion window puts an upper limit on how many bytes can be in flight, meaning sent but not yet acknowledged ๐Ÿฆ‹.

The congestion window is figured out in a way that's similar to LEDBAT [RFC6817]. LEDBAT uses send and receive timestamps to estimate the queuing delay (from now on called "qdelay") along the transmission path ๐ŸŒธ. That estimate is used to adjust the congestion window, and LEDBAT is the reason end-to-end latency stays low ๐Ÿซถ. [LEDBAT-delay-impact] shows that LEDBAT has some inherent issues that work against its goal of low delay ๐Ÿ˜ญ. The big problem the paper describes is that the base delay gets offset by LEDBAT's own queue buildup.

Now here's the twist ๐Ÿงš. With SCReAM, the source is rate limited, and the RTP queue has to be kept short (ideally empty). Also, a video encoder's output is rarely constant bitrate. Static content, like talking heads, gives almost zero video bitrate ๐Ÿ’€. That gives us two useful properties when LEDBAT is used with SCReAM, and they help avoid the issues described in [LEDBAT-delay-impact]:

  1. There is always a certain probability that SCReAM is short of data to transmit. This means the network queue will become empty every once in a while.
  2. The max video bitrate can be lower than the link capacity. If the max video bitrate is 5 Mbps and the capacity is 10 Mbps, then the network queue will become empty.

Only one of those two conditions has to be fulfilled for the base delay to update properly ๐Ÿ™Œ. Also, [LEDBAT-delay-impact] describes an issue with short-lived competing flows. In SCReAM, those short-lived flows make the self-clocking slow down, which builds up the RTP queue. That in turn lowers the media video bitrate ๐Ÿ˜ค. So SCReAM slows the bitrate down more when there are competing short-lived flows than traditional LEDBAT does. The basic LEDBAT-in-SCReAM idea is pretty simple, but a few steps are needed to make it work with conversational media ๐ŸŽ€:

  • Congestion window validation techniques. These are similar to the method described in [RFC7661]. Congestion window validation makes sure the congestion window is limited by the actual number of bytes in flight, which matters a lot for rate-limited sources like video ๐ŸŽฅ. Without it, the reaction to congestion is slow, because the congestion window doesn't properly reflect the congestion state in the network. The allowed idle period in this memo is shorter than in [RFC7661], to avoid excessive delays when, for example, wireless throughput has decreased during a stretch where the media coder's output bitrate was low (say, because of inactivity) ๐Ÿ˜ฌ. This memo also allows more relaxed rules for when the congestion window is allowed to grow, because variable output bitrate usually means the congestion window is often underutilized ๐Ÿ’œ.
  • Fast increase mode makes the bitrate increase faster when no congestion is detected ๐Ÿš€. It makes the media bitrate ramp up within 5 to 10 seconds. The behavior is similar to TCP slowstart. Fast increase mode is exited when congestion is detected. However, fast increase mode can resume if the congestion level is low, which allows a reasonably quick rate increase when link throughput goes up ๐ŸŒˆ.
  • A qdelay trend is computed for earlier detection of incipient congestion. As a result, it reduces jitter โœจ.
  • Addition of a media rate control function.
  • Use of inflection points in the media rate calculation to achieve reduced jitter.
  • Adjustment of qdelay target for better performance when competing with other loss-based congestion-controlled flows.

The features above are described in more detail in Sections 3.1 to 3.3. The full details are in Section 4 ๐Ÿ“š.

                +---------------------------+
                |        Media encoder      |
                +---------------------------+
                    ^                  |
                    |                  |(1)
                    |(3)              RTP
                    |                  V
                    |            +-----------+
               +---------+       |           |
               | Media   |  (2)  |   Queue   |
               | rate    |<------|           |
               | control |       |RTP packets|
               +---------+       |           |
                                 +-----------+
                                       |
                                       |(4)
                                      RTP
                                       |
                                       v
          +------------+       +--------------+
          |  Network   |  (7)  |    Sender    |
      +-->| congestion |------>| Transmission |
      |   |  control   |       |   Control    |
      |   +------------+       +--------------+
      |                                |
      |-------------RTCP----------|    |(5)
          (6)                     |   RTP
                                  |    v
                              +------------+
                              |     UDP    |
                              |   socket   |
                              +------------+

              Figure 1: SCReAM Sender Functional View

The SCReAM algorithm is made of three main parts: network congestion control, sender transmission control, and media rate control. All of these live on the sender side ๐Ÿง. Figure 1 shows the functional overview of a SCReAM sender. The receiver-side algorithm is way simpler in comparison, since it only generates feedback with acknowledgements of received RTP packets and an ECN count ๐Ÿ“.

3.1. Network Congestion Control

The network congestion control sets an upper limit on how much data can be in the network (bytes in flight). This limit is called CWND (congestion window), and it's used in the sender transmission control ๐ŸŒท.

SCReAM's congestion control uses techniques similar to LEDBAT [RFC6817] to measure the qdelay. Just like with LEDBAT, the sender and receiver don't need synchronized clocks to compute the qdelay ๐Ÿ•ฐ๏ธ. But they do need to use the same clock frequency, or the sender must be able to reliably infer the clock frequency at the receiver. If this isn't met, SCReAM's congestion control malfunctions, because it estimates the network queue delay incorrectly ๐Ÿ™ˆ.

The SCReAM sender calculates the congestion window based on feedback from the SCReAM receiver. The congestion window is allowed to increase if the qdelay is below a predefined qdelay target. Otherwise, the congestion window decreases. The qdelay target is typically set to 50-100 ms, which keeps the queuing delay low ๐ŸŒŠ. Reacting to a loss or ECN event causes an instant reduction of CWND ๐Ÿ’ฅ. Note that because real-time media like video is source rate limited, the queuing delay will mostly stay below the delay target. That's different from large file transfers using LEDBAT congestion control, where the queuing delay stays close to the delay target ๐Ÿ“ฆ.

3.2. Sender Transmission Control

The sender transmission control limits the output of data, based on the relation between the number of bytes in flight and the congestion window. Packet pacing is used to mitigate issues with ACK compression that MAY cause increased jitter and/or packet loss in the media traffic ๐ŸŽถ. Packet pacing limits the packet transmission rate to the estimated link throughput. Even if the send window allows a bunch of packets to go out, they aren't sent right away. Instead, they're sent at intervals based on the packet size and the estimated link throughput ๐Ÿ•Š๏ธ.

3.3. Media Rate Control

The media rate control adjusts the media bitrate so it ramps up quickly enough to get a fair share of system resources when link throughput increases ๐Ÿ“ˆ.

The reaction to reduced throughput MUST be prompt, so too much data doesn't pile up in the RTP packet queue(s) at the sender ๐Ÿšจ. The media bitrate is decreased if the RTP queue size exceeds a threshold.

When the sender's frame queues grow rapidly, like during a Radio Access Type (RAT) handover, the SCReAM sender MAY implement extra actions, such as discarding encoded media frames or frame skipping, to drain the RTP queues quickly ๐Ÿงน. Frame skipping temporarily reduces the frame rate. Which method to use is a design choice and is outside the scope of this algorithm description ๐Ÿคทโ€โ™€๏ธ.

4. Detailed Description of SCReAM

4.1. SCReAM Sender โœจ

okay so this part gets into the sender side in way more detail ๐Ÿงš it's split across three pieces: the network congestion control, the sender transmission control, and the media rate control. three main characters, one team ๐Ÿ’…

each SCReAM sender has media rate control plus an RTP queue for every media type or source. that queue is where RTP packets holding encoded media frames wait around before they get sent ๐ŸŽ€ Figure 1 shows the setup with a single media source (or stream). to handle multiple streams you add a transmission scheduler (it's not in the figure, bestie). the scheduler can enforce different priorities between streams and kinda acts like a coupled congestion controller for multiple flows ๐Ÿซถ multi-stream support is done in [SCReAM-CPP-implementation], so go peek there if you're curious ๐Ÿฆ‹

media frames get encoded and forwarded to the RTP queue (1) in Figure 1. the media rate adaptation looks at the size of the RTP queue (2) and gives the media encoder a target rate (3). the sender transmission controller then picks RTP packets from the RTP queue (4). with multiple flows, it pulls from each RTP queue based on some defined priority order or just round-robin ๐Ÿ” the sender transmission controller (or the transmission scheduler, if there are multiple flows) sends those RTP packets to the UDP socket (5). in the general case, all media SHOULD go through the sender transmission controller, and it's limited so that the bytes in flight stay less than the congestion window. RTCP packets come in (6), and the info about bytes in flight and the congestion window gets swapped between the network congestion control and the sender transmission control (7) ๐Ÿ”ฅ

4.1.1. Constants and Parameter Values

constants and state variables are listed in this section ๐Ÿ“‹ temporary variables are not listed. instead, they get '_t' tacked on in the pseudocode to show they're local to that spot ๐Ÿ™ˆ

4.1.1.1. Constants

the RECOMMENDED values (the ones in parentheses "()") were figured out from experiments ๐Ÿงช

  • QDELAY_TARGET_LO (0.1 s): target value for the minimum qdelay.
  • QDELAY_TARGET_HI (0.4 s): target value for the maximum qdelay. this is the upper limit on how far the target qdelay (qdelay_target) can climb to cope with competing loss-based flows. but the target qdelay doesn't have to start at this high value, since that would bump up end-to-end delay and make the rate control and congestion control loops sluggish ๐Ÿข
  • QDELAY_WEIGHT (0.1): averaging factor for qdelay_fraction_avg.
  • QDELAY_TREND_TH (0.2): threshold for detecting incipient congestion.
  • MIN_CWND (3000 bytes): minimum congestion window.
  • MAX_BYTES_IN_FLIGHT_HEAD_ROOM (1.1): headroom for limiting CWND.
  • GAIN (1.0): gain factor for congestion window adjustment.
  • BETA_LOSS (0.8): CWND scale factor due to a loss event.
  • BETA_ECN (0.9): CWND scale factor due to an ECN event.
  • BETA_R (0.9): scale factor for target rate due to a loss event.
  • MSS (1000 byte): maximum segment size = max RTP packet size.
  • RATE_ADJUST_INTERVAL (0.2 s): interval between media bitrate adjustments.
  • TARGET_BITRATE_MIN: minimum target bitrate in bps (bits per second).
  • TARGET_BITRATE_MAX: maximum target bitrate in bps.
  • RAMP_UP_SPEED (200000 bps/s): maximum allowed rate increase speed.
  • PRE_CONGESTION_GUARD (0.0..1.0): guard factor against early congestion onset. a higher value gives less jitter, possibly at the expense of lower link utilization. this value MAY be subject to tuning depending on e.g., media coder characteristics. experiments with H264 and VP8 indicate that 0.1 is a suitable value. see [SCReAM-CPP-implementation] and [SCReAM-implementation-experience] for evaluation of a real implementation.
  • TX_QUEUE_SIZE_FACTOR (0.0..2.0): guard factor against RTP queue buildup. this value MAY be subject to tuning depending on, e.g., media coder characteristics. experiments with H264 and VP8 indicate that 1.0 is a suitable value. see [SCReAM-CPP-implementation] and [SCReAM-implementation-experience] for evaluation of a real implementation.
  • RTP_QDELAY_TH (0.02 s): RTP queue delay threshold for a target rate reduction.
  • TARGET_RATE_SCALE_RTP_QDELAY (0.95): scale factor for target rate when the RTP qdelay threshold is exceeded (RTP_QDELAY_TH).
  • QDELAY_TREND_LO (0.2): threshold value for qdelay_trend.
  • T_RESUME_FAST_INCREASE (5 s): time span until fast increase mode can be resumed, given that the qdelay_trend is below QDELAY_TREND_LO.
  • RATE_PACE_MIN (50000 bps): minimum pacing rate.

4.1.1.2. State Variables

the values within parentheses "()" are the initial values ๐ŸŒฑ

  • qdelay_target (QDELAY_TARGET_LO): qdelay target. a variable qdelay target is introduced to manage cases where a fixed qdelay target would otherwise starve the RMCAT flow under such circumstances (e.g., FTP competes for bandwidth over the same bottleneck). the qdelay target is allowed to vary between QDELAY_TARGET_LO and QDELAY_TARGET_HI.
  • qdelay_fraction_avg (0.0): fractional qdelay filtered by the Exponentially Weighted Moving Average (EWMA).
  • qdelay_fraction_hist[20] ({0,..,0}): vector of the last 20 fractional qdelay samples.
  • qdelay_trend (0.0): qdelay trend, which indicates incipient congestion.
  • qdelay_trend_mem (0.0): low-pass filtered version of qdelay_trend.
  • qdelay_norm_hist[100] ({0,..,0}): vector of the last 100 normalized qdelay samples.
  • in_fast_increase (true): true if in fast increase mode.
  • cwnd (MIN_CWND): congestion window.
  • bytes_newly_acked (0): the number of bytes acknowledged with the last received acknowledgement, i.e., bytes acknowledged since the last CWND update.
  • max_bytes_in_flight (0): the maximum number of bytes in flight over a sliding time window, i.e., transmitted but not yet acknowledged bytes.
  • send_wnd (0): upper limit to how many bytes can currently be transmitted. updated when cwnd is updated and when an RTP packet is transmitted.
  • target_bitrate (0 bps): media target bitrate.
  • target_bitrate_last_max (1 bps): inflection point of the media target bitrate, i.e., the last known highest target_bitrate. used to limit bitrate increase speed close to the last known congestion point.
  • rate_transmit (0.0 bps): measured transmit bitrate.
  • rate_ack (0.0 bps): measured throughput based on received acknowledgements.
  • rate_media (0.0 bps): measured bitrate from the media encoder.
  • rate_media_median (0.0 bps): median value of rate_media, computed over more than 10 s.
  • s_rtt (0.0s): smoothed RTT (in seconds), computed with a similar method to that described in [RFC6298].
  • rtp_queue_size (0 bits): sum of the sizes of RTP packets in queue.
  • rtp_size (0 byte): size of the last transmitted RTP packet.
  • loss_event_rate (0.0): the estimated fraction of RTTs with lost packets detected.

4.1.2. Network Congestion Control โœจ

okay so this section is where the magic happens ๐Ÿฆ‹ network congestion control does two big jobs:

  • Figuring out the congestion window on the sender side: this is the upper limit on how many bytes can be in flight at once ๐Ÿ’…
  • Calculating the send window on the sender side: RTP packets only get sent if the number of bytes in flight fits within the congestion window, and the send window is what controls that ๐Ÿซถ

SCReAM is a window-based, byte-oriented congestion control protocol ๐ŸŒธ It counts bytes by looking at the size of the RTP packets that were actually sent. So the sender has to keep a list of every transmitted RTP packet and when it went out (wall-clock time) ๐Ÿ“ bestie, that list is non-negotiable, it MUST be kept for the later math ๐Ÿ˜ญ

Now the bytes_in_flight variable ๐Ÿงฎ it's the sum of the sizes of RTP packets from the most recently transmitted one all the way down to, but not including, the acknowledged packet with the highest sequence number. Put another way, it's the gap between the highest transmitted byte sequence number and the highest acknowledged byte sequence number ๐Ÿ’–

Picture this: you send packet SN, and the latest ACK says the highest thing received is SN-5. Then bytes_in_flight is the sum of the sizes of packets SN-4, SN-3, SN-2, SN-1, and SN โœจ and get this, even if SN-3 got lost, its size still counts ๐Ÿ’€ no skipping it, it's still in the total.

Next up is bytes_newly_acked, which goes up by however much the highest sequence number has moved since the last feedback ๐ŸŽ€ say the last feedback said N and the new one says N+3. Then bytes_newly_acked goes up by the sizes of N+1, N+2, and N+3 ๐ŸŒˆ lost packets get included here too, so even if N+2 disappeared into the void, its size still gets added ๐Ÿ™ˆ and bytes_newly_acked gets reset to zero after every CWND update ๐Ÿงน

The receiver's feedback is assumed to have these pieces:

  • A list of the sequence numbers of the received RTP packets ๐Ÿ“‹
  • The wall-clock timestamp of the received RTP packet with the highest sequence number โฐ
  • The accumulated number of ECN-CE-marked packets (n_ECN) ๐Ÿ“ CE just means "Congestion Experienced" ๐Ÿšฆ

When the sender gets RTCP feedback, the qdelay gets calculated the way [RFC6817] describes ๐Ÿงš you get a qdelay sample for every acknowledgement you receive. There's no smoothing on the qdelay itself, BUT some smoothing happens anyway because the CWND computation acts like a low-pass filter ๐Ÿซง the pseudocode below updates a bunch of variables, and temporary variables get a _t on the end ๐Ÿ’œ

Also heads up: as mentioned in Section 6, getting the congestion window and media bitrate just right might benefit from extra optimizations for very high and very low bitrates, plus some extra damping for periodic packet bursts ๐ŸŒธ some of those tricks live in [SCReAM-CPP-implementation], but they are NOT part of the SCReAM spec right now, so don't go thinking they're required ๐Ÿ™…โ€โ™€๏ธ

  update_variables(qdelay):
    qdelay_fraction_t = qdelay / qdelay_target
    # Calculate moving average
    qdelay_fraction_avg = (1 - QDELAY_WEIGHT) * qdelay_fraction_avg +
       QDELAY_WEIGHT * qdelay_fraction_t
    update_qdelay_fraction_hist(qdelay_fraction_t)
    # Compute the average of the values in qdelay_fraction_hist
    avg_t = average(qdelay_fraction_hist)
    # R is an autocorrelation function of qdelay_fraction_hist,
    #  with the mean (DC component) removed, at lag K
    # The subtraction of the scalar avg_t from
    #  qdelay_fraction_hist is performed element-wise
    a_t = R(qdelay_fraction_hist-avg_t, 1) /
          R(qdelay_fraction_hist-avg_t, 0)
    # Calculate qdelay trend
    qdelay_trend = min(1.0, max(0.0, a_t * qdelay_fraction_avg))
    # Calculate a 'peak-hold' qdelay_trend; this gives a memory
    #  of congestion in the past
    qdelay_trend_mem = max(0.99 * qdelay_trend_mem, qdelay_trend)

The qdelay fraction gets sampled every 50 ms, and the last 20 samples are kept in a vector called qdelay_fraction_hist ๐Ÿ“Š that vector feeds into a qdelay trend that lands somewhere between 0.0 and 1.0, depending on how congested things look โœจ the prediction coefficient a_t is positive when qdelay is trending up OR down, which means you get a congestion heads-up BEFORE qdelay even hits the target ๐Ÿšจ lowkey genius. As a side effect, if qdelay goes down, that also counts as a sign of congestion ๐Ÿ‘€ and honestly? Experiments showed this is actually beneficial, because a queue delay that's climbing or dropping means the transmit rate is super close to the path capacity ๐Ÿ”ฅ

The autocorrelation function R works like this: let x be a vector of N values, then the biased autocorrelation for a given lag k of the vector x is ๐Ÿ’…

              n=N-k
      R(x,k) = SUM x(n) * x(n + k)
              n=1

The prediction coefficient gets multiplied by qdelay_fraction_avg so it's less sensitive to increasing qdelay when qdelay is tiny ๐Ÿงธ Sampling every 50 ms is a simplification, so the same qdelay might get sampled a few times in a row, but that's totally fine ๐Ÿ™Œ the vector only cares whether qdelay is going up or down anyway. The qdelay_trend gets used in media rate control to spot incipient congestion and to decide when to leave fast increase mode ๐Ÿƒโ€โ™€๏ธ and qdelay_trend_mem is there to enforce a less aggressive rate increase after congestion events ๐Ÿซถ finally, update_qdelay_fraction_hist(..) kicks out the oldest element and adds the newest qdelay fraction to the qdelay_fraction_hist vector ๐Ÿ”„


4.1.2.1. Reaction to Packet Loss and ECN

okay now let's talk about how SCReAM reacts when things go wrong ๐Ÿ˜ญ a loss event happens when one or more RTP packets get declared missing ๐Ÿ’” how that loss detection works is in Section 4.1.2.4 ๐Ÿ” once a loss event is detected, further lost RTP packets SHOULD be ignored for a full smoothed round-trip time ๐Ÿ›‘ the goal is to keep the congestion window from dropping more than once per round trip ๐ŸŒŠ

Here's the spicy part ๐ŸŒถ๏ธ the congestion window back-off after loss is deliberately a bit gentler than, say, TCP Reno ๐Ÿ’… why? TCP is usually moving whole files, so it acts kinda like a source with infinite bitrate until the file is all sent ๐Ÿ“ SCReAM, on the other hand, has a source whose rate stays close to the available transmit rate and is often even below it ๐Ÿฅบ so SCReAM just has less chances to grab free capacity than a TCP file transfer does. To make up for that, it is RECOMMENDED to let SCReAM reduce the congestion window less than TCP would when loss events happen ๐ŸŽ€

Now ECN ๐Ÿฆ‹ an ECN event is detected if the n_ECN counter in the feedback report has gone up since the last feedback you received โœจ once an ECN event is detected, the n_ECN counter gets ignored for a full smoothed round-trip time, again so the congestion window only drops at most once per round trip ๐Ÿ” the back-off from an ECN event MAY be smaller than the one from a loss event ๐ŸŒˆ this lines up with the idea in [ALT-BACKOFF], which lets ECN marking thresholds sit lower than the packet drop thresholds ๐Ÿงš

4.1.2.2. Congestion Window Update ๐Ÿ’–

okay so the congestion window (cwnd) update depends on whether loss happens, an ECN mark shows up, or neither ๐Ÿซถ. The pseudocode below spells out what to do in each case โœจ

on congestion event(qdelay):
  # Either loss or ECN mark is detected
  in_fast_increase = false
  if (is loss)
    # Loss is detected
    cwnd = max(MIN_CWND, cwnd * BETA_LOSS)
  else
    # No loss, so it is then an ECN mark
    cwnd = max(MIN_CWND, cwnd * BETA_ECN)
  end
  adjust_qdelay_target(qdelay) #compensating for competing flows
  calculate_send_window(qdelay, qdelay_target)

# When no congestion event
on acknowledgement(qdelay):
  update_bytes_newly_acked()
  update_cwnd(bytes_newly_acked)
  adjust_qdelay_target(qdelay) # compensating for competing flows
  calculate_send_window(qdelay, qdelay_target)
  check_to_resume_fast_increase()

The methods are explained in more detail below ๐Ÿ‘€ The cwnd update is based on qdelay, except when there's a loss event (one or more lost RTP packets in one RTT) or an ECN event, which we already covered earlier ๐ŸŒธ

Here's the pseudocode for the cwnd update ๐Ÿ’…

update_cwnd(bytes_newly_acked):
  # In fast increase mode?
  if (in_fast_increase)
    if (qdelay_trend >= QDELAY_TREND_TH)
      # Incipient congestion detected; exit fast increase mode
      in_fast_increase = false
    else
      # No congestion yet; increase cwnd if it
      #  is sufficiently used
      # Additional slack of bytes_newly_acked is
      #  added to ensure that CWND growth occurs
      #  even when feedback is sparse
      if (bytes_in_flight * 1.5 + bytes_newly_acked > cwnd)
        cwnd = cwnd + bytes_newly_acked
      end
      return
    end
  end

  # Not in fast increase mode
  # off_target calculated as with LEDBAT
  off_target_t = (qdelay_target - qdelay) / qdelay_target

  gain_t = GAIN
  # Adjust congestion window
  cwnd_delta_t =
    gain_t * off_target_t * bytes_newly_acked * MSS / cwnd
  if (off_target_t > 0 &&
      bytes_in_flight * 1.25 + bytes_newly_acked <= cwnd)
    # No cwnd increase if window is underutilized
    # Additional slack of bytes_newly_acked is
    #  added to ensure that CWND growth occurs
    #  even when feedback is sparse
    cwnd_delta_t = 0;
  end

  # Apply delta
  cwnd += cwnd_delta_t
  # limit cwnd to the maximum number of bytes in flight
  cwnd = min(cwnd, max_bytes_in_flight *
             MAX_BYTES_IN_FLIGHT_HEAD_ROOM)
  cwnd = max(cwnd, MIN_CWND)

cwnd gets updated differently depending on whether or not the congestion control is in fast increase mode, which is tracked by the variable in_fast_increase ๐Ÿฆ‹

When it's in fast increase mode, the cwnd grows by the number of newly acknowledged bytes, as long as the window is sufficiently used ๐Ÿš€ Sparse feedback can slow cwnd growth down, so some extra slack (equal to the number of newly acknowledged bytes) gets added ๐Ÿงš

When in_fast_increase is false, the cwnd growth is decided by how qdelay compares to qdelay_target, and growth gets limited if the window isn't used enough ๐Ÿ’œ

SCReAM works out the GAIN in a similar way to [RFC6817] ๐ŸŒˆ The difference is that [RFC6817] says the CWND increase is also limited by an extra function controlled by a constant called ALLOWED_INCREASE. This specification removes that extra limit ๐Ÿ™…โ€โ™€๏ธ

The CWND is also capped by max_bytes_in_flight and MIN_CWND ๐ŸŽ€ Limiting the cwnd by the maximum number of bytes in flight over the last 5 seconds (max_bytes_in_flight) helps avoid overestimating throughput after things like idle periods ๐Ÿ˜Œ And MAX_BYTES_IN_FLIGHT_HEAD_ROOM gives some slack so the cwnd can handle a bit of variability in the media coder's output rate ๐Ÿ“


4.1.2.3. Competing Flows Compensation ๐ŸŒธ

It's pretty likely that a SCReAM flow will have to share congested bottlenecks with flows using more aggressive congestion control algorithms (like big FTP flows using loss-based congestion control) ๐Ÿ˜ญ The worst case is when the bottleneck queues are tail-drop with a large buffer size ๐Ÿ’€ SCReAM handles these situations by adjusting the qdelay_target when loss-based flows are detected, as shown in the pseudocode below โœจ

adjust_qdelay_target(qdelay)
  qdelay_norm_t = qdelay / QDELAY_TARGET_LOW
  update_qdelay_norm_history(qdelay_norm_t)
  # Compute variance
  qdelay_norm_var_t = VARIANCE(qdelay_norm_history(200))
  # Compensation for competing traffic
  # Compute average
  qdelay_norm_avg_t = AVERAGE(qdelay_norm_history(50))
  # Compute upper limit to target delay
  new_target_t = qdelay_norm_avg_t + sqrt(qdelay_norm_var_t)
  new_target_t *= QDELAY_TARGET_LO
  if (loss_event_rate > 0.002)
    # Packet losses detected
    qdelay_target = 1.5 * new_target_t
  else
    if (qdelay_norm_var_t < 0.2)
      # Reasonably safe to set target qdelay
      qdelay_target = new_target_t
    else
      # Check if target delay can be reduced; this helps prevent
      #  the target delay from being locked to high values forever
      if (new_target_t < QDELAY_TARGET_LO)
        # Decrease target delay quickly, as measured queuing
        #  delay is lower than target
        qdelay_target = max(qdelay_target * 0.5, new_target_t)
      else
        # Decrease target delay slowly
        qdelay_target *= 0.9
      end
    end
  end

  # Apply limits
  qdelay_target = min(QDELAY_TARGET_HI, qdelay_target)
  qdelay_target = max(QDELAY_TARGET_LO, qdelay_target)

Two temporary variables get calculated here ๐Ÿง  qdelay_norm_avg_t is the long-term average queue delay, and qdelay_norm_var_t is the long-term variance of the queue delay ๐Ÿ“Š A high qdelay_norm_var_t means the queue delay is jumping around, which can mean the bottleneck bandwidth dropped or a competing flow just showed up ๐Ÿ‘€ So it's not safe to adjust the queue delay target right then ๐Ÿ™ˆ

A low qdelay_norm_var_t means the queue delay is pretty stable ๐Ÿง˜โ€โ™€๏ธ That could be because the queue delay is low, but it could also be a competing flow pushing the bottleneck to the point where packet losses start, in which case the queue delay stays relatively high for longer ๐Ÿซ 

The queue delay target is allowed to go up if either the loss event rate is above a given threshold or qdelay_norm_var_t is low ๐Ÿ”ฅ Both of those conditions hint that a competing flow might be around ๐Ÿ•ต๏ธโ€โ™€๏ธ In every other case, the queue delay target goes down ๐Ÿ“‰

The function that adjusts qdelay_target is simple, so it can give false positives and false negatives ๐Ÿ™ƒ If the algorithm mistakes its own self-inflicted congestion for competing loss-based FTP flows, that's a false positive. The opposite, where it fails to notice a competing FTP flow, is a false negative ๐Ÿ˜ฌ

Extensive simulations showed the algorithm performs well in LTE test cases, and it also does well in simple bandwidth-limited bottleneck test cases with competing FTP flows ๐ŸŽ‰ Still, the algorithm could potentially fail, and that can't be ruled out completely ๐Ÿฅบ A false positive (where self-inflicted congestion gets wrongly identified as competing flows) is especially problematic when it leads to raising the target queue delay, because that can make the end-to-end delay jump up dramatically ๐Ÿšจ

If it's deemed unlikely that competing flows share the same bottleneck, the algorithm described in this section MAY be turned off ๐ŸŒท One example is QoS-enabled bearers in 3GPP-based access such as LTE ๐Ÿ“ฑ But when sending over the Internet, the network conditions are often not known for sure, so it's generally not possible to make safe assumptions about how the network is being used or whether competing flows share the same bottleneck ๐ŸŒ So turning this algorithm off must be considered with caution, because it can lead to basically zero throughput when competing with loss-based traffic โš ๏ธ

Turning this algorithm off without thinking can leave you with basically zero throughput if loss-based traffic is competing with you. Think carefully first, bestie ๐Ÿ’€

4.1.2.4. Lost Packet Detection ๐Ÿ•ต๏ธโ€โ™€๏ธ

Lost packet detection is based on the received sequence number list ๐Ÿ“‹ A reordering window SHOULD be applied so that packet reordering doesn't trigger loss events ๐Ÿ›‘ The reordering window is specified as a time unit, which is similar to the ideas behind Recent ACKnowledgement (RACK) [RACK] ๐Ÿ’ซ Computing the reordering window is possible thanks to a lost flag in the list of transmitted RTP packets ๐Ÿท๏ธ This flag gets set if the received sequence number list shows that a given RTP packet is missing ๐Ÿ™‹โ€โ™€๏ธ If later feedback shows that a previously lost, marked packet was actually received, the reordering window gets updated to reflect the reordering delay โฑ๏ธ The reordering window is the difference in time between the event where the packet was marked as lost and the event where it was indicated as successfully received ๐Ÿงฎ Loss is detected if a given RTP packet isn't acknowledged within a time window (the reordering window) after an RTP packet with a higher sequence number was acknowledged ๐Ÿ’…โœจ

4.1.2.5. Send Window Calculation โœจ

Okay bestie, the core SCReAM sending rule is giving simple: only send when the bytes in flight are less than the congestion window ๐Ÿซถ. But this strict rule doesn't work optimally, and there are two reasons why ๐Ÿ˜ญ:

  • Bitrate variations: Video encoders and other media sources usually produce frames whose size varies to a larger or smaller extent. The RTP queue soaks up those natural variations ๐ŸŽ€. But the RTP queue should be as short as possible so the end-to-end delay doesn't climb ๐Ÿ“ˆ. So the media rate control takes the RTP queue size into account when it computes the target bitrate. A strict "send only when bytes in flight is less than the congestion window" rule can make the RTP queue grow just because the send window is limited, and that can push the target bitrate down ๐Ÿ“‰. The congestion window is only allowed to increase when there's enough data in flight, so it won't increase, or will increase very slowly ๐Ÿข. The final result is that the media bitrate increases very slowly or not at all ๐Ÿ’€.
  • Reverse (feedback) path congestion: Especially when you're sending over buffer-bloated networks, the one-way delay in the reverse direction can jump because of congestion ๐Ÿ™ˆ. That delays the acknowledgements and temporarily halts the self-clocking, even though the forward path isn't congested ๐Ÿ˜ค.

So the send window gets adjusted based on qdelay, how qdelay compares to the qdelay target, and the relationship between the congestion window and the bytes in flight ๐Ÿ’…. A strict rule kicks in when qdelay is higher than qdelay_target, so the queue in the network doesn't keep building ๐Ÿšง. When qdelay is lower than qdelay_target, a more relaxed rule applies. That lets the bitrate ramp up fast when no congestion is detected, while still behaving stably when things get congested ๐ŸŒˆ.

The send window is given by the relationship between the adjusted congestion window and the bytes in flight, following the pseudocode below ๐Ÿงš.

calculate_send_window(qdelay, qdelay_target)
  # send window is computed differently depending on congestion level
  if (qdelay <= qdelay_target)
    send_wnd = cwnd + MSS - bytes_in_flight
  else
    send_wnd = cwnd - bytes_in_flight
  end

The send window gets updated every time an RTP packet is transmitted or an RTCP feedback messaged is received ๐Ÿ“ฌ.

4.1.2.6. Packet Pacing ๐Ÿฆ‹

Packet pacing exists to mitigate coalescing, which is when packets get sent in bursts ๐Ÿ’ฅ. Bursts bring increased jitter and potentially increased packet loss ๐Ÿ˜ฌ. Pacing also helps with queue overflow caused by key-frame generation in video coders ๐ŸŽฌ. The time interval between consecutive packet transmissions is greater than or equal to t_pace, where t_pace is given by the equations below :

pace_bitrate = max (RATE_PACE_MIN, cwnd * 8 / s_rtt)
t_pace = rtp_size * 8 / pace_bitrate

Here, rtp_size is the size of the last transmitted RTP packet, and s_rtt is the smoothed round trip time โฑ๏ธ. RATE_PACE_MIN is the minimum pacing rate ๐Ÿ“.

4.1.2.7. Resuming Fast Increase Mode ๐Ÿš€

Fast increase mode can resume to speed up the bitrate increase if congestion abates ๐ŸŒธ. The condition for resuming fast increase mode (in_fast_increase = true) is that qdelay_trend stays less than QDELAY_TREND_LO for T_RESUME_FAST_INCREASE seconds or more โญ.

4.1.2.8. Stream Prioritization ๐Ÿ’œ

The SCReAM algorithm makes a really good distinction between network congestion control and media rate control ๐Ÿ‘‘. This is easily extended to many streams: RTP packets from two or more RTP queues get scheduled at the rate the network congestion control permits ๐Ÿซง.

There are a few different scheduling regimes it can use. For example, the method for coupled congestion control specified in [COUPLED-CC] can be used ๐Ÿ”—. One implementation of SCReAM [SCReAM-CPP-implementation] uses credit-based scheduling ๐Ÿง. In credit-based scheduling, queues accumulate credit as they wait for service, and they spend that credit while they're being serviced ๐Ÿ’ธ. For instance, if one queue is allowed to transmit 1000 bytes, then a credit of 1000 bytes is allocated to the other unscheduled queues ๐ŸŽ. This principle can be extended to weighted scheduling, where the credit allocated to unscheduled queues depends on the relative weights โš–๏ธ. The latter is also implemented in [SCReAM-CPP-implementation] โœจ.

4.1.3. Media Rate Control ๐ŸŽ€

The media rate control algorithm runs at regular intervals, indicated by RATE_ADJUSTMENT_INTERVAL, except when it needs to react promptly to loss events โฐ. It works from the size of the RTP packet send queue and the loss events it observes ๐Ÿ‘€. On top of that, qdelay_trend is also considered in the media rate control, which helps reduce the amount of induced network jitter ๐Ÿง˜โ€โ™€๏ธ.

The media rate control's job is to strike a reasonable balance between a low amount of queuing in the RTP queue(s) and enough data to send to keep the data path busy ๐ŸŒŠ. If you set it too cautiously, you can underutilize the network capacity, and then the flow gets starved out by more opportunistic traffic ๐Ÿฅ€. But if you set it too aggressively, you get increased jitter ๐Ÿ˜ตโ€๐Ÿ’ซ.

The target_bitrate gets adjusted depending on the congestion state ๐ŸŽฏ. It can vary between a minimum value (TARGET_BITRATE_MIN) and a maximum value (TARGET_BITRATE_MAX) ๐Ÿ“. TARGET_BITRATE_MIN SHOULD be set to a low enough value to stop RTP packets from piling up when the network throughput drops ๐Ÿ›‘. The sender SHOULD also have a mechanism that discards RTP packets when the network throughput becomes very low and RTP packets are getting excessively delayed ๐Ÿ—‘๏ธ.

For the overall bitrate adjustment, two network throughput estimates get computed ๐Ÿงฎ:

  • rate_transmit: The measured transmit bitrate ๐Ÿ“ค.
  • rate_ack: The ACKed bitrate, i.e., the volume of ACKed bits per second ๐Ÿ“ฅ.

Both estimates get updated every 200 ms โฒ๏ธ.

The current throughput, current_rate, is computed as the maximum value of rate_transmit and rate_ack ๐Ÿ’ฏ. Why use rate_ack as well as rate_transmit? Because rate_transmit is also affected by how much data is available to transmit. So a lack of data to send can look like reduced throughput, and that can cause an unnecessary rate reduction ๐Ÿ˜ฉ. To overcome this shortcoming, rate_ack gets used too, and that gives a more stable throughput estimate ๐Ÿซถ.

The rate change behaviour depends on whether a loss or ECN event has happened, and on whether the congestion control is in fast increase mode ๐ŸŒˆ.

# The target_bitrate is updated at a regular interval according
# to RATE_ADJUST_INTERVAL

on loss:
   # Loss event detected
   target_bitrate = max(BETA_R * target_bitrate,
                        TARGET_BITRATE_MIN)
   exit
on ecn_mark:
   # ECN event detected
   target_bitrate = max(BETA_ECN * target_bitrate,
                        TARGET_BITRATE_MIN)
   exit

ramp_up_speed_t = min(RAMP_UP_SPEED, target_bitrate / 2.0)
scale_t = (target_bitrate - target_bitrate_last_max) /
     target_bitrate_last_max
scale_t = max(0.2, min(1.0, (scale_t * 4)^2))
# min scale_t value 0.2, as the bitrate should be allowed to
#  increase slowly. This prevents locking the rate to
#  target_bitrate_last_max
if (in_fast_increase = true)
   increment_t = ramp_up_speed_t * RATE_ADJUST_INTERVAL
   increment_t *= scale_t
   target_bitrate += increment_t
else
   current_rate_t = max(rate_transmit, rate_ack)
   # Compute a bitrate change
   delta_rate_t = current_rate_t * (1.0 - PRE_CONGESTION_GUARD *
        queue_delay_trend) - TX_QUEUE_SIZE_FACTOR * rtp_queue_size
   # Limit a positive increase if close to target_bitrate_last_max
   if (delta_rate_t > 0)
     delta_rate_t *= scale_t
     delta_rate_t =
       min(delta_rate_t, ramp_up_speed_t * RATE_ADJUST_INTERVAL)

   end
   target_bitrate += delta_rate_t
   # Force a slight reduction in bitrate if RTP queue
   #  builds up
   rtp_queue_delay_t = rtp_queue_size / current_rate_t
   if (rtp_queue_delay_t > RTP_QDELAY_TH)
     target_bitrate *= TARGET_RATE_SCALE_RTP_QDELAY
   end
end

rate_media_limit_t =
   max(current_rate_t, max(rate_media, rtp_rate_median))
rate_media_limit_t *= (2.0 - qdelay_trend_mem)
target_bitrate = min(target_bitrate, rate_media_limit_t)
target_bitrate = min(TARGET_BITRATE_MAX,
   max(TARGET_BITRATE_MIN, target_bitrate))

If a loss event happens, the target_bitrate gets updated and the rate change procedure is exited โ›”. Otherwise, the rate change procedure carries on ๐Ÿƒโ€โ™€๏ธ. The reasoning behind cutting the rate on loss is that a congestion window reduction is about to take effect, so cutting the rate proactively stops RTP packets from queuing up when the transmit rate drops because the congestion window shrank ๐Ÿง . ECN events get a similar rate reduction ๐Ÿซง.

The rate update frequency is limited by RATE_ADJUST_INTERVAL, unless a loss event happens ๐Ÿ•ฐ๏ธ. That value is based on experimentation, with real-life limitations in video coders taken into account [SCReAM-CPP-implementation] ๐ŸŽฅ. An interval that's too short makes the rate control loop in video coders more unstable ๐ŸŒ€, and an interval that's too long makes the overall congestion control sluggish ๐ŸŒ.

In fast increase mode (in_fast_increase = true), the bitrate increase is set by the desired ramp-up speed (RAMP_UP_SPEED) ๐Ÿš„. The ramp-up speed gets limited when the target bitrate is low, so the rate doesn't oscillate at low bottleneck bitrates ๐ŸŽข. The setting for RAMP_UP_SPEED depends on preferences ๐Ÿซต. A high setting, such as 1000 kbps/s, gets you high-quality media quickly ๐Ÿคฉ. The tradeoff is increased jitter, which can show up as choppy video rendering, for example ๐Ÿ˜–.

When in_fast_increase is false, the bitrate increase depends on the current bitrate, and it's also controlled by the estimated RTP queue and the qdelay trend ๐Ÿ“Š. So an increased congestion level sensed by the network congestion control is enough to limit the bitrate ๐Ÿšฆ. The target_bitrate_last_max gets updated whenever congestion is detected ๐Ÿ“Œ.

Finally, the target_bitrate stays within the defined min and max values ๐ŸŽ€.

The aware reader might notice that the target bitrate computation depends on qdelay too. This shows up in the use of qdelay_trend. These parameters are also used in the network congestion control, so you might suspect some odd interaction between the media rate control and the network congestion control ๐Ÿ™ˆ. And that's actually the case if PRE_CONGESTION_GUARD is set to a high value ๐Ÿ˜ณ. The use of qdelay_trend in the media rate control is solely to reduce jitter, and you can remove the dependency by setting PRE_CONGESTION_GUARD=0 ๐Ÿซถ. The effect is a somewhat larger rate increase after congestion, at the expense of increased jitter in congested situations ๐Ÿ’….

4.2. SCReAM Receiver โœจ

okay so the SCReAM receiver has one super simple job ๐Ÿ’… it sends acknowledgements for the packets it got, plus the total ECN count, back to the SCReAM sender ๐Ÿฆ‹ it also echoes back the receive time of the RTP packet with the highest sequence number โฐ every time an RTP packet shows up, the receiver MUST keep enough info around to send all of that back in an RTCP transport-layer feedback message ๐Ÿซถ how often that feedback goes out depends on how much RTCP bandwidth is available. the requirements for the feedback elements and the feedback interval are below ๐ŸŽ€

4.2.1. Requirements on Feedback Elements

the following feedback elements are REQUIRED for basic functionality in SCReAM ๐Ÿ‘‘

  • A list of received RTP packets. This list SHOULD be sufficiently long to cover all received RTP packets. This list can be realized with the Loss RLE (Run Length Encoding) Report Block in [RFC3611].
  • A wall-clock timestamp corresponding to the received RTP packet with the highest sequence number is required in order to compute the qdelay. This can be realized by means of the Packet Receipt Times Report Block in [RFC3611]. begin_seq MUST be set to the highest received sequence number (which has possibly wrapped around); end_seq MUST be set to begin_seq+1 modulo 65536. The timestamp clock MAY be set according to [RFC3611], i.e., equal to the RTP timestamp clock. Detailed individual packet receive times are not necessary, as SCReAM does currently not describe how they can be used. ๐Ÿ™ˆ

The basic feedback needed for SCReAM involves the use of the Loss RLE Report Block and the Packet Receipt Times Report Block as shown in Figure 2 ๐ŸŒธ

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |V=2|P|reserved |   PT=XR=207   |             length            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                              SSRC                             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     BT=2      | rsvd. |  T=0  |         block length          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        SSRC of source                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          begin_seq            |             end_seq           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          chunk 1              |             chunk 2           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   :                              ...                              :
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          chunk n-1            |             chunk n           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     BT=3      | rsvd. |  T=0  |         block length          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        SSRC of source                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          begin_seq            |             end_seq           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |       Receipt time of packet begin_seq                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 2: Basic Feedback Message for SCReAM, Based on RFC 3611

in a typical use case, no more than four Loss RLE chunks are needed, thus the feedback message will be 44 bytes. it is obvious from Figure 2 that there is a lot of redundant information in the feedback message ๐Ÿ˜ญ a more optimized feedback format, including the additional feedback elements listed below, could reduce the feedback message size a bit.

an additional feedback element that can improve the performance of SCReAM is:

  • Accumulated number of ECN-CE-marked packets (n_ECN). For instance, this can be realized with the ECN Feedback Report Format in [RFC6679]. The given feedback report format is slightly overkill, as SCReAM would do quite well with only a counter that increments by one for each received packet with the ECN-CE codepoint set. The more bulky format could nevertheless be useful for, e.g., ECN black-hole detection. ๐Ÿงš

4.2.2. Requirements on Feedback Intensity

SCReAM benefits from relatively frequent feedback. It is RECOMMENDED that a SCReAM implementation follows the guidelines below ๐Ÿ’–

the feedback interval depends on the media bitrate. at low bitrates, it is sufficient with a feedback interval of 100 to 400 ms; while at high bitrates, a feedback interval of roughly 20 ms is preferred. at very high bitrates, even shorter feedback intervals MAY be needed in order to keep the self-clocking in SCReAM working well. one indication that feedback is too sparse is that the SCReAM implementation cannot reach high bitrates, even in uncongested links. more frequent feedback might solve this issue โœจ

the numbers above can be formulated as a feedback interval function that can be useful for the computation of the desired RTCP bandwidth. the following equation expresses the feedback rate:

rate_fb = min(50, max(2.5, rate_media / 10000))

rate_media is the RTP media bitrate expressed in bps; rate_fb is the feedback rate expressed in packets/s. converting to feedback interval, we get:

fb_int = 1.0 / min(50, max(2.5, rate_media / 10000))

the transmission interval is not critical ๐Ÿซง so, in the case of multi-stream handling between two hosts, the feedback for two or more synchronization sources (SSRCs) can be bundled to save UDP/IP overhead. however, the final realized feedback interval SHOULD not exceed 2*fb_int in such cases, meaning that a scheduled feedback transmission event should not be delayed more than fb_int.

SCReAM works with AVPF regular mode; immediate or early mode is not required by SCReAM but can nonetheless be useful for RTCP messages not directly related to SCReAM, such as those specified in [RFC4585]. It is RECOMMENDED to use reduced-size RTCP [RFC5506], where regular full compound RTCP transmission is controlled by trr-int as described in [RFC4585] ๐ŸŒˆ


5. Discussion โœจ

this section covers a few discussion points ๐Ÿ’ฌ

  • Clock drift: SCReAM can suffer from the same issues with clock drift as is the case with LEDBAT [RFC6817]. However, Appendix A.2 in [RFC6817] describes ways to mitigate issues with clock drift.
  • Support for alternate ECN semantics: This specification adopts the proposal in [ALT-BACKOFF] to reduce the congestion window less when ECN-based congestion events are detected. Future work on Low Loss, Low Latency for Scalable throughput (L4S) may lead to updates in a future document that describes SCReAM support for L4S.
  • A new transport-layer feedback message (as specified in RFC 4585) could be standardized if the use of the already existing RTCP extensions as described in Section 4.2 is not deemed sufficient.
  • The target bitrate given by SCReAM is the bitrate including the RTP and Forward Error Correction (FEC) overhead. The media encoder SHOULD take this overhead into account when the media bitrate is set. This means that the media coder bitrate SHOULD be computed as
media_rate = target_bitrate - rtp_plus_fec_overhead_bitrate

It is not necessary to make a 100% perfect compensation for the overhead, as the SCReAM algorithm will inherently compensate for moderate errors. Under-compensating for the overhead has the effect of increasing jitter, while overcompensating will cause the bottleneck link to become underutilized ๐Ÿ“


6. Suggested Experiments ๐Ÿงช

SCReAM has been evaluated in a number of different ways, mostly in a simulator. The OpenWebRTC implementation work ([OpenWebRTC] and [SCReAM-implementation]) involved extensive testing with artificial bottlenecks with varying bandwidths and using two different video coders (OpenH264 and VP9) ๐Ÿ’œ

Preferably, further experiments will be done by means of implementation in real clients and web browsers. RECOMMENDED experiments are:

  • Trials with various access technologies: EDGE/3G/4G, Wi-Fi, DSL. Some experiments have already been carried out with LTE access; see [SCReAM-CPP-implementation] and [SCReAM-implementation-experience].
  • Trials with different kinds of media: Audio, video, slideshow content. Evaluation of multi-stream handling in SCReAM.
  • Evaluation of functionality of the compensation mechanism when there are competing flows: Evaluate how SCReAM performs with competing TCP-like traffic and to what extent the compensation for competing flows causes self-inflicted congestion.
  • Determine proper parameters: A set of default parameters are given that makes SCReAM work over a reasonably large operation range. However, for very low or very high bitrates, it may be necessary to use different values for the RAMP_UP_SPEED, for instance.
  • Experimentation with further improvements to the congestion window and media bitrate calculation. [SCReAM-CPP-implementation] implements some optimizations, not described in this memo, that improve performance slightly. Further experiments are likely to lead to more optimizations of the algorithm.

7. IANA Considerations ๐Ÿงš

this document does not require any IANA actions ๐Ÿซถ


8. Security Considerations ๐Ÿšจ

the feedback can be vulnerable to attacks similar to those that can affect TCP. It is therefore RECOMMENDED that the RTCP feedback is at least integrity protected. Furthermore, as SCReAM is self-clocked, a malicious middlebox can drop RTCP feedback packets and thus cause the self-clocking in SCReAM to stall. However, this attack is mitigated by the minimum send rate maintained by SCReAM when no feedback is received. ๐Ÿ’€

9. References ๐Ÿ“š

9.1. Normative References โœจ

Okay, these are the big ones bestie, the docs this spec straight up leans on ๐Ÿซถ Normative means you really need them to get the whole picture ๐Ÿ’…


9.2. Informative References ๐Ÿฆ‹

These ones are the extra reading, the background, the experiments and the implementations ๐Ÿ“š Super useful if you want to go deeper, no cap ๐ŸŒธ


Acknowledgements ๐ŸŒˆ

Huge thank-yous to everyone who helped make this happen ๐Ÿซถ Markus Andersson, Bo Burman, Tomas Frankkila, Frederic Gabin, Laurits Hamm, Hans Hannu, Nikolas Hermanns, Stefan Haakansson, Erlendur Karlsson, Daniel Lindstroem, Mats Nordberg, Jonathan Samuelsson, Rickard Sjoeberg, Robert Swain, Magnus Westerlund, and Stefan Aalund, you're all iconic ๐Ÿ‘‘ for the comments, questions, and support during the work that led to this memo.

Many, many more thanks to the RMCAT chairs, Karen E. E. Nielsen and Mirja Kuehlewind ๐Ÿ’œ They patiently read everything, suggested improvements, and kept asking all the difficult but necessary questions, which is literally the best kind of friend energy ๐Ÿ˜ญ

Thanks also to Stefan Holmer, Xiaoqing Zhu, Safiqul Islam, and David Hayes for the additional review of this document โœจ Thanks to Ralf Globisch for taking time to try out SCReAM in his challenging low-bitrate use cases ๐Ÿ“ Thanks to Robert Hedman for finding a few additional flaws in the running code ๐Ÿ™ˆ And thanks to Gustavo Garcia and 'miseri' for code contributions ๐Ÿ’…


Authors' Addresses ๐ŸŽ€

Ingemar Johansson

Ericsson AB
Laboratoriegraend 11
Luleaa  977 53
Sweden

Phone: +46 730783289 Email: ingemar.s.johansson@ericsson.com

Zaheduzzaman Sarker

Ericsson AB
Laboratoriegraend 11
Luleaa  977 53
Sweden

Phone: +46 761153743 Email: zaheduzzaman.sarker@ericsson.com

๐Ÿ“ฆ where this page comes from