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.vp8(_:)takes aVp8Encoding. The SDK encodes VP8 in software.VideoCodec.h264(_:)takes anH264Encoding. The device's hardware encoder encodes H.264.
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 incqLevel, 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:
LocalTrackPublication.setCodec(_:)switches the codec, settings included.LocalTrackPublication.setVp8Encoding(_:)andLocalTrackPublication.setH264Encoding(_:)change the settings without switching the codec. They throwRoomError.codecNotSupported(codec:)when the publication uses the other codec.LocalTrackPublication.setMaxFps(_:)changesmaxFps; it accepts 1 to 60.LocalTrackPublication.requestKeyframe()asks the encoder for a keyframe now.
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).
尚未支援
AudioPublishOptions.encoding 目前沒有作用:設定它不會改變音訊碼率。
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.
尚未支援
VideoPublishOptions.degradationPreference 目前沒有作用,不論傳哪個 DegradationPreference。 頻寬不足時,SDK 不會降低解析度或幀率。