Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions public/realtime/static/calls-api-2024-05-21.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ paths:
sessionId: 2a45361d5fd7cc14eface0587c276c94
trackName: generated-audio
kind: "audio"
remote_track_with_simulcast:
description: Pull a remote track with simulcast preferences
value:
tracks:
- location: remote
sessionId: 2a45361d5fd7cc14eface0587c276c94
trackName: simulcast-video-track
simulcast:
preferredRid: "h"
priorityOrdering: "asciibetical"
ridUnavailableStrategy: "nextPriority"
security:
- secret: []
parameters:
Expand Down Expand Up @@ -207,6 +218,32 @@ paths:
a=rtpmap:96 VP8/90000
...
type: offer
remote_tracks_with_simulcast:
value:
requiresImmediateRenegotiation: true
tracks:
- sessionId: 2a45361d5fd7cc14eface0587c276c94
trackName: simulcast-video-track
mid: "5"
simulcast:
preferredRid: "h"
priorityOrdering: "asciibetical"
ridUnavailableStrategy: "nextPriority"
sessionDescription:
sdp: |
v=0
o=- 0 0 IN IP4 127.0.0.1
s=-
c=IN IP4 127.0.0.1
t=0 0
m=video 4002 RTP/AVP 96
a=rtpmap:96 VP8/90000
a=simulcast:recv f;h;q
a=rid:f recv
a=rid:h recv
a=rid:q recv
...
type: offer
/apps/{appId}/sessions/{sessionId}/renegotiate:
put:
tags:
Expand Down Expand Up @@ -329,6 +366,73 @@ paths:
requiresImmediateRenegotiation: false
tracks:
- mid: "7"
/apps/{appId}/sessions/{sessionId}/tracks/update:
put:
tags:
- Change tracks
summary: Change tracks by reusing existing transceivers
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateTracksRequest"
examples:
reuse_transceiver:
description: Reuse an existing transceiver for a new track
value:
tracks:
- location: "remote"
sessionId: "2a45361d5fd7cc14eface0587c276c94"
trackName: "other-track-name"
mid: "7"
reuse_with_simulcast:
description: Reuse an existing transceiver with new simulcast preferences
value:
tracks:
- location: "remote"
sessionId: "2a45361d5fd7cc14eface0587c276c94"
trackName: "simulcast-track"
mid: "8"
simulcast:
preferredRid: "h"
priorityOrdering: "asciibetical"
ridUnavailableStrategy: "nextPriority"
security:
- secret: []
parameters:
- in: path
name: appId
schema:
type: string
required: true
description: WebRTC application ID
- in: path
name: sessionId
schema:
type: string
required: true
description: Current PeerConnection session ID
responses:
"200":
description: OK
headers:
vary:
schema:
type: string
example: Origin
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateTracksResponse"
examples:
success:
value:
requiresImmediateRenegotiation: false
tracks:
- mid: "7"
sessionId: "2a45361d5fd7cc14eface0587c276c94"
trackName: "new-track-name"

/apps/{appId}/sessions/{sessionId}:
get:
tags:
Expand Down Expand Up @@ -412,6 +516,27 @@ components:
kind:
type: string
description: Give a hint to the SFU about the transceiver kind. This is required when the SFU generates the offer
simulcast:
type: object
description: Simulcast configuration for the track
properties:
preferredRid:
type: string
description: Preferred RID for simulcast streams
priorityOrdering:
type: string
enum:
- none
- asciibetical
default: none
description: Controls what happens if there is not enough network resources available to send the preferredRid. 'none' means keep sending even if not enough bandwidth, 'asciibetical' uses a-z order to determine priority where a is most desirable and z is least desirable.
ridUnavailableStrategy:
type: string
enum:
- none
- nextPriority
default: none
description: Controls what happens when the rid currently being used or preferredRid is no longer being sent by the publisher. 'none' means do nothing, 'nextPriority' uses the next on the priorityOrdering.
CloseTrackObject:
type: object
properties:
Expand Down Expand Up @@ -543,3 +668,44 @@ components:
type: string
sessionDescription:
$ref: "#/components/schemas/SessionDescription"
ChangeTracksRequest:
type: object
properties:
tracks:
type: object
additionalProperties:
allOf:
- $ref: "#/components/schemas/TrackObject"
description: Map of track IDs to track objects for changing tracks
sessionDescription:
$ref: "#/components/schemas/SessionDescription"
UpdateTracksRequest:
type: object
properties:
tracks:
type: array
items:
$ref: "#/components/schemas/TrackObject"
description: Array of track objects for updating tracks
sessionDescription:
$ref: "#/components/schemas/SessionDescription"
UpdateTracksResponse:
type: object
properties:
errorCode:
type: string
errorDescription:
type: string
requiresImmediateRenegotiation:
type: boolean
tracks:
type: array
items:
allOf:
- $ref: "#/components/schemas/TrackObject"
- properties:
errorCode:
type: string
errorDescription:
type: string
description: Array of track objects with results
87 changes: 87 additions & 0 deletions src/content/docs/realtime/simulcast.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
pcx_content_type: get-started
title: Simulcast
sidebar:
order: 8
---

Simulcast is a feature of WebRTC that allows a publisher to send multiple streams of the same media at different qualities. For example, this is useful for scenarios where you want to send a high quality stream for desktop users and a lower quality stream for mobile users.

```mermaid
graph LR
A[Publisher] -->|Low quality| B[Cloudflare Calls SFU]
A -->|Medium quality| B
A -->|High quality| B
B -->|Low quality| C@{ shape: procs, label: "Subscribers"}
B -->|Medium quality| D@{ shape: procs, label: "Subscribers"}
B -->|High quality| E@{ shape: procs, label: "Subscribers"}

```

### How it works

Simulcast in WebRTC allows a single media source, like a camera or screen share, to be encoded at multiple quality levels and sent simultaneously, which is beneficial for subscribers with varying network conditions and device capabilities. The media source is encoded into multiple streams, each identified by RIDs (RTP Stream Identifiers) for different quality levels, such as low, medium, and high. These simulcast streams are described in the SDP you send to Cloudflare Calls SFU. It's the responsibility of the Cloudflare Calls SFU to ensure that the appropriate quality stream is delivered to each subscriber based on their network conditions and device capabilities.

Cloudflare Calls SFU will automatically handle the simulcast configuration based on the SDP you send to it from the publisher. The SFU will then automatically switch between the different quality levels based on the subscriber's network conditions. You can control the quality switching behavior using the `simulcast` configuration object when you send an API call to start pulling a remote track.

### Quality Control

The `simulcast` configuration object in the API call when you start pulling a remote track allows you to specify:

- `preferredRid`: The preferred stream (RID for the simulcast stream. [RIDs can be specified by the publisher.](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/setParameters#encodings))
- `priorityOrdering`: Controls how the SFU handles bandwidth constraints
- `none`: Keep sending the preferred layer even if there's not enough bandwidth
- `asciibetical`: Use alphabetical ordering (a-z) to determine priority, where 'a' is most desirable and 'z' is least desirable
- `ridUnavailableStrategy`: Controls what happens when the preferred RID is no longer available, for example when the publisher stops sending it

- `none`: Do nothing
- `nextPriority`: Switch to the next available RID based on the priority ordering

You will likely want to order the asciibetical RIDs based on your desired metric, such as higest resoltion to lowest or highest bandwidth to lowest.

### Bandwidth Management across media tracks

Cloudflare Calls treats all media tracks equally at the transport level. For example, if you have multiple video tracks (cameras, screen shares, etc.), they all have equal priority for bandwidth allocation. This means:

1. Each track's simulcast configuration is handled independently
1. The SFU performs automatic bandwidth estimation and layer switching based on network conditions independently for each track

### Layer Switching Behavior

When a layer switch is requested (through updating `preferredRid`) with the `/tracks/update` API:

1. The SFU will automatically generate a Picture Loss Indication (PLI)
2. Layer switching only occurs when a keyframe arrives on the target layer
3. PLI generation is debounced to prevent excessive requests

### Publisher Configuration

For publishers (local tracks), you only need to include the simulcast attributes in your SDP. The SFU will automatically handle the simulcast configuration based on the SDP. For example, the SDP should contain a section like this:

```sdp
a=simulcast:send f;h;q
a=rid:f send
a=rid:h send
a=rid:q send
```

You can include these by specifying `sendEncodings` when creating the transceiver:

```js
const transceiver = peerConnection.addTransceiver(track, {
direction: "sendonly",
sendEncodings: [
{ scaleResolutionDownBy: 1, rid: "a" },
{ scaleResolutionDownBy: 2, rid: "b" },
{ scaleResolutionDownBy: 4, rid: "c" }
]
});
## Example

Here's an example of how to use simulcast with Cloudflare Calls:

1. Create a new local track with simulcast configuration. There should be a section in the SDP with `a=simulcast:send`.
2. Use the [Cloudflare Calls API](/calls/https-api) to push this local track, by calling the /tracks/new endpoint.
3. Use the [Cloudflare Calls API](/calls/https-api) to start pulling a remote track (from another browser or device), by calling the /tracks/new endpoint and specifying the `simulcast` configuration object along with the remote track ID you get from step 2.

For more examples, check out the [Calls Examples GitHub repository](https://github.com/cloudflare/calls-examples/tree/main/simulcast).