Skip to content

Publish options and encoding

When you publish a track you can pass options that decide how it is encoded for that participant: the codec, the bitrate and the frame rate. Every publish goes to one participant, so every LocalTrackPublication has its own settings. You can send the same camera as VP8 to one participant and as H.264 to another, and change one participant's settings during the call without touching the others.

Capture settings are separate. CameraCaptureOptions decides what the camera produces; the options on this page decide what is sent. See Camera and capture options.

Defaults

Leaving out options: is the same as passing VideoPublishOptions(): the codec is VideoCodec.vp8AllKeyframe and maxFps is 15. Every frame is a keyframe, so a lost packet never leaves a broken picture on the other side, at the cost of more bandwidth. For audio, leaving out options: is the same as passing AudioPublishOptions().

A buffer track from LocalVideoTrack.createBufferTrack(name:source:) has its own defaults: name is "buffer" and source is .unknown, not a screen share. createBufferTrack has no statistics parameter; statistics for buffer tracks are switched on with all the others by Room.reportStatistics on Room.shared. See Your own frames and audio.

import RevueSDK

/// Send the camera with the default options: VP8 with every frame a keyframe, at most 15 fps.
@MainActor
func publishCameraWithDefaults(to participant: RemoteParticipant) async throws {
    guard let local = Room.shared.localParticipant else { return }
    try await local.publishCamera(to: participant)
}

/// Send the camera as H.264 at 2 Mbps and up to 24 fps.
@MainActor
func publishCameraAsH264(to participant: RemoteParticipant) async throws {
    guard let local = Room.shared.localParticipant else { return }
    let options = VideoPublishOptions(codec: .h264(H264Encoding(maxBitrate: 2_000_000)), maxFps: 24)
    try await local.publishCamera(to: participant, options: options)
}

/// Start from a VP8 preset and change one setting.
@MainActor
func publishCameraWithLowerBitrate(to participant: RemoteParticipant) async throws {
    guard let local = Room.shared.localParticipant else { return }
    let preset = Vp8Encoding.lowLatency
    let encoding = Vp8Encoding(
        maxBitrate: 1_000_000,
        cpuUsed: preset.cpuUsed,
        quantizerMin: preset.quantizerMin,
        quantizerMax: preset.quantizerMax,
        keyframeInterval: preset.keyframeInterval,
        rateControlMode: preset.rateControlMode,
        cqLevel: preset.cqLevel,
        temporalLayers: preset.temporalLayers
    )
    try await local.publishCamera(to: participant, options: VideoPublishOptions(codec: .vp8(encoding)))
}

Video publish options

VideoPublishOptions has three fields:

Field Default Meaning
codec vp8AllKeyframe The codec and its settings. See Codecs.
maxFps 15 The most frames per second sent to this participant. Extra frames are dropped before encoding.
degradationPreference balanced Not supported yet; see Degradation preference.

maxFps limits sending, not capture. When the camera captures fewer frames than maxFps, the capture rate is what you get; when it captures more, the extra frames are dropped.

Codecs

VideoCodec is the codec together with its settings, so a VP8 codec always carries VP8 settings and an H.264 codec carries H.264 settings:

VideoCodec.type gives the codec without its settings, as a VideoCodecType (vp8 or h264). Use it to compare codecs or as a dictionary key.

Five ready-made codecs:

Codec Codec type Bitrate Keyframe interval Rate control
vp8AllKeyframe (default) VP8 3 Mbps 1 vbr
vp8LowLatency VP8 3 Mbps 15 cbr
vp8HighQuality VP8 8 Mbps 60 vbr
vp8LowBandwidth VP8 1.5 Mbps 10 cq
h264Default H.264 3 Mbps — —

The VP8 ones are the Vp8Encoding presets of the same name: Vp8Encoding.allKeyframe, Vp8Encoding.lowLatency, Vp8Encoding.highQuality and Vp8Encoding.lowBandwidth.

Bitrate

maxBitrate, on both Vp8Encoding and H264Encoding, is the target bitrate in bits per second. The SDK passes it straight to the encoder, which aims for it; it is not a ceiling the SDK enforces on top. When the connection to the participant goes through a relay, the core lowers the bitrate to 1500 kbps on its own. The SDK does not lower the resolution or the frame rate to fit a slow connection; see Degradation preference.

VP8 settings

Vp8Encoding has a default for every field, and Vp8Encoding() is the same as Vp8Encoding.allKeyframe:

Field Range allKeyframe Meaning
maxBitrate above 0 3,000,000 Target bitrate in bits per second. See Bitrate.
cpuUsed -16 to 16 16 Lower values compress better and use more CPU.
quantizerMin 0 to 63 30 The best quality the encoder may use. Lower is sharper and larger.
quantizerMax 0 to 63, not below quantizerMin 40 The worst quality the encoder may use.
keyframeInterval 1 and up 1 A keyframe every this many frames. 1 makes every frame a keyframe.
rateControlMode RcMode vbr How the encoder trades bitrate for quality.
cqLevel 0 to 63 30 Target quality in cq mode; ignored in the other modes.
temporalLayers 1 to 3 1 See Temporal layers.

RcMode has three cases:

  • vbr: aims for quality; the bitrate varies.
  • cbr: keeps the bitrate steady; the quality varies.
  • cq: aims for the quality in cqLevel, with the bitrate as a secondary limit.

A shorter keyframeInterval recovers from lost packets sooner but uses more bandwidth.

Temporal layers

temporalLayers splits the frames into layers, so that a receiver could drop the upper layers and still decode the rest at a lower frame rate:

  • 1: no layers. Every frame is in the same layer.
  • 2: the base layer is every second frame and gets half the bitrate; the second layer is the frames in between.
  • 3: the base layer is every fourth frame and gets 40% of the bitrate; the second layer adds the frames halfway between, up to 60%; the third layer is the remaining frames.

Changing the number of layers restarts the encoder, so the video stalls briefly. Nothing ever uses the layers: the connection is peer to peer, so there is no server to drop layers for a slow participant, and the receiver never drops layers either. Setting 2 or 3 only costs you the restart and a lower picture quality at the same bitrate. Keep temporalLayers at 1.

H.264 settings

H264Encoding has one field, maxBitrate, the target bitrate in bits per second (default 3 Mbps). See Bitrate.

Change settings during a call

Each LocalTrackPublication keeps the options it was published with in videoPublishOptions, and has setters that change them while the call goes on. They apply to that participant only, and the call is not interrupted:

Call them on the main thread. They throw RoomError.trackNotPublished once the publication is unpublished, and RoomError.invalidParameter(name:reason:) for a bitrate of 0 or less or a frame rate out of range; see Errors.

import RevueSDK

/// Change what one participant receives while the call goes on. Each publication has its own
/// settings, so the other participants are not affected.
@MainActor
func sendSharperVideo(on publication: LocalTrackPublication) throws {
    switch publication.videoPublishOptions?.codec.type {
    case .vp8:
        try publication.setVp8Encoding(.highQuality)
    case .h264:
        try publication.setH264Encoding(H264Encoding(maxBitrate: 6_000_000))
    case nil:
        return
    }
    try publication.setMaxFps(30)
}

Audio publish options

AudioPublishOptions has one field, encoding, an AudioEncoding with a maxBitrate in bits per second. AudioEncoding has three presets: presetSpeech (24 kbps), presetMusic (128 kbps) and presetMusicHighQuality (192 kbps).

Not supported yet

AudioPublishOptions.encoding has no effect yet: setting it does not change the audio bitrate.

Degradation preference

DegradationPreference says what to give up first when bandwidth runs short: balanced (both), maintainResolution (lower the frame rate first), maintainFramerate (lower the resolution first), or disabled. You pass it as VideoPublishOptions.degradationPreference.

Not supported yet

VideoPublishOptions.degradationPreference has no effect yet, whichever DegradationPreference you pass. The SDK does not lower the resolution or the frame rate when bandwidth runs short.