Skip to content

Adaptive bitrate

By default Jadarit passes video through exactly as it was published, and every viewer receives that one bitrate. That costs almost no CPU, and it is the right choice when your viewers have good connections.

Turn on adaptive bitrate when they do not. Jadarit then encodes a ladder of lower renditions alongside the original, and each viewer moves between them as their connection changes: no stalls on a weak mobile signal, full quality where the network allows it.

It is set per application, off until you turn it on, and an individual stream can have its own ladder or follow the application’s.

In the dashboard, open the application’s Settings tab, find Adaptive bitrate, choose On, and tick the renditions to offer. Each live stream’s card also has an Adaptive bitrate option for giving that one stream its own ladder.

Through the API:

Terminal window
curl -b jar -X PUT http://localhost:9001/api/v1/apps/default \
-H 'content-type: application/json' \
-d '{"abr": {"enabled": true, "rungs": [
{"name": "1080p", "height": 1080, "video_bitrate_kbps": 5000, "max_bitrate_kbps": 5500, "audio_bitrate_kbps": 128, "preset": "veryfast"},
{"name": "720p", "height": 720, "video_bitrate_kbps": 2800, "max_bitrate_kbps": 3000, "audio_bitrate_kbps": 128, "preset": "veryfast"},
{"name": "480p", "height": 480, "video_bitrate_kbps": 1400, "max_bitrate_kbps": 1600, "audio_bitrate_kbps": 96, "preset": "veryfast"},
{"name": "360p", "height": 360, "video_bitrate_kbps": 800, "max_bitrate_kbps": 900, "audio_bitrate_kbps": 96, "preset": "veryfast"}
]}}'

That is the default ladder. Streams that start after the change get it; a stream already live keeps what it started with until it is published again.

For a single stream:

Terminal window
# Its own ladder
curl -b jar -X PUT http://localhost:9001/api/v1/apps/default/stream-settings/mystream \
-H 'content-type: application/json' \
-d '{"abr": {"enabled": true, "rungs": [ ... ]}}'
# Back to following the application
curl -b jar -X PUT http://localhost:9001/api/v1/apps/default/stream-settings/mystream \
-H 'content-type: application/json' -d '{"abr": null}'
  • Nothing is upscaled. Renditions taller than the incoming stream are skipped for that stream, so a 720p source gets 720p and below.
  • A rendition that matches the source is passed through, not encoded, as long as the publisher sends keyframes at a steady interval. In OBS that is Keyframe Interval: 2 s under Output settings. The top quality is then bit for bit what you published, and costs nothing.
  • Every other rendition is a live encode with x264, using CPU for as long as the stream runs. The encodes share keyframe positions with each other, so a player can switch at any segment without a glitch.

Encoding is the expensive part. Each encoded rendition of each live stream takes a share of a CPU core for the whole broadcast, so size the ladder to the machine: fewer renditions, lower heights, or ABR only on the applications that need it. The Encoding speed setting trades CPU for picture quality; the default, veryfast, suits most servers.

HLS. The stream’s usual playlist becomes a multivariant playlist listing every rendition, and the player picks between them itself, segment by segment:

http://your-host:9001/live/default/mystream/playlist.m3u8

Nothing changes for players: the URL is the same with ABR on or off.

WebRTC. A WebRTC viewer has one continuous track and no playlist, so the server chooses for it. Every viewer starts on the original stream, and the server moves it between renditions at keyframes, following its bandwidth estimate. The built-in player also shows a quality menu, so a viewer can pick a rendition and hold it.

Your own WebRTC player can offer the same menu by opening a data channel named quality on the WHEP connection. The server sends the list of renditions:

{"type": "layers", "layers": [{"id": 0, "label": "1080p", "height": 1080},
{"id": 1, "label": "720p", "height": 720}],
"current": 0, "auto": true}

Layer 0 is always the original stream, labelled with its height (or Source when that is not known yet).

and a {"type": "current", ...} message whenever the rendition changes. Send {"type": "select", "layer": 1} to hold a rendition, or "layer": null to let the server choose again.

Each rendition gets its own copy of an ad break, cut at the same points, so a viewer who changes quality during a break never sees a jump. Overlays are drawn onto every rendition the same way; see Overlays.