Skip to content
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ logging:
template_base: can be used to host custom templates (default http://localhost:<template_port>/)
backup_storage: files will be moved here when uploads fail. location must have write access granted for all users
enable_chrome_sandbox: if true, egress will run Chrome with sandboxing enabled. This requires a specific Docker setup, see below.
enable_video_passthrough: if true, track composite and participant egress with HLS segment output may skip video decoding/encoding when the published track is already H.264 and no encoding options were requested. Falls back to transcoding otherwise (default false).
Note on segment durations: in pass-through mode segment boundaries depend on the publisher's keyframe interval and the keyframe request (PLI) round trip, not on a local encoder, so real segment durations vary (typically nominal + up to one GOP + RTT). #EXTINF entries always carry the measured duration and EXT-X-TARGETDURATION is declared as 2x the nominal segment duration for spec compliance. Players that size their live window by segment count (e.g. hls.js liveSyncDurationCount) will see slightly variable display latency.
cpu_cost: # optionally override cpu cost estimation, used when accepting or denying requests
room_composite_cpu_cost: 3.0
web_cpu_cost: 3.0
Expand Down
1 change: 1 addition & 0 deletions pkg/config/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type BaseConfig struct {
LatencyOverrides map[types.RequestType]LatencyConfig `yaml:"latency_overrides"` // latency overrides for different request types, experimental only, will be removed
EnableOneShotSenderReportSync bool `yaml:"enable_one_shot_sender_report_sync"` // temporary rollout flag enabling one-shot sender report correction for room composite / track requests that previously used audio PTS adjustment disabling
EnableSyncEngine bool `yaml:"enable_sync_engine"` // use Chrome-inspired sync engine for improved cross-participant alignment and A/V sync
EnableVideoPassthrough bool `yaml:"enable_video_passthrough"` // allow skipping video decode/encode for single-track H.264 HLS egress when the input codec already matches the output
AudioTempoController AudioTempoController `yaml:"audio_tempo_controller"` // audio tempo controller
TestOverrides TestOverrides `yaml:"test_overrides"` // set of config overrides for testing purposes
}
Expand Down
24 changes: 22 additions & 2 deletions pkg/config/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func (p *PipelineConfig) updateEncodedOutputs(req egress.EncodedOutput) error {
p.OutputCount.Inc()
p.FinalizationRequired = true
if p.VideoEnabled {
p.DisableVideoPassthrough("file output")
p.VideoEncoding = true
}

Expand Down Expand Up @@ -120,6 +121,7 @@ func (p *PipelineConfig) updateEncodedOutputs(req egress.EncodedOutput) error {
p.Outputs[types.EgressTypeStream] = []OutputConfig{conf}
p.OutputCount.Add(int32(len(stream.Urls)))
if p.VideoEnabled {
p.DisableVideoPassthrough("stream output")
p.VideoEncoding = true
}

Expand Down Expand Up @@ -161,7 +163,7 @@ func (p *PipelineConfig) updateEncodedOutputs(req egress.EncodedOutput) error {
p.Outputs[types.EgressTypeSegments] = []OutputConfig{conf}
p.OutputCount.Inc()
p.FinalizationRequired = true
if p.VideoEnabled {
if p.VideoEnabled && !p.VideoPassthrough {
p.VideoEncoding = true
}

Expand Down Expand Up @@ -189,6 +191,8 @@ func (p *PipelineConfig) updateEncodedOutputs(req egress.EncodedOutput) error {
return errors.ErrInvalidInput("audio_only images")
}

p.DisableVideoPassthrough("image output")

if len(p.Outputs) == 0 {
// enforce video only
p.AudioEnabled = false
Expand All @@ -214,6 +218,12 @@ func (p *PipelineConfig) updateEncodedOutputs(req egress.EncodedOutput) error {
return errors.ErrInvalidInput("output")
}

// if passthrough was disabled after the segment output was parsed (e.g. by
// an image output), the segment encoder still needs to be enabled
if p.VideoEnabled && !p.VideoPassthrough && p.Outputs[types.EgressTypeSegments] != nil {
p.VideoEncoding = true
}

return nil
}

Expand Down Expand Up @@ -256,6 +266,7 @@ func (p *PipelineConfig) updateOutputs(req egress.EgressRequest) error {
p.OutputCount.Inc()
p.FinalizationRequired = true
if p.VideoEnabled {
p.DisableVideoPassthrough("file output")
p.VideoEncoding = true
}

Expand Down Expand Up @@ -319,6 +330,7 @@ func (p *PipelineConfig) updateOutputs(req egress.EgressRequest) error {
p.Outputs[egressType] = []OutputConfig{conf}
p.OutputCount.Add(int32(len(stream.Urls)))
if p.VideoEnabled {
p.DisableVideoPassthrough("stream output")
p.VideoEncoding = true
}

Expand All @@ -344,7 +356,7 @@ func (p *PipelineConfig) updateOutputs(req egress.EgressRequest) error {
p.Outputs[types.EgressTypeSegments] = []OutputConfig{conf}
p.OutputCount.Inc()
p.FinalizationRequired = true
if p.VideoEnabled {
if p.VideoEnabled && !p.VideoPassthrough {
p.VideoEncoding = true
}

Expand All @@ -355,6 +367,8 @@ func (p *PipelineConfig) updateOutputs(req egress.EgressRequest) error {
return errors.ErrInvalidInput("audio_only images")
}

p.DisableVideoPassthrough("image output")

conf, err := p.getImageConfig(o.Images, storage)
if err != nil {
return err
Expand Down Expand Up @@ -407,6 +421,12 @@ func (p *PipelineConfig) updateOutputs(req egress.EgressRequest) error {
p.KeyFrameInterval = StreamKeyframeInterval
}

// if passthrough was disabled after the segment output was parsed (outputs
// arrive in request order), the segment encoder still needs to be enabled
if p.VideoEnabled && !p.VideoPassthrough && hasSegments {
p.VideoEncoding = true
}

return nil
}

Expand Down
97 changes: 97 additions & 0 deletions pkg/config/passthrough.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"

"github.com/livekit/egress/pkg/types"
)

// Video pass-through skips the decode/encode stages and forwards the published
// H.264 bitstream directly to the muxer. It is opt-in via the
// enable_video_passthrough deployment flag and only ever downgrades to the
// default transcoding pipeline, never the other way around.
//
// Eligibility is decided in two phases:
//
// 1. Request time (maybeEnableVideoPassthrough): the deployment flag is on,
// the request subscribes to at most one video track (TrackComposite or
// Participant - never composition), and no video encoding options were
// requested. Requiring the request to carry no explicit video options also
// guarantees no resolution/bitrate change is needed: the output follows
// whatever the track publishes.
// 2. Output validation (disableVideoPassthrough callers in output.go) and
// subscription time (updatePreInitStateLocked in track_worker.go): any
// non-segment output, or an input codec that differs from the output
// codec, falls back to transcoding with an informative log.

// maybeEnableVideoPassthrough marks the request as a pass-through candidate,
// clearing the forced VideoDecoding flag. Must be called after encoding
// options are applied and before outputs are parsed.
func (p *PipelineConfig) maybeEnableVideoPassthrough(hasPreset bool, advanced *livekit.EncodingOptions) {
if !p.EnableVideoPassthrough || !p.VideoEnabled {
return
}

switch p.RequestType {
case types.RequestTypeTrackComposite, types.RequestTypeParticipant:
// single-publisher SDK sources, at most one video track subscribed
default:
logger.Infow("video passthrough not eligible",
"reason", "request type requires composition",
"requestType", p.RequestType)
return
}

if hasPreset {
logger.Infow("video passthrough not eligible",
"reason", "encoding preset requested")
return
}
if advanced != nil && advancedHasVideoOverrides(advanced) {
logger.Infow("video passthrough not eligible",
"reason", "advanced video encoding options requested")
return
}

p.VideoDecoding = false
p.VideoPassthrough = true
logger.Infow("video passthrough candidate",
"requestType", p.RequestType)
}

// DisableVideoPassthrough reverts a pass-through candidate to the default
// decode path. Safe to call unconditionally.
func (p *PipelineConfig) DisableVideoPassthrough(reason string) {
if !p.VideoPassthrough {
return
}
p.VideoPassthrough = false
p.VideoDecoding = true
logger.Infow("video passthrough disabled, falling back to transcoding",
"reason", reason)
}

func advancedHasVideoOverrides(advanced *livekit.EncodingOptions) bool {
return advanced.VideoCodec != livekit.VideoCodec_DEFAULT_VC ||
advanced.Width != 0 ||
advanced.Height != 0 ||
advanced.Depth != 0 ||
advanced.Framerate != 0 ||
advanced.VideoBitrate != 0 ||
advanced.KeyFrameInterval != 0
}
Loading
Loading