tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

index.md (8409B)


<!-- go/cmark --> <!--* freshness: {owner: 'sprang' reviewed: '2021-04-12'} *-->

Paced Sending

The paced sender, often referred to as just the "pacer", is a part of the WebRTC RTP stack used primarily to smooth the flow of packets sent onto the network.

Background

Consider a video stream at 5Mbps and 30fps. This would in an ideal world result in each frame being ~21kB large and packetized into 18 RTP packets. While the average bitrate over say a one second sliding window would be a correct 5Mbps, on a shorter time scale it can be seen as a burst of 167Mbps every 33ms, each followed by a 32ms silent period. Further, it is quite common that video encoders overshoot the target frame size in case of sudden movement especially dealing with screensharing. Frames being 10x or even 100x larger than the ideal size is an all too real scenario. These packet bursts can cause several issues, such as congesting networks and causing buffer bloat or even packet loss. Most sessions have more than one media stream, e.g. a video and an audio track. If you put a frame on the wire in one go, and those packets take 100ms to reach the other side - that means you have now blocked any audio packets from reaching the remote end in time as well.

The paced sender solves this by having a buffer in which media is queued, and then using a leaky bucket algorithm to pace them onto the network. The buffer contains separate fifo streams for all media tracks so that e.g. audio can be prioritized over video - and equal prio streams can be sent in a round-robin fashion to avoid any one stream blocking others.

Since the pacer is in control of the bitrate sent on the wire, it is also used to generate padding in cases where a minimum send rate is required - and to generate packet trains if bitrate probing is used.

Life of a Packet

The typical path for media packets when using the paced sender looks something like this:

  1. const display; Atom wmstate_ = None; Atom windowtype = None; Atom
  2. const display; Atom wmstate_ = None; Atom windowtype = None; Atom
  3. const display; Atom wmstate_ = None; Atom windowtype = None; Atom

batch.

  1. const display; Atom wmstate_ = None; Atom windowtype = None; Atom

to send them.

  1. const display; Atom wmstate_ = None; Atom windowtype = None; Atom

callback method, normally implemented by the [PacketRouter] class.

  1. const display; Atom wmstate_ = None; Atom windowtype = None; Atom

packet's SSRC, and in which the RTPSenderEgress class makes final time stamping, potentially records it for retransmissions etc.

  1. const display; Atom wmstate_ = None; Atom windowtype = None; Atom

now out of scope.

Asynchronously to this, the estimated available send bandwidth is determined - and the target send rate is set on the RtpPacketPacer via the `void SetPacingRates(DataRate pacingrate, DataRate paddingrate)` method.

Packet Prioritization

The pacer prioritized packets based on two criteria:

1. Audio 2. Retransmissions 3. Video and FEC 4. Padding

The enqueue order is enforced on a per stream (SSRC) basis. Given equal priority, the [RoundRobinPacketQueue] alternates between media streams to ensure no stream needlessly blocks others.

Implementations

The main class to use is called [TaskQueuePacedSender]. It uses a task queue to manage thread safety and schedule delayed tasks, but delegates most of the actual work to the PacingController class. This way, it's possible to develop a custom pacer with different scheduling mechanism - but ratain the same pacing logic.

The Packet Router

An adjacent component called [PacketRouter] is used to route packets coming out of the pacer and into the correct RTP module. It has the following functions:

the packet for further routing to the network.

sequence number extension.

with the last module to have sent media always being the first choice.

At present the FEC is generated on a per SSRC basis, so is always returned from an RTP module after sending media. Hopefully one day we will support covering multiple streams with a single FlexFEC stream - and the packet router is the likely place for that FEC generator to live. It may even be used for FEC padding as an alternative to RTX.

The API

The section outlines the classes and methods relevant to a few different use cases of the pacer.

Packet sending

For sending packets, use `RtpPacketSender::EnqueuePackets(std::vector<std::unique_ptr<RtpPacketToSend>> packets) The pacer takes a PacingController::PacketSender` as constructor argument, this callback is used when it's time to actually send packets.

Send rates

To control the send rate, use `void SetPacingRates(DataRate pacing_rate, DataRate padding_rate)` If the packet queue becomes empty and the send rate drops below padding_rate, the pacer will request padding packets from the PacketRouter.

In order to completely suspend/resume sending data (e.g. due to network availability), use the Pause() and Resume() methods.

The specified pacing rate may be overriden in some cases, e.g. due to extreme encoder overshoot. Use void SetQueueTimeLimit(TimeDelta limit) to specify the longest time you want packets to spend waiting in the pacer queue (pausing excluded). The actual send rate may then be increased past the pacing_rate to try to make the average queue time less than that requested limit. The rationale for this is that if the send queue is say longer than three seconds, it's better to risk packet loss and then try to recover using a key-frame rather than cause severe delays.

Bandwidth estimation

If the bandwidth estimator supports bandwidth probing, it may request a cluster of packets to be sent at a specified rate in order to gauge if this causes increased delay/loss on the network. Use the void CreateProbeCluster(...) method - packets sent via this PacketRouter will be marked with the corresponding cluster_id in the attached PacedPacketInfo struct.

If congestion window pushback is used, the state can be updated using SetCongestionWindow() and UpdateOutstandingData().

A few more methods control how we pace: * SetAccountForAudioPackets() determines if audio packets count into bandwidth consumed. * SetIncludeOverhead() determines if the entire RTP packet size counts into bandwidth used (otherwise just media payload). * SetTransportOverhead() sets an additional data size consumed per packet, representing e.g. UDP/IP headers.

Stats

Several methods are used to gather statistics in pacer state:

added.

[RTPSender]: https://source.chromium.org/chromium/chromium/src/+/main:thirdparty/webrtc/modules/rtprtcp/source/rtp_sender.h;drc=77ee8542dd35d5143b5788ddf47fb7cdb96eb08e [RtpPacketSender]: https://source.chromium.org/chromium/chromium/src/+/main:thirdparty/webrtc/api/rtppacket_sender.h;drc=ea55b0872f14faab23a4e5dbcb6956369c8ed5dc [RtpPacketPacer]: https://source.chromium.org/chromium/chromium/src/+/main:thirdparty/webrtc/modules/pacing/rtppacket_pacer.h;drc=e7bc3a347760023dd4840cf6ebdd1e6c8592f4d7 [PacketRouter]: https://source.chromium.org/chromium/chromium/src/+/main:thirdparty/webrtc/modules/pacing/packetrouter.h;drc=3d2210876e31d0bb5c7de88b27fd02ceb1f4e03e [TaskQueuePacedSender]: https://source.chromium.org/chromium/chromium/src/+/main:thirdparty/webrtc/modules/pacing/taskqueuepacedsender.h;drc=5051693ada61bc7b78855c6fb3fa87a0394fa813 [RoundRobinPacketQueue]: https://source.chromium.org/chromium/chromium/src/+/main:thirdparty/webrtc/modules/pacing/roundrobinpacketqueue.h;drc=b571ff48f8fe07678da5a854cd6c3f5dde02855f