Expand description
§Vidaptive
Responsive rate control for real-time video.
Vidaptive decouples encoder output from wire rate: a pluggable congestion controller and pacer decide when bytes leave the sender, while dummy padding keeps the feedback loop tight when the media queue is empty. An α optimizer adapts the encoder target bitrate to track link capacity.
The crate is codec-agnostic and packetization-agnostic — you enqueue pre-formed wire packets (MediaPacket). Chunking, FEC, RTP, and framing are your responsibility.
§Results vs GCC
In the Vidaptive evaluation (WebRTC M108, 15 cellular traces, 20× 1080p clips, 2 min per run, Copa as CC), the algorithm was compared head-to-head with Google Congestion Control (GCC):
| Metric | GCC | Vidaptive | Δ |
|---|---|---|---|
| Video bitrate (avg) | — | — | ~1.5× |
| VMAF (avg / P50) | 39 / 36.8 | 53.5 / 58.2 | +40% avg |
| SSIM (P50) | 12 dB | 13.8 dB | +1.4 dB |
| PSNR (median) | 37.5 dB | 38.8 dB | +1.3 dB |
| P95 frame latency | 3.94 s | 1.70 s | −2.2 s (−57%) |
| Median frame latency | 48 ms | 65 ms | +17 ms |
| Avg frame latency | 734 ms | 383 ms | −351 ms |
GCC ties send rate to encoder output, so it ramps slowly and under-uses the link; tail latency spikes when capacity drops. Vidaptive keeps the congestion-control loop fed (pacer + dummy padding) and sets encoder bitrate separately — more throughput and quality, much lower P95 latency, at the cost of slightly higher median delay from probing.
§Features
- Pluggable CC —
CongestionControllertrait;CopaandNoopCcfor tests. - Backlog filler —
DummyFiller(≤ 200 B) when the media queue is empty but the CC can send. - Pacing — interval pacing at
CC-Rate; cwnd gate viaCongestionController::can_send. - Encoder pause — stop encoding when the oldest queued packet waits longer than τ (default 33 ms).
- Held capture — on resume, encode a recently held camera frame or skip if stale.
- α optimizer — percentile control on frame service times; EWMA-smoothed target bitrate
α · CC-Rate · 8. - Filler policy — withhold dummy before the next expected frame; stop dummy above a configurable video send-rate cap.
- Pre-formed ingress — no internal MTU split; optional
counts_as_videofor non-video packets (e.g. parity).
§When to use vidaptive
Good fits: live video over UDP or similar datagram transports, custom send stacks, trace simulators, any setup where you already have ACK/loss feedback for a CC.
Poor fits: VOD / TCP bulk transfer, systems without send-side CC feedback, drop-in WebRTC (no RTP/SRTP/codec here).
§Architecture
┌─────────────┐ pre-formed ┌──────────────────────────────┐ ┌──────┐
│ Encoder / │ MediaPackets │ Vidaptive │ │ Wire │
│ packetizer │ ────────────────────► │ CC + pacer + α + safeguards │ ──► │ … │
└─────────────┘ │ + DummyFiller for gaps │ └──────┘
▲ └──────────────────────────────┘
│ EncoderAdvice ▲ ACK / loss
└────────────────────────────────────┘Vidaptive queues, paces, and congestion-controls bytes. It does not packetize or encode.
§Core concepts
§Media packet (ingress)
Each MediaPacket is one on-wire payload:
| Field | Meaning |
|---|---|
payload | Bytes to send |
fin | Last packet of the logical frame (ends service-time sample for α) |
counts_as_video | When false, bytes are still paced and CC’d but excluded from the video-bitrate filler cap |
Use chunk_payload to split a blob into fixed-size packets for tests or simple UDP paths.
§Dummy filler
When the pacer is ready, cwnd allows sending, and the media queue is empty, DummyFiller emits small zero payloads so the CC sees continuous traffic. Dummy is suppressed when:
- the encoder is paused,
- video send rate in the optimizer window exceeds
max_video_bitrate, - within
filler_withhold_before_frameof the next expected camera frame.
§Frame service time
For α: time from when the first packet of a frame reaches the head of the pacer queue until the last packet (fin) is sent.
§Target bitrate
Once per optimizer_window, Vidaptive updates:
target_bitrate_bps = α · CC-Rate · 8α is derived from recent frame service times and the configured percentile / target service time.
§Encoder pause & held capture
If the oldest queued packet age exceeds tau_pause, encoding pauses (target_bitrate_bps = 0). On resume:
- held capture not older than half a frame interval →
CaptureAdvice::EncodeHeld, - older →
CaptureAdvice::SkipHeld.
§Usage
use std::time::Instant;
use vidaptive::{
chunk_payload, CaptureAdvice, DummyFiller, EncodedFrame, NoopCc, TransmitAction,
Vidaptive, VidaptiveConfig,
};
let cc = NoopCc::new(500_000, 500_000, std::time::Duration::from_millis(50));
let config = VidaptiveConfig::builder().build().unwrap();
let mut session = Vidaptive::new(cc, DummyFiller::new(), config);
let mut now = Instant::now();
let frame = EncodedFrame {
id: 1,
captured_at: now,
target_bitrate: session.poll_encoder(now).target_bitrate_bps,
packets: chunk_payload(vec![0u8; 4_000], 1_200),
};
session.enqueue_packets(frame, now);
match session.advise_capture(now, now) {
CaptureAdvice::Encode => { /* encode new capture */ }
CaptureAdvice::Hold => { /* wait */ }
CaptureAdvice::EncodeHeld { captured_at } => { /* encode held frame */ }
CaptureAdvice::SkipHeld { .. } => { /* drop stale hold */ }
}
loop {
session.tick(now);
if let Some(action) = session.poll_transmit(now) {
match action {
TransmitAction::SendPacket { payload } => { /* send */ }
TransmitAction::SendFiller { payload } => { /* send dummy */ }
TransmitAction::WaitUntil(t) => { now = t; }
TransmitAction::Idle => {}
}
}
// session.on_network_ack(ack, now);
// session.on_network_loss(loss, now);
}§Configuration reference
Build with VidaptiveConfig::builder() → … → .build(). Invalid combinations return ConfigError at build time (bad percentiles, zero durations, withhold longer than frame interval, etc.).
Default builder values (@ 30 fps):
| Parameter | Default | Role |
|---|---|---|
tau_pause | 33 ms | Pause encoding if oldest queued packet is older than this |
target_service_time | 33 ms | Target frame service time for α |
latency_percentile | 0.9 | Percentile for service-time control |
optimizer_window | 1 s | α update period; video bitrate measurement window |
alpha_ewma | 0.25 | EWMA smoothing for α |
max_video_bitrate | 12 Mbps | Stop dummy above this video send rate |
filler_withhold_before_frame | 8.25 ms | No dummy just before next capture |
frame_interval | 33 ms | Expected time between camera frames |
use core::num::NonZeroU64;
use core::time::Duration;
use vidaptive::VidaptiveConfig;
let config = VidaptiveConfig::builder()
.frame_rate_hz(NonZeroU64::new(30).unwrap())
.tau_pause(Duration::from_millis(33))
.latency_percentile(0.9)
.build()
.unwrap();VidaptiveConfig::default() is equivalent to builder().build() with the defaults above.
§tau_pause
When oldest_queued_age > tau_pause, EncoderAdvice.pause = true and target_bitrate_bps = 0. Clears when the media queue drains. Set roughly to one frame interval at your capture rate.
§target_service_time and latency_percentile
Control the latency vs quality tradeoff in α. Higher percentile or lower target service time → more conservative α.
§optimizer_window
How quickly α reacts to new samples; also the window for the video-bitrate filler cap.
§max_video_bitrate and filler_withhold_before_frame
Limit dummy traffic when video already fills the link; avoid dummy bursts right before a scheduled capture.
§Consolidated event API
use vidaptive::{VidaptiveInput, VidaptiveOutput};
let output: VidaptiveOutput = session.handle(VidaptiveInput::Tick, now);§Further reading
- Vidaptive paper (arXiv) — methodology and full evaluation
- Copa
§License
Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0).
Re-exports§
pub use config::ConfigError;pub use config::VidaptiveConfig;
Modules§
- config
- Session configuration.
Structs§
- AckInfo
- Receiver acknowledgement for a previously sent packet.
- Copa
- Copa congestion controller.
- Copa
Config - Copa configuration.
- Dummy
Filler - Generates zero-filled dummy packets.
- Encoded
Frame - A logical encoded frame: metadata plus pre-packetized wire payloads.
- Encoder
Advice - Advice from Vidaptive to the video encoder.
- Lost
Packet - Loss notification for a previously sent packet.
- Media
Packet - One wire-ready media packet queued in the pacer.
- NoopCc
- Congestion controller with constant
pacing_rateandcwnd. - Sent
Packet - Congestion-control notification for a sent packet.
- Vidaptive
- Main Vidaptive session wiring CC, pacer, filler, safeguards, and α control.
- Vidaptive
Output - Consolidated output from
Enums§
- Capture
Advice - Guidance for whether to encode a newly captured camera frame.
- Transmit
Action - Action the application should take on the wire.
- Vidaptive
Input - Consolidated input event
Constants§
- DEFAULT_
DUMMY_ PACKET_ SIZE - Maximum dummy payload size (default 200 B).
Traits§
- Backlog
Filler - Produces dummy filler packets when the pacer is ready but the video queue is empty.
- Congestion
Controller - Bandwidth estimator
Functions§
- chunk_
payload - Splits a payload into fixed-size packets for tests and plain-UDP paths without FEC.