switcher

package
v0.0.0-...-3c84d77 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 25, 2026 License: MIT Imports: 39 Imported by: 0

Documentation

Overview

Package switcher implements the core video switching engine.

The Switcher type manages source registration, preview/program selection, and frame routing. It uses atomic.Pointer for lock-free reads on the hot path (frame forwarding) and a mutex for state-changing commands (Cut, SetPreview, StartTransition).

Key types:

  • Switcher: Main state machine with Cut, SetPreview, StartTransition
  • TransitionConfig: Codec factory configuration for dissolve/wipe transitions
  • DelayBuffer: Per-source configurable frame delay (0-500ms)
  • FrameSynchronizer: Freerun frame alignment across sources (90 kHz PTS)

Frame routing: each registered source gets a sourceViewer that tags frames with the source key. Only the program source's frames are forwarded to the program relay for downstream viewers. After a cut, video and audio are gated until the first IDR keyframe from the new source to prevent decoder artifacts.

Index

Constants

View Source
const (
	BuiltinSourceKeyBlack     = "source:black"
	BuiltinSourceKeyColorbars = "source:colorbars"
)

Canonical keys for the always-present virtual sources every engine exposes. Operators can CUT or PREVIEW to these without any external feed configured.

View Source
const (
	// ColorYUV420_8bit is planar YUV 4:2:0 at 8 bits per sample (standard broadcast).
	ColorYUV420_8bit = video.YUV420_8bit
	// ColorYUV422_10bit is planar YUV 4:2:2 at 10 bits per sample (professional broadcast).
	ColorYUV422_10bit = video.YUV422_10bit
)
View Source
const BuiltinSourceCount = 2

BuiltinSourceCount is the number of virtual sources Switcher.New auto-registers. Exposed so downstream tests across packages can `len(userSources) + switcher.BuiltinSourceCount` without drifting when the set grows.

Variables

View Source
var (
	ErrSourceNotFound                  = errors.New("switcher: source not found")
	ErrAlreadyOnProgram                = errors.New("switcher: already on program")
	ErrInvalidDelay                    = errors.New("switcher: delay must be 0-500ms")
	ErrInvalidPosition                 = errors.New("switcher: position must be >= 1")
	ErrNoTransition                    = errors.New("switcher: no active transition")
	ErrFormatDuringTransition          = errors.New("switcher: cannot change pipeline format during active transition")
	ErrEncoderNotAvailable             = errors.New("switcher: encoder not available")
	ErrInvalidFTBDuration              = errors.New("switcher: ftb duration must be 100-5000ms")
	ErrInvalidTransitionAbortThreshold = errors.New("switcher: transition abort threshold must be 0.05-0.5")
)

Sentinel errors for the switcher package.

View Source
var DefaultFormat = FormatPresets["1080p29.97"]

DefaultFormat is the startup default when no --format flag is provided.

View Source
var ErrBuiltinSourceUnknown = errors.New("switcher: unknown built-in source")

ErrBuiltinSourceUnknown is returned from BuiltinSourceFrame for a key that isn't a registered built-in virtual source.

View Source
var ErrOddDimensions = errors.New("TileCache: tile dimensions must be positive and even")

ErrOddDimensions is returned by NewTileCache for zero or odd width/height. YUV420 requires even dimensions so the 4:2:0 chroma planes divide cleanly.

View Source
var FormatPresets = map[string]PipelineFormat{

	"1080p60":     {Width: 1920, Height: 1080, FPSNum: 60, FPSDen: 1, Name: "1080p60", Color: ColorProfileStandard()},
	"1080p59.94":  {Width: 1920, Height: 1080, FPSNum: 60000, FPSDen: 1001, Name: "1080p59.94", Color: ColorProfileStandard()},
	"1080p50":     {Width: 1920, Height: 1080, FPSNum: 50, FPSDen: 1, Name: "1080p50", Color: ColorProfileStandard()},
	"1080p30":     {Width: 1920, Height: 1080, FPSNum: 30, FPSDen: 1, Name: "1080p30", Color: ColorProfileStandard()},
	"1080p29.97":  {Width: 1920, Height: 1080, FPSNum: 30000, FPSDen: 1001, Name: "1080p29.97", Color: ColorProfileStandard()},
	"1080p25":     {Width: 1920, Height: 1080, FPSNum: 25, FPSDen: 1, Name: "1080p25", Color: ColorProfileStandard()},
	"1080p24":     {Width: 1920, Height: 1080, FPSNum: 24, FPSDen: 1, Name: "1080p24", Color: ColorProfileStandard()},
	"1080p23.976": {Width: 1920, Height: 1080, FPSNum: 24000, FPSDen: 1001, Name: "1080p23.976", Color: ColorProfileStandard()},

	"720p60":    {Width: 1280, Height: 720, FPSNum: 60, FPSDen: 1, Name: "720p60", Color: ColorProfileStandard()},
	"720p59.94": {Width: 1280, Height: 720, FPSNum: 60000, FPSDen: 1001, Name: "720p59.94", Color: ColorProfileStandard()},
	"720p50":    {Width: 1280, Height: 720, FPSNum: 50, FPSDen: 1, Name: "720p50", Color: ColorProfileStandard()},
	"720p30":    {Width: 1280, Height: 720, FPSNum: 30, FPSDen: 1, Name: "720p30", Color: ColorProfileStandard()},
	"720p29.97": {Width: 1280, Height: 720, FPSNum: 30000, FPSDen: 1001, Name: "720p29.97", Color: ColorProfileStandard()},
	"720p25":    {Width: 1280, Height: 720, FPSNum: 25, FPSDen: 1, Name: "720p25", Color: ColorProfileStandard()},

	"2160p60":    {Width: 3840, Height: 2160, FPSNum: 60, FPSDen: 1, Name: "2160p60", Color: ColorProfileStandard()},
	"2160p59.94": {Width: 3840, Height: 2160, FPSNum: 60000, FPSDen: 1001, Name: "2160p59.94", Color: ColorProfileStandard()},
	"2160p50":    {Width: 3840, Height: 2160, FPSNum: 50, FPSDen: 1, Name: "2160p50", Color: ColorProfileStandard()},
	"2160p30":    {Width: 3840, Height: 2160, FPSNum: 30, FPSDen: 1, Name: "2160p30", Color: ColorProfileStandard()},
	"2160p29.97": {Width: 3840, Height: 2160, FPSNum: 30000, FPSDen: 1001, Name: "2160p29.97", Color: ColorProfileStandard()},
	"2160p25":    {Width: 3840, Height: 2160, FPSNum: 25, FPSDen: 1, Name: "2160p25", Color: ColorProfileStandard()},
}

FormatPresets contains standard broadcast format presets (ATSC + EBU).

Functions

func DefaultBitrateForResolution

func DefaultBitrateForResolution(width, height int) int

DefaultBitrateForResolution returns the minimum encoding bitrate for broadcast-quality output at the given resolution.

func Downconvert422_10to420

func Downconvert422_10to420(src, dst []byte, width, height int) error

Downconvert422_10to420 converts a YUV422P10LE frame to YUV420P 8-bit.

func SAD10

func SAD10(a, b []byte, aOff, bOff, stride, bw, bh int) int

SAD10 computes the Sum of Absolute Differences between two blocks in a YUV422P10LE Y plane. The block dimensions are bw x bh samples, and stride is in uint16 samples (not bytes). aOff and bOff are byte offsets into a and b.

func ScaleAndEncodeJPEG

func ScaleAndEncodeJPEG(yuv []byte, srcW, srcH, dstW, dstH int) ([]byte, error)

ScaleAndEncodeJPEG takes raw YUV420 planar data at (srcW x srcH), scales it to (dstW x dstH), converts to RGB, and encodes as JPEG at quality 60. Follows the same pattern as the confidence monitor (output/confidence.go).

func Upconvert420to422_10

func Upconvert420to422_10(src, dst []byte, width, height int) error

Upconvert420to422_10 converts a YUV420P 8-bit frame to YUV422P10LE.

func ValidFormatPreset

func ValidFormatPreset(name string) bool

ValidFormatPreset returns true if the name is a recognized preset.

Types

type AsyncMetricsProvider

type AsyncMetricsProvider interface {
	AsyncMetrics() map[string]any
}

AsyncMetricsProvider is optionally implemented by pipeline nodes that perform work asynchronously (after Process() returns). The returned map is merged into the node's Snapshot() entry, giving debug tools access to the real work duration instead of the near-zero enqueue time measured by the pipeline loop.

type BuiltinFrameTap

type BuiltinFrameTap func(key string, yuv []byte, width, height int, pts int64, format video.Format)

BuiltinFrameTap is an external observer for built-in source frames. The app layer installs this to route raw YUV into a per-source MoQ-published preview encoder so the browser multiview tile has content. Tap runs on the producer goroutine — callers must not block.

type DelayBuffer

type DelayBuffer struct {
	// contains filtered or unexported fields
}

DelayBuffer introduces a configurable per-source delay between frame ingestion (from sourceViewer) and delivery to the downstream frameHandler. When delay is 0 for a source, frames pass through immediately with zero allocation. Delayed frames are scheduled via time.AfterFunc, eliminating the need for a background polling goroutine.

func NewDelayBuffer

func NewDelayBuffer(handler frameHandler) *DelayBuffer

NewDelayBuffer creates a DelayBuffer that forwards released frames to the given handler. No background goroutine is started; delayed frames are scheduled individually via time.AfterFunc.

func (*DelayBuffer) Close

func (db *DelayBuffer) Close()

Close marks the buffer as stopped. Any in-flight time.AfterFunc callbacks will check the stopped flag and discard frames. It is safe to call Close multiple times.

func (*DelayBuffer) GetDelay

func (db *DelayBuffer) GetDelay(sourceKey string) time.Duration

GetDelay returns the configured delay for a source, or 0 if not set.

func (*DelayBuffer) RemoveSource

func (db *DelayBuffer) RemoveSource(sourceKey string)

RemoveSource removes a source's delay configuration. Any in-flight time.AfterFunc callbacks for this source will detect the generation mismatch and discard the frame.

func (*DelayBuffer) SetDelay

func (db *DelayBuffer) SetDelay(sourceKey string, delay time.Duration)

SetDelay configures the delay for a source. New frames pushed after this call use the new delay; already-scheduled frames retain their original scheduled release time.

type FRCQuality

type FRCQuality int

FRCQuality controls the frame rate conversion interpolation method.

const (
	// FRCNone uses frame duplication (current behavior).
	FRCNone FRCQuality = iota
	// FRCNearest selects the nearest source frame by PTS distance.
	FRCNearest
	// FRCBlend linearly blends between source frames (may ghost on motion).
	FRCBlend
	// FRCMCFI uses motion-compensated frame interpolation.
	FRCMCFI
)

func ParseFRCQuality

func ParseFRCQuality(s string) FRCQuality

ParseFRCQuality converts a string to FRCQuality. Returns FRCNone for unknown values.

func (FRCQuality) String

func (q FRCQuality) String() string

String returns the string representation of an FRCQuality value.

type FramePool

type FramePool struct {
	// contains filtered or unexported fields
}

FramePool manages a fixed set of pre-allocated pixel buffers. Buffers are acquired and released via a mutex-guarded free list. The pool is sized at init time and never grows. If all buffers are in use, Acquire falls back to a fresh allocation (logged as a pool miss).

Pre-allocated buffers use mmap (MAP_PRIVATE|MAP_ANON) so they live outside the Go heap and are invisible to the GC. At 1080p with 512 buffers this removes ~1.5 GB from GC scanning.

func NewFramePool

func NewFramePool(n int, width, height int, colorFmt PipelineColorFormat) *FramePool

NewFramePool creates a pool with n pre-allocated buffers for the given dimensions and color format. Buffers are allocated via mmap to avoid GC overhead; if mmap fails, the pool falls back to heap allocation.

func (*FramePool) Acquire

func (fp *FramePool) Acquire() []byte

Acquire returns a YUV buffer. If the pool is exhausted, allocates fresh.

func (*FramePool) BufSize

func (fp *FramePool) BufSize() int

BufSize returns the buffer size in bytes (for external size checks).

func (*FramePool) Close

func (fp *FramePool) Close()

Close releases the pre-allocated buffers currently in the free list. Mmap-backed buffers still checked out by in-flight frames stay mapped — they are munmapped by Release when their holders return them (and are never recycled). Safe to call during graceful shutdown. After Close, Acquire falls back to make() (pool is empty and closed). Idempotent.

func (*FramePool) Release

func (fp *FramePool) Release(buf []byte)

Release returns a buffer to the pool. Wrong-sized buffers are discarded. Duplicate releases of the same buffer are detected and silently ignored to prevent buffer aliasing corruption.

After Close, buffers are never recycled: an mmap-backed buffer released late (by an in-flight frame that outlived Close) is munmapped here, and heap buffers are simply discarded.

func (*FramePool) Stats

func (fp *FramePool) Stats() (hits, misses uint64)

Stats returns hit/miss counts for diagnostics.

type FrameSynchronizer

type FrameSynchronizer struct {
	// contains filtered or unexported fields
}

FrameSynchronizer aligns frames from multiple sources to a common frame boundary ("freerun sync"). Each source has a 2-frame ring buffer. A background ticker at the program frame rate releases the most recent buffered frame from each source on every tick. If no new frame arrived since the last tick, the previous frame is repeated (freeze behavior).

PTS strategy: fresh source frames preserve their original PTS (maintaining A/V sync with passthrough audio). Repeated/frozen frames advance PTS by one tick interval for monotonic output. If a fresh frame arrives after a freeze with PTS behind the accumulated freeze PTS, it is clamped forward to prevent backward PTS in the MPEG-TS output.

func NewFrameSynchronizer

func NewFrameSynchronizer(
	tickRate time.Duration,
	onVideo func(sourceKey string, frame media.VideoFrame),
	onAudio func(sourceKey string, frame media.AudioFrame),
) *FrameSynchronizer

NewFrameSynchronizer creates a FrameSynchronizer with the given tick rate and output callbacks. The ticker is NOT started automatically — call Start() to begin releasing frames.

func (*FrameSynchronizer) AddSource

func (fs *FrameSynchronizer) AddSource(key string)

AddSource registers a source for frame synchronization. Safe to call while the ticker is running. When FRC is enabled (quality >= FRCMCFI), the program source gets full quality while non-program sources get FRCNearest (near-zero CPU). For quality levels below FRCMCFI, all sources get the same quality.

func (*FrameSynchronizer) BufferOnly

func (fs *FrameSynchronizer) BufferOnly() bool

BufferOnly returns whether the FrameSynchronizer is in buffer-only mode.

func (*FrameSynchronizer) DebugSnapshot

func (fs *FrameSynchronizer) DebugSnapshot() map[string]any

DebugSnapshot returns a point-in-time snapshot of the frame synchronizer state for diagnostic display. Includes per-source buffer counts, audio miss counts, and FRC state (when enabled).

Locking order: fs.mu → ss.mu (matches releasePending/Tick pattern).

func (*FrameSynchronizer) FRCQuality

func (fs *FrameSynchronizer) FRCQuality() FRCQuality

FRCQuality returns the current FRC quality level.

func (*FrameSynchronizer) GetSourcePTSCorrection

func (fs *FrameSynchronizer) GetSourcePTSCorrection(sourceKey string) int64

GetSourcePTSCorrection returns the PTS correction delta for a source. This is the amount by which the frame sync has shifted video PTS relative to the source's original PTS. Audio frames should have this delta added to their PTS to maintain A/V sync (since audio bypasses the frame sync). Returns 0 if the source is not found or no correction is needed.

func (*FrameSynchronizer) IngestAudio

func (fs *FrameSynchronizer) IngestAudio(sourceKey string, frame *media.AudioFrame)

IngestAudio buffers an incoming audio frame for the specified source. Audio frames are appended to a FIFO queue (never dropped) and also pushed into the ring buffer (for freeze/repeat behavior when the queue is empty). All queued frames are drained on the next tick release. Takes a pointer to avoid value copy heap escape on the hot path.

func (*FrameSynchronizer) IngestRawVideo

func (fs *FrameSynchronizer) IngestRawVideo(sourceKey string, pf *ProcessingFrame)

IngestRawVideo buffers a decoded YUV frame for the specified source. When the source is the current program source, signals the tick loop to fire immediately (phase-lock). This works safely with FRC because each early release resets the timer deadline (nextTick = time.Now().Add(rate)), consuming a timer slot and preventing rate inflation. Between program source frames, the timer continues firing normally for FRC interpolation.

func (*FrameSynchronizer) IngestVideo

func (fs *FrameSynchronizer) IngestVideo(sourceKey string, frame *media.VideoFrame)

IngestVideo buffers an incoming video frame for the specified source. If the source is not registered, the frame is silently dropped. Takes a pointer to avoid value copy heap escape on the hot path.

func (*FrameSynchronizer) ReleaseFPS

func (fs *FrameSynchronizer) ReleaseFPS() float64

ReleaseFPS computes the frame sync output rate (releases per second) from the delta in total releases since the last call. Designed to be called once per second by the perf sampler. Must be called under the Switcher's RLock (fs is accessed via s.frameSync).

func (*FrameSynchronizer) RemoveSource

func (fs *FrameSynchronizer) RemoveSource(key string)

RemoveSource unregisters a source and releases any buffered frames. Pool buffers held by raw video ring slots, lastRawVideo, and FRC state are explicitly released to prevent FramePool starvation.

func (*FrameSynchronizer) SetBufferOnly

func (fs *FrameSynchronizer) SetBufferOnly(enabled bool)

SetBufferOnly enables or disables buffer-only mode. When true, Start() will NOT launch the tickLoop goroutine. Ring buffers still accept frames via IngestRawVideo (for the reference clock sync loop to pop), but no timer fires and no callbacks (onVideo, onRawVideo, onAudio) are invoked. This prevents the FrameSync timer from competing with the ref clock sync loop over the same ring buffers.

If the tickLoop is already running (Start() was called before SetBufferOnly), it is stopped gracefully. The done channel is closed and replaced in one critical section under fs.mu, so a concurrent Stop() can never observe the stale closed channel (which would double-close and panic) — it closes the fresh channel instead. The tickLoop captured the old channel at launch, so it still receives the close signal.

func (*FrameSynchronizer) SetClockDriven

func (fs *FrameSynchronizer) SetClockDriven(enabled bool)

SetClockDriven enables or disables clock-driven output mode. When true, the frame sync uses only timer-driven releases at a fixed rate (like a hardware frame sync / TBC). This decouples output timing from source jitter at the cost of up to one frame of latency (~33ms). When false (default), the program source drives early releases for minimum latency, but output timing inherits source jitter.

func (*FrameSynchronizer) SetFRCQuality

func (fs *FrameSynchronizer) SetFRCQuality(q FRCQuality)

SetFRCQuality sets the frame rate conversion quality for all sources. FRCNone disables FRC and removes frcSource instances. When quality is FRCMCFI or above, the program source gets full quality while non-program sources get FRCNearest (near-zero CPU). Below FRCMCFI, all sources get the same quality since they're cheap enough.

func (*FrameSynchronizer) SetFramePool

func (fs *FrameSynchronizer) SetFramePool(pool *FramePool)

SetFramePool updates the frame pool reference used by the FrameSynchronizer for FRC deep copies and new source FRC initialization. Called by SetPipelineFormat after creating a new pool at the updated dimensions. Also propagates the new pool to all existing FRC sources.

func (*FrameSynchronizer) SetProgramSource

func (fs *FrameSynchronizer) SetProgramSource(key string)

SetProgramSource sets which source drives early release of the tick loop. When the program source ingests a fresh frame, the tick fires immediately instead of waiting for the fixed-rate timer.

When FRC quality is FRCMCFI or above, this also demotes the old program source to FRCNearest and promotes the new program source to full quality. This ensures only the on-air source pays the MCFI CPU cost (~16% per source).

func (*FrameSynchronizer) SetSourceFRCQuality

func (fs *FrameSynchronizer) SetSourceFRCQuality(key string, q FRCQuality)

SetSourceFRCQuality sets the FRC quality for a specific source. Used to run full MCFI only on the program source while other sources use cheaper interpolation (e.g., FRCNearest).

func (*FrameSynchronizer) SetTickRate

func (fs *FrameSynchronizer) SetTickRate(d time.Duration)

SetTickRate updates the tick rate. Takes effect on the next tick cycle. This is used when auto-detecting frame rate from source streams. Also propagates the new tick interval to all existing FRC sources so their interpolation alpha computations use the correct PTS spacing.

func (*FrameSynchronizer) SourceCount

func (fs *FrameSynchronizer) SourceCount() int

SourceCount returns the number of sources registered with the frame synchronizer.

func (*FrameSynchronizer) SourceDrift

func (fs *FrameSynchronizer) SourceDrift(sourceKey string) (int64, int)

SourceDrift returns the clock drift estimate for the given source. Returns (driftPPM, samples). If the source is not found, returns (0, 0).

func (*FrameSynchronizer) Start

func (fs *FrameSynchronizer) Start()

Start begins the background ticker goroutine that releases frames at the configured tick rate. Calling Start multiple times is safe (no-op after first call).

In buffer-only mode (SetBufferOnly(true)), Start() marks the synchronizer as started but does NOT launch the tickLoop goroutine. Ring buffers remain functional for external consumers (e.g., the reference clock sync loop).

func (*FrameSynchronizer) Stop

func (fs *FrameSynchronizer) Stop()

Stop halts the background ticker. Safe to call multiple times. Releases all pool buffers held by sources (pendingRawVideo, lastRawVideo, FRC state) to prevent FramePool starvation.

type GPUPipelineRunner

type GPUPipelineRunner interface {
	// RunWithUpload uploads a CPU YUV420p frame to GPU, runs all GPU nodes
	// (key, layout, compositor, stmap, raw sinks, encode), releases the GPU
	// frame, and returns. The encode callback has already been called with
	// the H.264 output by the time this returns.
	RunWithUpload(yuv []byte, width, height int, pts int64) error

	// RunFromCache retrieves a pre-uploaded GPU frame from the source cache
	// (GPUSourceManager), copies it to a pipeline frame, and runs the GPU
	// pipeline — skipping the CPU→GPU upload entirely. Returns an error if
	// the source has no cached frame, in which case the caller should fall
	// back to RunWithUpload.
	RunFromCache(sourceKey string, pts int64) error

	// RunTransition blends two source frames on GPU and runs the result
	// through the rest of the GPU pipeline (key → layout → compositor →
	// stmap → raw sinks → encode). Both source frames are read from the
	// GPU source cache. transType is "mix", "dip", "wipe", "ftb",
	// "ftb_reverse", or "stinger". wipeDir is an int matching gpu.WipeDirection.
	// position is 0.0 (all A) to 1.0 (all B). stinger carries the overlay
	// YUV + alpha for stinger transitions (nil otherwise).
	RunTransition(fromKey, toKey string, transType string, wipeDir int, position float64, pts int64, stinger *GPUStingerFrame) error

	// SetWipeMap uploads a gradient map to the GPU for gradient-map-based wipe
	// transitions. Called once at transition start; the gradient map persists
	// across frames until ClearWipeMap. The config holds soft-edge, border,
	// reverse, and multiply parameters used per-frame during RunTransition.
	SetWipeMap(gradient []byte, config *wipemap.WipeConfig, width, height int) error

	// ClearWipeMap releases the GPU gradient map buffer and clears the wipe
	// config. Called when a transition completes or is aborted.
	ClearWipeMap()

	// Snapshot returns GPU pipeline stats: per-node timing, run counts,
	// source manager state, and backend info. Used by debug and perf endpoints.
	Snapshot() map[string]any
}

GPUPipelineRunner is the interface for running the full GPU video pipeline. The implementation lives in the gpu package (wrapped by the app layer). When set on the Switcher, frames are routed through the GPU pipeline instead of the CPU PipelineNode chain.

type GPURefCounter

type GPURefCounter interface {
	Ref()
	Release()
}

GPURefCounter is an interface for GPU frame reference counting. When GPUData implements this interface, DeepCopy will call Ref() and ReleaseYUV will call Release() to maintain correct GPU memory lifecycle.

type GPUSourceManagerIface

type GPUSourceManagerIface interface {
	IngestYUV(sourceKey string, yuv []byte, w, h int, pts int64)
	RemoveSource(sourceKey string)
}

GPUSourceManagerIface provides GPU source frame management. Implemented by gpu.GPUSourceManager in the app layer. When set on the Switcher, handleRawVideoFrame routes YUV frames through GPU upload + ST map + cache instead of CPU fill paths (IngestFillYUV, IngestSourceFrame).

type GPUStingerFrame

type GPUStingerFrame struct {
	YUV      []byte // YUV420p overlay (stinger graphic)
	Alpha    []byte // per-luma-pixel alpha [0-255]
	Width    int
	Height   int
	CutPoint float64 // position where base switches from A to B
}

GPUStingerFrame carries the stinger overlay and alpha for GPU transitions.

type MCFIState

type MCFIState struct {
	// contains filtered or unexported fields
}

MCFIState holds reusable state for standalone motion-compensated frame interpolation. It satisfies the replay.FrameInterpolator interface via Go's structural typing (same Interpolate method signature).

Motion vectors are computed once per unique frame pair and cached for subsequent calls with different alpha values. This is critical for slow-motion replay where dupCount calls share the same source frame pair but need different interpolation positions.

func NewMCFIState

func NewMCFIState() *MCFIState

NewMCFIState creates a new MCFI interpolation state.

func (*MCFIState) Interpolate

func (s *MCFIState) Interpolate(frameA, frameB []byte, width, height int, alpha float64) []byte

Interpolate produces a motion-compensated interpolated frame between frameA and frameB at position alpha (0.0=frameA, 1.0=frameB). Both frames must be YUV420 with dimensions width × height.

Uses per-pixel bilinear MV interpolation to eliminate block boundary artifacts. Motion vectors are still estimated at 16×16 block granularity (using SIMD-accelerated diamond search), but the warp smoothly interpolates MVs across block boundaries and samples source pixels with bilinear interpolation for sub-pixel accuracy.

Motion estimation runs once per unique frame pair (~5-15ms for 1080p) and is cached. Subsequent calls with different alpha values only perform the smooth warp (~8-18ms). Falls back to linear blend on scene change.

The returned slice is a freshly allocated copy, safe for async consumers.

func (*MCFIState) Interpolate10

func (s *MCFIState) Interpolate10(frameA, frameB []byte, width, height int, t float64) []byte

Interpolate10 generates an interpolated frame between two YUV422P10LE frames. t is the interpolation factor (0.0 = frameA, 1.0 = frameB). Uses linear blending as the Go fallback; MCFI warp can be layered on via SIMD later.

The returned slice is a freshly allocated buffer, safe for async consumers.

func (*MCFIState) InterpolateInto

func (s *MCFIState) InterpolateInto(dst, frameA, frameB []byte, width, height int, alpha float64)

InterpolateInto writes the interpolated frame into dst. dst must be at least ColorYUV420_8bit.FrameSize(width, height) bytes. This variant avoids allocation when the caller can provide a reusable buffer.

type PerfSourceSample

type PerfSourceSample struct {
	DecodeLastNs  int64
	DecodeDrops   int64
	AvgFPS        float64
	AvgFrameBytes int
	Health        string

	// RawFrameCount is the monotonic raw video ingest counter.
	// The perf sampler computes IngestFPS from deltas between ticks.
	RawFrameCount int64

	// DriftPPM is the estimated clock drift between the source's PTS clock
	// and the pipeline wall clock. Positive = source clock faster.
	DriftPPM     int64
	DriftSamples int
}

PerfSourceSample mirrors perf.SourceSample.

type PerfSwitcherSample

type PerfSwitcherSample struct {
	Sources            map[string]PerfSourceSample
	PipelineLastNs     int64
	NodeTimings        map[string]int64
	E2ELastNs          int64
	QueueLen           int
	OutputFPS          float64
	BroadcastGapNs     int64
	VideoBroadcast     int64
	DeadlineViolations int64
	FrameBudgetNs      int64
	ProcDropped        int64
	DecodeQueueNs      int64
	DecodeNs           int64
	SyncWaitNs         int64
	ProcQueueNs        int64

	// Frame synchronizer stats
	FrameSyncReleaseFPS  float64
	FrameSyncSourceCount int

	// GPU pipeline stats (populated when GPU pipeline is active)
	GPUActive         bool
	GPUPipelineLastNs int64
	GPUNodeTimings    map[string]int64
	GPUBackend        string
	GPUDevice         string
}

PerfSwitcherSample mirrors perf.SwitcherSample for interface satisfaction. We can't import the perf package from switcher (circular dependency), so we define compatible types here. The perf.Sampler wraps these via a thin adapter.

type Pipeline

type Pipeline struct {
	// contains filtered or unexported fields
}

Pipeline holds a configured, ready-to-run processing chain. Built via Build() on main goroutine. Run() called per-frame on pipeline goroutine. Immutable once built — reconfiguration creates a new Pipeline via atomic swap.

func (*Pipeline) Build

func (p *Pipeline) Build(format PipelineFormat, pool *FramePool, nodes []PipelineNode) error

Build validates all nodes against the format, filters active nodes, and pre-computes total latency. Runs on main goroutine.

func (*Pipeline) BypassState

func (p *Pipeline) BypassState() map[string]bool

BypassState returns the current bypass state for all nodes.

func (*Pipeline) Close

func (p *Pipeline) Close() error

Close waits for in-flight frames, then closes all nodes.

func (*Pipeline) RestoreBypass

func (p *Pipeline) RestoreBypass(state map[string]bool)

RestoreBypass saves bypass state to apply during the next Build() call. Used to carry bypass flags across pipeline rebuilds. Must be called before Build(). Only restores flags for nodes that exist in the new pipeline.

func (*Pipeline) Run

func (p *Pipeline) Run(frame *ProcessingFrame) *ProcessingFrame

Run processes a single frame through all active nodes. Called on pipeline goroutine (single-threaded).

MakeWritable ensures the pipeline owns its YUV buffer before any node modifies it in-place. Source frames delivered via shallow copy (Ref) from frame_sync remain untouched — critical for PIP fill cache correctness.

func (*Pipeline) SetBypass

func (p *Pipeline) SetBypass(nodeName string, bypass bool) error

SetBypass toggles the bypass flag for the named node. When bypassed, Run() skips the node's Process() call — the frame passes through unchanged. Returns an error if the node name is "gpu_encode" (protected) or unknown.

func (*Pipeline) SetMetrics

func (p *Pipeline) SetMetrics(m *metrics.Metrics)

SetMetrics sets the Prometheus metrics instance for per-node observations. Must be called after Build() but before Run(). Nil is safe (no observations).

func (*Pipeline) Snapshot

func (p *Pipeline) Snapshot() map[string]any

Snapshot returns per-node timing for debug endpoint.

func (*Pipeline) TotalLatency

func (p *Pipeline) TotalLatency() time.Duration

TotalLatency returns sum of all active nodes' reported latencies. Used for automatic audio delay compensation (lip-sync).

func (*Pipeline) Wait

func (p *Pipeline) Wait()

Wait blocks until all in-flight Run() calls complete.

type PipelineCodec

type PipelineCodec int

PipelineCodec identifies the video codec used for encoding/decoding.

const (
	// CodecAVC is H.264/AVC. Supports 8-bit 4:2:0 only.
	CodecAVC PipelineCodec = iota
	// CodecHEVC is H.265/HEVC. Supports 8-bit 4:2:0 and 10-bit 4:2:2.
	CodecHEVC
)

func (PipelineCodec) String

func (c PipelineCodec) String() string

String returns the codec name.

type PipelineColorConfig

type PipelineColorConfig struct {
	ColorFormat PipelineColorFormat `json:"colorFormat"`
	Codec       PipelineCodec       `json:"codec"`
}

PipelineColorConfig combines color format and codec selection.

func ColorProfileProfessional

func ColorProfileProfessional() PipelineColorConfig

ColorProfileProfessional returns the professional profile: YUV422 10-bit + HEVC.

func ColorProfileStandard

func ColorProfileStandard() PipelineColorConfig

ColorProfileStandard returns the standard broadcast profile: YUV420 8-bit + AVC.

func ColorProfileStandardHEVC

func ColorProfileStandardHEVC() PipelineColorConfig

ColorProfileStandardHEVC returns YUV420 8-bit + HEVC (better compression, same quality).

func ParseColorProfile

func ParseColorProfile(name string) (PipelineColorConfig, error)

ParseColorProfile returns a PipelineColorConfig for the given profile name. Valid names: "standard", "standard-hevc", "professional".

func (PipelineColorConfig) Validate

func (c PipelineColorConfig) Validate() error

Validate checks that the color format and codec are compatible.

type PipelineColorFormat

type PipelineColorFormat = video.Format

PipelineColorFormat is the pixel format for the pipeline. This is an alias for video.Format — all methods are inherited.

type PipelineEpoch

type PipelineEpoch struct {
	Format    PipelineFormat
	Epoch     uint64
	StartPTS  int64
	NodeNames []string
}

PipelineEpoch captures the pipeline's identity at a point in time. Downstream consumers (SRT output, recording, confidence monitor) can compare epochs to detect pipeline changes and respond (force keyframe, start new segment).

type PipelineFormat

type PipelineFormat struct {
	Width  int                 `json:"width"`  // Horizontal resolution (e.g. 1920)
	Height int                 `json:"height"` // Vertical resolution (e.g. 1080)
	FPSNum int                 `json:"fpsNum"` // Frame rate numerator (e.g. 30000)
	FPSDen int                 `json:"fpsDen"` // Frame rate denominator (e.g. 1001)
	Name   string              `json:"name"`   // Human-readable name (e.g. "1080p29.97")
	Color  PipelineColorConfig `json:"color"`  // Color format and codec (default: YUV420 8-bit AVC)
}

PipelineFormat defines the global video pipeline format. All frame timing, encoder parameters, and resolution scaling derive from this. Frame rate is expressed as a rational number (FPSNum/FPSDen) for broadcast correctness — e.g. 30000/1001 for 29.97fps NTSC.

func (PipelineFormat) FPS

func (f PipelineFormat) FPS() float64

FPS returns the frame rate as a float64 (FPSNum / FPSDen). Returns 0 if FPSDen is zero.

func (PipelineFormat) FPSFloat32

func (f PipelineFormat) FPSFloat32() float32

FPSFloat32 returns the frame rate as a float32.

func (PipelineFormat) FrameBudgetNs

func (f PipelineFormat) FrameBudgetNs() int64

FrameBudgetNs returns the frame duration in nanoseconds.

func (PipelineFormat) FrameDuration

func (f PipelineFormat) FrameDuration() time.Duration

FrameDuration returns the duration of one frame. Computed as FPSDen * time.Second / FPSNum. Returns 33333µs as a safety fallback if FPSNum is zero.

func (PipelineFormat) String

func (f PipelineFormat) String() string

String returns the human-readable name if set, otherwise "WxH@Num/Den".

type PipelineNode

type PipelineNode interface {
	// Name returns a human-readable identifier for debugging and metrics.
	Name() string

	// Configure is called once when the pipeline is built or reconfigured.
	// Receives the pipeline format. Returns error if the node cannot operate.
	Configure(format PipelineFormat) error

	// Active returns whether this node should be included in processing.
	// Inactive nodes are skipped entirely (zero overhead). Must be safe
	// for concurrent reads.
	Active() bool

	// Process transforms the frame. Called per-frame on pipeline goroutine.
	// Must not allocate, must not block.
	// Returns output frame (src for in-place, dst if separate buffer used).
	Process(dst, src *ProcessingFrame) *ProcessingFrame

	// Err returns the last error from Process(), or nil. Checked by
	// monitoring, not on hot path. Nodes log their own errors.
	Err() error

	// Latency reports estimated per-frame processing time. Used for
	// pipeline latency reporting and automatic lip-sync calculation.
	Latency() time.Duration

	// Close releases resources held by this node.
	Close() error
}

PipelineNode is the fundamental processing unit in the video pipeline.

Lifecycle:

  • Configure() runs once when the pipeline is built or reconfigured. May allocate, acquire locks, or fail. Runs on main goroutine.
  • Process() runs on every frame on the pipeline goroutine. Must not allocate, must not block, must not acquire contested locks.
  • Active() is checked during pipeline build to filter inactive nodes. Must be safe for concurrent reads (atomic or lock-free).

Contract: Process receives src (current frame). In-place nodes modify src and return it. Passthrough nodes return src unmodified. The dst parameter is reserved for future nodes needing a separate output buffer (e.g., scaling to different resolution).

type ProcessingFrame

type ProcessingFrame struct {
	Data       []byte              // Raw planar pixel data (layout depends on pipeline color format)
	Format     PipelineColorFormat // Color format of this frame's data
	Width      int
	Height     int
	PTS        int64
	DTS        int64
	IsKeyframe bool
	GroupID    uint32
	Codec      string // preserved from source for output metadata

	// ArrivalNano records UnixNano when the frame entered sourceViewer.SendVideo().
	// Used for E2E latency measurement (source arrival → pipeline processing complete).
	ArrivalNano int64

	// DecodeStartNano records UnixNano when decodeLoop dequeues this frame from
	// the sourceDecoder channel (T1 in the latency breakdown).
	DecodeStartNano int64

	// DecodeEndNano records UnixNano after the H.264 decode completes for this
	// frame (T2 in the latency breakdown).
	DecodeEndNano int64

	// SyncReleaseNano records UnixNano when frame_sync releases this frame in
	// releaseTick Phase 3 (T3 in the latency breakdown).
	SyncReleaseNano int64

	// GPUData holds a reference to the GPU-resident frame (e.g., *gpu.GPUFrame)
	// when the frame is being processed on the GPU. Set by the GPU upload node,
	// cleared by the GPU download node. Typed as `any` to avoid a circular
	// import between switcher and gpu packages. GPU pipeline nodes type-assert
	// this to access the underlying GPU frame.
	GPUData any
	// contains filtered or unexported fields
}

ProcessingFrame carries decoded pixel data through the video processing chain. Created by decoding a media.VideoFrame, consumed by encoding back to one. Used only inside the switcher pipeline — not a replacement for media.VideoFrame.

Reference counting: frames that flow through the pipeline should be created with refs=1 (via SetRefs). Pipeline nodes that share the frame with sinks call Ref() before and ReleaseYUV() after. The buffer returns to the pool only when the last reference is dropped. Unmanaged frames (refs==nil, the zero value) release immediately on ReleaseYUV — this preserves backward compatibility with test code and transient frames.

The refs pointer is shared across value copies of a ProcessingFrame, so frame_sync's pattern of `releaseRawVideo = *newest` correctly shares the refcount. This follows the FFmpeg AVBufferRef model.

func (*ProcessingFrame) CbPlane

func (pf *ProcessingFrame) CbPlane() []byte

CbPlane returns the Cb (blue-difference chroma) plane slice.

func (*ProcessingFrame) CrPlane

func (pf *ProcessingFrame) CrPlane() []byte

CrPlane returns the Cr (red-difference chroma) plane slice.

func (*ProcessingFrame) DeepCopy

func (pf *ProcessingFrame) DeepCopy() *ProcessingFrame

DeepCopy returns a new ProcessingFrame with a copied data buffer. The copy starts with nil refs (unmanaged, independent lifecycle). Caller should call SetRefs(1) if the copy will flow through the pipeline.

If GPUData implements GPURefCounter, Ref() is called so the GPU frame stays alive as long as the copy exists. The caller must ensure ReleaseYUV is called on the copy to release the GPU reference.

func (*ProcessingFrame) FrameDataSize

func (pf *ProcessingFrame) FrameDataSize() int

FrameDataSize returns the expected total byte count for this frame's format and dimensions.

func (*ProcessingFrame) MakeWritable

func (pf *ProcessingFrame) MakeWritable(pool *FramePool)

MakeWritable ensures this frame has exclusive ownership of its data buffer. If the refcount is > 1 (buffer shared with frame_sync, another consumer, etc.), acquires a new buffer from the pool, copies the data, and detaches from the shared refcount. If the frame is already the sole owner (refs <= 1) or unmanaged (nil refs), this is a no-op.

Follows the FFmpeg av_frame_make_writable() / GStreamer gst_buffer_make_writable() pattern. The pipeline calls this at entry so compositor nodes can safely modify data in-place without aliasing source frames retained by frame_sync.

func (*ProcessingFrame) Ref

func (pf *ProcessingFrame) Ref()

Ref increments the reference count, indicating an additional consumer holds a reference to this frame's data buffer. No-op on unmanaged frames.

Invariant: Ref and ReleaseYUV are deliberately ASYMMETRIC for unmanaged frames (refs == nil) — Ref is a no-op while ReleaseYUV frees the buffer immediately. An unmanaged frame therefore supports exactly ONE owner that calls ReleaseYUV exactly once. Any pool-backed frame that will be shared with additional holders (e.g., ingested into the frame-sync ring, where it is retained as lastRawVideo while sync-loop consumers take and release borrowed copies) MUST be made managed via SetRefs(1) by its producer. Otherwise a consumer-side ReleaseYUV recycles the pool buffer out from under the retaining owner (use-after-release) and the owner's eventual release returns the same buffer to the pool a second time.

func (*ProcessingFrame) Refs

func (pf *ProcessingFrame) Refs() int32

Refs returns the current reference count (for diagnostics/testing). Returns 0 for unmanaged frames (nil refs).

func (*ProcessingFrame) ReleaseYUV

func (pf *ProcessingFrame) ReleaseYUV()

ReleaseYUV returns the data buffer to the pool when the last reference is dropped. For refcounted frames (refs >= 1), decrements and releases only when refs reaches 0. For unmanaged frames (nil refs), releases immediately — the caller must be the buffer's sole owner (see Ref for the invariant). Safe to call multiple times; subsequent calls on nil Data are no-ops.

Also releases GPUData if it implements GPURefCounter (decrements GPU frame reference count, freeing GPU memory when the last reference drops).

func (*ProcessingFrame) SetPool

func (pf *ProcessingFrame) SetPool(pool *FramePool)

SetPool sets the FramePool that owns this frame's data buffer. ReleaseYUV will return the buffer to the pool when all references are dropped. Exported so external packages (e.g., SRT wiring in main) can set pool ownership on frames they create.

Pool-backed frames that enter shared pipeline paths (frame-sync ring, IngestRawVideo) must also be made managed via SetRefs(1) — see Ref for the single-owner invariant on unmanaged frames.

func (*ProcessingFrame) SetRefs

func (pf *ProcessingFrame) SetRefs(n int32)

SetRefs allocates (if needed) and initializes the reference count. Call once after creation, before the frame is shared with any other goroutine. Typically set to 1 for frames entering the pipeline.

func (*ProcessingFrame) YPlane

func (pf *ProcessingFrame) YPlane() []byte

YPlane returns the Y (luma) plane slice.

type RawVideoSink

type RawVideoSink func(pf *ProcessingFrame)

RawVideoSink receives a deep copy of the processed YUV420p frame after all video processing (keying, compositor) but before H.264 encode. Used by MXL output to write raw video to shared memory.

type SourceHealthStatus

type SourceHealthStatus string

SourceHealthStatus represents the health/connectivity state of a video source.

const (
	SourceHealthy  SourceHealthStatus = "healthy"
	SourceStale    SourceHealthStatus = "stale"
	SourceNoSignal SourceHealthStatus = "no_signal"
	SourceOffline  SourceHealthStatus = "offline"
)

type State

type State int

State represents the global state of the switching engine. It replaces the implicit (inTransition, ftbActive) boolean pair with an explicit enum that makes every valid state and transition auditable.

const (
	StateIdle             State = iota // No transition, normal frame routing
	StateTransitioning                 // Mix/dip/wipe in progress
	StateFTBTransitioning              // FTB forward in progress (transitioning to black)
	StateFTB                           // Faded to black (holding black)
	StateFTBReversing                  // Reversing FTB (fading back in)
)

func (State) String

func (s State) String() string

String returns the human-readable name of the switcher state.

type Switcher

type Switcher struct {
	// contains filtered or unexported fields
}

Switcher is the central switching engine. It manages which source is on-program (live output) and which is on-preview, maintains tally state, and routes frames from the program source to the program Relay.

func New

func New(programRelay *distribution.Relay) *Switcher

New creates a Switcher that forwards program frames to programRelay.

func NewTestSwitcher

func NewTestSwitcher(t *testing.T, programRelay *distribution.Relay) *Switcher

NewTestSwitcher creates a Switcher with a tiny frame pool (4 × 320×240) suitable for testing. The production New() allocates 512 × 1080p buffers (~1.5 GB) which causes OOM kills when many tests each create one.

Registers sw.Close via t.Cleanup so the built-in source producer goroutine (and everything else Close tears down) shuts down when the test returns. Close is idempotent, so callers that also want an explicit defer sw.Close() remain correct.

This function is intended for use in other packages' tests (e.g., control API tests) that need a Switcher but don't exercise video processing at full resolution.

func (*Switcher) AbortTransition

func (s *Switcher) AbortTransition()

AbortTransition stops any active transition and restores normal frame routing.

func (*Switcher) AvailableEncoders

func (s *Switcher) AvailableEncoders() []codec.EncoderInfo

AvailableEncoders returns the list of encoders available on this system.

func (*Switcher) AvailableEncodersInternal

func (s *Switcher) AvailableEncodersInternal() []internal.EncoderInfo

AvailableEncodersInternal returns the pre-computed internal.EncoderInfo slice for state broadcast enrichment. The returned slice must not be modified.

func (*Switcher) BroadcastToProgramFunc

func (s *Switcher) BroadcastToProgramFunc() func(*media.VideoFrame)

BroadcastToProgramFunc returns the direct program broadcast callback (no caption injection). Used when the caller handles caption SEI injection externally (e.g., GPU pipeline with shared caption cache).

func (*Switcher) BroadcastWithCaptionsFunc

func (s *Switcher) BroadcastWithCaptionsFunc() func(*media.VideoFrame)

BroadcastWithCaptionsFunc returns the caption-injecting broadcast callback for use by the CPU pipeline's encode node.

func (*Switcher) BuildPipeline

func (s *Switcher) BuildPipeline() error

BuildPipeline constructs and stores the video processing pipeline. Must be called after SetCompositor, SetKeyBridge, and SetPipelineCodecs. Safe to call multiple times — each call rebuilds from scratch.

func (*Switcher) BuiltinSourceFrame

func (s *Switcher) BuiltinSourceFrame(key string) ([]byte, int, int, error)

BuiltinSourceFrame returns a frame from the named built-in source at the current pipeline format (dimensions + color format). Returns ErrBuiltinSourceUnknown for non-built-in keys. Frame regenerates on format change; professional (YUV422 10-bit) and standard (YUV420 8-bit) modes are both supported.

func (*Switcher) Close

func (s *Switcher) Close()

Close stops the health monitor, delay buffer, frame sync, and unregisters all sources. Idempotent — gated by sync.Once so a second call (e.g. from `t.Cleanup` plus an existing `defer sw.Close()`) does not panic on double-close of videoProcCh. Concurrent callers see serialized behavior: one runs the body, the rest wait and observe a fully-closed switcher.

func (*Switcher) Cut

func (s *Switcher) Cut(ctx context.Context, sourceKey string) error

Cut performs a hard cut to the named source, making it the program output. The previous program source is automatically moved to preview. If the source is already on program, Cut is a no-op (Seq is not incremented). When an audioCutHandler (mixer) is attached, Cut triggers an audio crossfade and AFV program change automatically.

The ctx parameter is accepted for API compatibility and future use (e.g. tracing) but is not currently checked; the operation is sub-millisecond.

func (*Switcher) DebugSnapshot

func (s *Switcher) DebugSnapshot() map[string]any

DebugSnapshot returns a map of debug instrumentation data for diagnostics.

func (*Switcher) EnableWallClockVideoPTS

func (s *Switcher) EnableWallClockVideoPTS()

EnableWallClockVideoPTS enables wall-clock PTS rewriting on the program relay. When enabled, video PTS is rewritten to match the mixer's wall-clock audio PTS, keeping A/V aligned after source cuts. No-op when the reference clock is active (PTS comes from the clock).

func (*Switcher) EncoderName

func (s *Switcher) EncoderName() string

EncoderName returns the name of the currently active encoder.

func (*Switcher) FTBDurationMs

func (s *Switcher) FTBDurationMs() int

func (*Switcher) FadeToBlack

func (s *Switcher) FadeToBlack(ctx context.Context) error

FadeToBlack starts or toggles a Fade to Black transition. If FTB is already active and no transition is running, it toggles off (restores normal output). If a non-FTB transition is active, FTB is rejected.

The ctx parameter is accepted for API compatibility and future use (e.g. tracing) but is not currently checked; the operation is sub-millisecond.

func (*Switcher) ForceNextIDRPtr

func (s *Switcher) ForceNextIDRPtr() *atomic.Bool

ForceNextIDRPtr returns a pointer to the forceNextIDR atomic for GPU encode.

func (*Switcher) GetFramePool

func (s *Switcher) GetFramePool() *FramePool

GetFramePool returns the current FramePool for YUV buffer allocation. Used by external packages (e.g., SRT wiring) that create ProcessingFrames and want pool-managed buffer lifecycle instead of heap allocation.

func (*Switcher) GetRawPreviewSink

func (s *Switcher) GetRawPreviewSink() RawVideoSink

GetRawPreviewSink returns the currently set raw preview sink, or nil if none.

func (*Switcher) GetRawVideoSink

func (s *Switcher) GetRawVideoSink() RawVideoSink

GetRawVideoSink returns the currently set raw video sink, or nil if none.

func (*Switcher) GetSourceDelay

func (s *Switcher) GetSourceDelay(sourceKey string) int

GetSourceDelay returns the configured input delay in milliseconds for a source, or 0 if the source has no delay configured.

func (*Switcher) IngestRawVideo

func (s *Switcher) IngestRawVideo(sourceKey string, pf *ProcessingFrame)

IngestRawVideo accepts a raw YUV420p frame from an MXL or SRT source. When frame sync is active, frames are buffered in the synchronizer's per-source ring and released at steady tick rate. Otherwise, delegates directly to handleRawVideoFrame for immediate processing.

Ownership: IngestRawVideo always CONSUMES pf — callers must not release or reuse pf after the call. The frame-sync path takes ownership of the buffer (ring buffer / FRC releases it); the direct path releases it here after handleRawVideoFrame returns. This mirrors makeDecoderCallback's direct path.

func (*Switcher) IngestReplayVideo

func (s *Switcher) IngestReplayVideo(sourceKey string, pf *ProcessingFrame)

IngestReplayVideo accepts a raw YUV420p frame from the replay player, clip player, or playout channel. Routes through IngestRawVideo so frames enter the FrameSynchronizer (needed for ref clock sync loop).

func (*Switcher) LastBroadcastVideoPTS

func (s *Switcher) LastBroadcastVideoPTS() int64

LastBroadcastVideoPTS returns the PTS of the most recently broadcast video frame to the program relay. Used by the replay system to anchor its output PTS to the program timeline.

func (*Switcher) OnStateChange

func (s *Switcher) OnStateChange(cb func(internal.ControlRoomState))

OnStateChange registers a callback invoked whenever the switcher state changes. Multiple callbacks may be registered; they are called in order. Callbacks are called outside the lock so they may safely perform slow operations (JSON marshal, network I/O).

func (*Switcher) PerfSample

func (s *Switcher) PerfSample() PerfSwitcherSample

PerfSample returns a performance snapshot of the switcher's current state. Safe for concurrent access from any goroutine.

func (*Switcher) PicTimingConfig

func (s *Switcher) PicTimingConfig() *caption.PicTimingConfig

PicTimingConfig returns the current pic_timing SEI configuration, or nil if pic_timing injection is not enabled. Used by the GPU pipeline encode callback which maintains its own frame counter.

func (*Switcher) PipelineFormat

func (s *Switcher) PipelineFormat() PipelineFormat

PipelineFormat returns the current pipeline format.

func (*Switcher) PipelineSnapshot

func (s *Switcher) PipelineSnapshot() map[string]any

PipelineSnapshot returns a combined snapshot of all pipeline stages: CPU pipeline nodes, GPU pipeline nodes, and registered sources with health. The response is structured for the routing overlay UI.

func (*Switcher) ProgramRelay

func (s *Switcher) ProgramRelay() *distribution.Relay

ProgramRelay returns the program relay for external broadcast (e.g. authored captions).

func (*Switcher) ProgramSource

func (s *Switcher) ProgramSource() string

ProgramSource returns the key of the current program source.

func (*Switcher) RebuildPipeline

func (s *Switcher) RebuildPipeline()

RebuildPipeline rebuilds the video processing pipeline from current state. Called by external components (compositor, key processor) via callbacks when their Active() status may have changed.

func (*Switcher) RegisterMXLSource

func (s *Switcher) RegisterMXLSource(key string)

RegisterMXLSource registers a source that provides raw YUV420p frames directly (no Prism relay/viewer). Used for MXL shared-memory sources.

func (*Switcher) RegisterRISTSource

func (s *Switcher) RegisterRISTSource(key string)

RegisterRISTSource registers a source that provides raw YUV420p frames via IngestRawVideo (same path as SRT/MXL). Used for RIST input sources that are decoded by the rist.Source orchestrator before being fed to the switcher.

func (*Switcher) RegisterReplaySource

func (s *Switcher) RegisterReplaySource(key string)

RegisterReplaySource registers a transient replay source that receives raw YUV frames via IngestReplayVideo. Virtual sources skip delay buffer and replay buffering, but ARE registered with the FrameSynchronizer so the ref clock sync loop can read their frames. Safe to call if the key already exists — cleans up the old registration.

func (*Switcher) RegisterSRTSource

func (s *Switcher) RegisterSRTSource(key string)

RegisterSRTSource registers a source that provides raw YUV420p frames via IngestRawVideo (same path as MXL). Used for SRT input sources that are decoded by the srt.Source orchestrator before being fed to the switcher.

func (*Switcher) RegisterSource

func (s *Switcher) RegisterSource(key string, relay *distribution.Relay)

RegisterSource adds a source to the switcher. A sourceViewer proxy is created and attached to the source's Relay so that frames flow into the Switcher's handleVideoFrame/handleAudioFrame methods tagged with the source key. When frame sync is active, frames route through the FrameSynchronizer; otherwise the delay buffer is attached for per-source lip-sync compensation.

func (*Switcher) RegisterVirtualSource

func (s *Switcher) RegisterVirtualSource(key string, relay *distribution.Relay)

RegisterVirtualSource registers a transient internal source (e.g. replay). Virtual sources skip delay buffer, frame sync, and replay buffering. Safe to call if the key already exists — cleans up the old viewer first.

func (*Switcher) RequestKeyframe

func (s *Switcher) RequestKeyframe()

RequestKeyframe forces the next encoded frame to be an IDR keyframe. Called when a new output viewer joins (e.g., SRT output starts) so the TSMuxer can initialize immediately without waiting for the next GOP boundary.

func (*Switcher) SetAACBuffer

func (s *Switcher) SetAACBuffer(buf *clock.AACFrameBuffer)

SetAACBuffer attaches an AAC frame buffer for the synchronous audio path.

func (*Switcher) SetAudioHandler

func (s *Switcher) SetAudioHandler(handler func(sourceKey string, frame *media.AudioFrame))

SetAudioHandler registers a handler that receives audio frames from ALL sources. When set, the handler (typically an audio mixer) is responsible for deciding which audio reaches the program output. When no handler is set, the original behavior (only program source audio forwarded) is used.

func (*Switcher) SetAudioTransition

func (s *Switcher) SetAudioTransition(handler audioTransitionHandler)

SetAudioTransition attaches an audio transition handler for dissolve sync.

func (*Switcher) SetAvailableEncoders

func (s *Switcher) SetAvailableEncoders(encoders []codec.EncoderInfo)

SetAvailableEncoders stores the list of encoders that are available on this system. Called once at startup from the codec probe results. Also pre-computes the internal.EncoderInfo conversion to avoid per-broadcast allocations in state enrichment.

func (*Switcher) SetBuiltinFrameTap

func (s *Switcher) SetBuiltinFrameTap(fn BuiltinFrameTap)

SetBuiltinFrameTap installs a callback that fires for every built-in frame the producer generates. Pass nil to remove the tap. Safe to call from any goroutine. The app layer uses this to feed per-source MoQ preview encoders so browser multiview tiles render thumbnails for BLACK / BARS. The tap runs on the producer goroutine; handlers must not block.

func (*Switcher) SetCaptionManager

func (s *Switcher) SetCaptionManager(cm captionManager)

SetCaptionManager attaches a caption manager for CEA-608/708 SEI injection.

func (*Switcher) SetClockDrivenSync

func (s *Switcher) SetClockDrivenSync(enabled bool)

SetClockDrivenSync enables clock-driven frame sync output. When enabled, the frame sync uses only timer-driven releases at a fixed rate, decoupling output timing from source jitter. Adds up to one frame of latency (~33ms) but produces rock-steady output timing like a hardware TBC/frame sync.

func (*Switcher) SetClockRecovery

func (s *Switcher) SetClockRecovery(cr *clock.ClockRecovery)

SetClockRecovery attaches a clock recovery instance to the switcher. When set, program source frame arrivals are tracked and the reference clock rate is adjusted if a rate change is detected.

func (*Switcher) SetCodecInfo

func (s *Switcher) SetCodecInfo(encoder, decoder string, hwAccel bool)

SetCodecInfo records which encoder/decoder were selected at startup and whether hardware acceleration is active. Called once during init after codec.ProbeEncoders(). Values are exposed in DebugSnapshot() under "codec".

func (*Switcher) SetCommandQueue

func (s *Switcher) SetCommandQueue(q *cmdqueue.Queue, d *cmdqueue.Dispatcher)

SetCommandQueue wires a PTP-timed command queue and its dispatcher into the switcher. Once set, the video processing loop drains ready commands on every frame tick, giving frame-level execution accuracy (~33ms at 30fps).

func (*Switcher) SetCompositor

func (s *Switcher) SetCompositor(c *graphics.Compositor)

SetCompositor attaches the DSK graphics compositor. The compositor's ProcessYUV method is called in the video processing pipeline when active.

func (*Switcher) SetDVECompositor

func (s *Switcher) SetDVECompositor(dc *dve.Compositor)

SetDVECompositor sets the DVE compositor for DVE-transformed PIP layouts.

func (*Switcher) SetEncoder

func (s *Switcher) SetEncoder(name string) error

SetEncoder switches the video encoder at runtime. The name must match one of the entries in AvailableEncoders. Returns ErrFormatDuringTransition if a transition is in progress. The new encoder takes effect on the next encoded frame (the current encoder is invalidated).

func (*Switcher) SetEncoderOpts

func (s *Switcher) SetEncoderOpts(opts *codec.EncoderOptions)

SetEncoderOpts stores the user-configurable encoder options. These are used by SetEncoder() when rebuilding the encoder factory at runtime, so the user's bitrate/GOP/preset preferences are preserved across encoder switches.

func (*Switcher) SetFRCQuality

func (s *Switcher) SetFRCQuality(q FRCQuality)

SetFRCQuality sets the frame rate conversion quality for all sources. Only effective when frame sync is enabled.

func (*Switcher) SetFTBDuration

func (s *Switcher) SetFTBDuration(durationMs int) error

SetFTBDuration updates the fade-to-black duration in ms. Range [100, 5000]. The change applies to the next FadeToBlack trigger; an in-flight leg keeps its original rate (duration is captured under lock at FadeToBlack entry).

func (*Switcher) SetFrameBudget

func (s *Switcher) SetFrameBudget(ns int64)

SetFrameBudget sets the per-frame processing time budget in nanoseconds. When pipeline latency exceeds this budget, deadlineViolations is incremented. Default is 33ms (30fps). Call with 16_666_666 for 60fps sources.

func (*Switcher) SetFrameSync

func (s *Switcher) SetFrameSync(enabled bool, tickRate time.Duration)

SetFrameSync enables or disables the freerun frame synchronizer. When enabled, all source video and audio frames are buffered and released at a common tick rate (program frame rate) instead of flowing through the per-source delay buffer. This ensures frame-aligned output across sources.

The tickRate parameter sets the release interval (e.g., 33ms for 30fps). Passing 0 uses the default of 33.333ms (30fps).

When enabled, existing source viewers are re-wired to route through the FrameSynchronizer. When disabled, they revert to the delay buffer.

func (*Switcher) SetFrameSyncBufferOnly

func (s *Switcher) SetFrameSyncBufferOnly(enabled bool)

SetFrameSyncBufferOnly puts the frame sync into buffer-only mode. Ring buffers still accept frames, but the tickLoop timer is not started. This is used in reference clock mode where the sync loop pops frames directly from the ring buffers, preventing two goroutines from racing over the same buffers. Must be called before the frame sync is started (i.e., before SetFrameSync enables it, or immediately after).

func (*Switcher) SetGPUPipeline

func (s *Switcher) SetGPUPipeline(gp GPUPipelineRunner)

SetGPUPipeline registers a GPU pipeline that handles the full video processing chain (upload → key → layout → compositor → stmap → raw sinks → encode). When set, videoProcessingLoop routes frames through the GPU pipeline instead of the CPU pipeline, falling back to CPU on GPU errors. Pass nil to disable.

func (*Switcher) SetGPUSourceManager

func (s *Switcher) SetGPUSourceManager(mgr GPUSourceManagerIface)

SetGPUSourceManager registers a GPU source manager that handles per-source GPU upload, ST map correction, caching, and preview encoding. When set, handleRawVideoFrame routes YUV through IngestYUV instead of CPU fill paths (keyBridge.IngestFillYUV, dveCompositor.IngestSourceFrame). Also sets gpuSourceActive so sourceDecoder skips redundant CPU ST map correction. Pass nil to disable.

func (*Switcher) SetInitialCommandSeq

func (s *Switcher) SetInitialCommandSeq(seq uint64)

SetInitialCommandSeq seeds lastCommandSeq from the persisted on-disk value before the HTTP listener and peer poller start. Only advances the tracker — never regresses — so a later wire-up that reads a stale file cannot undo a higher in-memory value. Called from app init.

func (*Switcher) SetKeyBridge

func (s *Switcher) SetKeyBridge(kb *graphics.KeyProcessorBridge)

SetKeyBridge attaches the upstream key bridge for chroma/luma keying. The bridge's ProcessYUV method is called in the video processing pipeline.

func (*Switcher) SetLabel

func (s *Switcher) SetLabel(ctx context.Context, sourceKey, label string) error

SetLabel sets a human-readable label for the given source.

The ctx parameter is accepted for API compatibility and future use (e.g. tracing) but is not currently checked; the operation is sub-millisecond.

func (*Switcher) SetMetrics

func (s *Switcher) SetMetrics(m *metrics.Metrics)

SetMetrics attaches Prometheus metrics to the switcher for production observability. When set, the switcher increments counters for cuts, transitions, and IDR gate events alongside the existing atomic debug counters.

func (*Switcher) SetMixer

func (s *Switcher) SetMixer(m audioStateProvider)

SetMixer attaches an audio mixer to the switcher for state broadcasts. When set, buildStateLocked will include audio channel states, master level, and program peak levels in the ControlRoomState. If the mixer also implements audioCutHandler, crossfade and AFV program changes are triggered automatically on Cut().

func (*Switcher) SetOnCaptionBroadcast

func (s *Switcher) SetOnCaptionBroadcast(fn func(*ccx.CaptionFrame))

SetOnCaptionBroadcast registers a callback that fires whenever passthrough captions are broadcast to the program relay. Used by the app layer to mirror captions to the program-preview relay.

func (*Switcher) SetOnCommandExecuted

func (s *Switcher) SetOnCommandExecuted(fn func())

SetOnCommandExecuted registers a callback that fires after each command dispatch attempt in drainCommandQueue (including failures, since error state changes are also relevant to browsers). Used by the app layer to trigger state broadcasts so browsers see state changes from timed commands.

func (*Switcher) SetOnFirstVideoPTS

func (s *Switcher) SetOnFirstVideoPTS(fn func(pts int64))

SetOnFirstVideoPTS registers a callback invoked once when the first program video frame enters the pipeline. Used to seed the audio mixer's PTS epoch so audio and video start from the same wall-clock moment.

func (*Switcher) SetOnFormatChange

func (s *Switcher) SetOnFormatChange(fn func(width, height, fpsNum, fpsDen int))

SetOnFormatChange registers a callback that fires after SetPipelineFormat rebuilds the CPU pipeline. Used by the app layer to tear down and recreate the GPU pipeline at the new dimensions.

func (*Switcher) SetOnKeyframeBroadcast

func (s *Switcher) SetOnKeyframeBroadcast(fn func())

SetOnKeyframeBroadcast registers a callback that fires when an IDR keyframe is broadcast to the program relay. Used by the app layer to gate relay audio until the first video keyframe is available for browser A/V sync.

func (*Switcher) SetOutputAudioCallback

func (s *Switcher) SetOutputAudioCallback(fn func(*media.AudioFrame))

SetOutputAudioCallback registers a callback invoked for each AAC frame produced by the sync loop. Intended for broadcasting to the program relay (browser viewers). Only used in reference clock mode.

func (*Switcher) SetOutputAudioCallbackDirect

func (s *Switcher) SetOutputAudioCallbackDirect(fn func(*media.AudioFrame))

SetOutputAudioCallbackDirect registers a callback invoked for each AAC frame produced by the sync loop, for direct delivery to the MPEG-TS muxer (SRT/recording output). Only used in reference clock mode.

func (*Switcher) SetOutputVideoCallback

func (s *Switcher) SetOutputVideoCallback(fn func(*media.VideoFrame))

SetOutputVideoCallback registers a direct video output callback that bypasses the relay for zero-latency delivery to the MPEG-TS muxer. Called synchronously from the encode goroutine — no channels, no goroutine hops. Pass nil to clear.

func (*Switcher) SetPicTimingConfig

func (s *Switcher) SetPicTimingConfig(cfg *caption.PicTimingConfig)

SetPicTimingConfig configures pic_timing SEI injection parameters. When set (non-nil), BuildPicTimingAndCaptionSEI is used instead of BuildSEINALU, producing a combined SEI NALU with pic_timing before caption data for broadcast decoder compatibility (Evertz, Harmonic, etc.).

func (*Switcher) SetPipelineBypass

func (s *Switcher) SetPipelineBypass(name string, bypass bool) error

SetPipelineBypass toggles the bypass flag for the named pipeline node. When the GPU pipeline is active, bypass is set there (GPU nodes have names like gpu_key, gpu_layout, etc). Falls back to the CPU pipeline for CPU node names. Returns an error if the node is protected or unknown in both pipelines.

func (*Switcher) SetPipelineCodecs

func (s *Switcher) SetPipelineCodecs(encoderFactory transition.EncoderFactory, pipelineCodec ...PipelineCodec)

SetPipelineCodecs creates the shared pipeline encoder for the video processing chain. Called from app.go during initialization. The pipelineCodec parameter determines wire format (AVC vs HEVC) and parameter set extraction (SPS/PPS vs VPS/SPS/PPS).

func (*Switcher) SetPipelineFormat

func (s *Switcher) SetPipelineFormat(f PipelineFormat) error

SetPipelineFormat changes the global pipeline format at runtime. Returns error if a transition is currently active. Propagates change to: frame budget, frame sync tick rate, encoder.

func (*Switcher) SetPipelineVideoInfoCallback

func (s *Switcher) SetPipelineVideoInfoCallback(cb func(vps, sps, pps []byte, width, height int))

SetPipelineVideoInfoCallback sets the callback invoked when the pipeline encoder produces a keyframe with new VPS/SPS/PPS parameters. For AVC pipelines, vps is always nil.

func (*Switcher) SetPreview

func (s *Switcher) SetPreview(ctx context.Context, sourceKey string) error

SetPreview sets the preview source. This does not affect the program output.

The ctx parameter is accepted for API compatibility and future use (e.g. tracing) but is not currently checked; the operation is sub-millisecond.

func (*Switcher) SetRawPreviewSink

func (s *Switcher) SetRawPreviewSink(sink RawVideoSink)

SetRawPreviewSink sets or clears the raw preview output tap. Same pattern as RawVideoSink and RawMonitorSink — receives a deep copy of each processed YUV420p frame after all processing but before H.264 encode. Used by the program preview encoder for low-bitrate browser delivery.

func (*Switcher) SetRawVideoSink

func (s *Switcher) SetRawVideoSink(sink RawVideoSink)

SetRawVideoSink sets or clears the raw video output tap. The sink receives a deep copy of each processed YUV420p frame after all video processing (keying, compositor) but before H.264 encode. This is used by MXL output to write raw video to shared memory. Pass nil to disable.

func (*Switcher) SetReferenceClock

func (s *Switcher) SetReferenceClock(rc *clock.ReferenceClock)

SetReferenceClock configures the Switcher to use a reference clock for synchronous A/V processing. Must be called before the switcher starts processing frames. When set, the synchronous processing loop is used instead of the async videoProcessingLoop.

func (*Switcher) SetSTMapRegistry

func (s *Switcher) SetSTMapRegistry(r *stmap.Registry)

SetSTMapRegistry sets the ST map registry for per-source correction. When set, each source decoder applies the assigned ST map warp after decode and resolution normalization, before fan-out to all consumers.

func (*Switcher) SetSeqPersistFn

func (s *Switcher) SetSeqPersistFn(fn func(uint64) error)

SetSeqPersistFn installs a callback invoked by the debounced persist loop whenever lastCommandSeq advances. Must be called before the first TrackCommandSeq advance that should survive a restart; setting fn starts the persist goroutine. Passing a nil fn disables persistence (used for tests + the single-engine fast path).

Contract: do NOT call after Close. Close drops the persist loop; a post-Close call would start a new loop with no teardown path and leak a goroutine. In practice this is called exactly once at app init.

func (*Switcher) SetSourceDecoderFactory

func (s *Switcher) SetSourceDecoderFactory(factory transition.DecoderFactory)

SetSourceDecoderFactory enables always-decode mode. When set, RegisterSource creates a per-source decoder that decodes H.264 to raw YUV at ingest time, eliminating keyframe waits on cuts and transitions. Must be called before any sources are registered.

func (*Switcher) SetSourceDelay

func (s *Switcher) SetSourceDelay(sourceKey string, delayMs int) error

SetSourceDelay sets the input delay for a source in milliseconds (0-500). A delay of 0 means no buffering (passthrough). Non-zero delays are used for lip-sync compensation. Returns ErrSourceNotFound if the source is not registered, or ErrInvalidDelay if the value is out of range.

func (*Switcher) SetSourcePosition

func (s *Switcher) SetSourcePosition(sourceKey string, position int) error

SetSourcePosition sets the display position for a source. Sources are ordered by position in the UI. If another source already occupies the target position, they swap positions.

func (*Switcher) SetSyncMixer

func (s *Switcher) SetSyncMixer(m syncMixerInterface)

SetSyncMixer attaches an audio mixer for the synchronous processing loop. This is the mixer whose ProduceSamples method is called on each tick.

func (*Switcher) SetTransitionAbortThreshold

func (s *Switcher) SetTransitionAbortThreshold(threshold float64) error

SetTransitionAbortThreshold updates the T-bar pull-back abort threshold. Range [0.05, 0.5]. The new value takes effect on the next SetTransitionPosition call; an in-flight threshold evaluation is not retroactively re-checked.

func (*Switcher) SetTransitionConfig

func (s *Switcher) SetTransitionConfig(config TransitionConfig)

SetTransitionConfig stores the transition codec configuration under lock.

func (*Switcher) SetTransitionEngine

func (s *Switcher) SetTransitionEngine(engine *transition.Engine)

SetTransitionEngine sets the transition engine and transition state for testing the synchronous (ref clock) transition path from external packages. This bypasses the async StartTransition flow which spawns goroutines.

func (*Switcher) SetTransitionPosition

func (s *Switcher) SetTransitionPosition(ctx context.Context, position float64) error

SetTransitionPosition sets the T-bar position during an active transition.

If the operator has driven the T-bar forward past 2× transitionAbortThreshold and then pulls back below that threshold, the transition is internally aborted — the engine is torn down, program reverts to the from-source, and a fresh StartTransition is required to retry. The 2× max-forward gate prevents trivial jitter near zero from triggering unwanted aborts.

The ctx parameter is accepted for API compatibility and future use (e.g. tracing) but is not currently checked; the operation is sub-millisecond.

func (*Switcher) SetWipeMapStore

func (s *Switcher) SetWipeMapStore(store *wipemap.WipeMapStore)

SetWipeMapStore sets the wipe map store used to resolve gradient maps for gradient-map-based wipe transitions.

func (*Switcher) SourceKeys

func (s *Switcher) SourceKeys() []string

SourceKeys returns the keys of all registered sources. Lock-free via sourcesAtomic — no configMu acquired.

func (*Switcher) StartHealthMonitor

func (s *Switcher) StartHealthMonitor(interval time.Duration)

StartHealthMonitor begins periodic health checking at the given interval. When any source's health status changes, a state snapshot is published to all registered state-change callbacks.

func (*Switcher) StartSyncLoop

func (s *Switcher) StartSyncLoop()

StartSyncLoop starts the synchronous processing loop. Must be called after SetReferenceClock. The reference clock must be started separately. Safe to call multiple times — second call is a no-op (Issue 10).

func (*Switcher) StartTransition

func (s *Switcher) StartTransition(ctx context.Context, sourceKey string, transType string, durationMs int, wipeDirection string, opts ...TransitionOption) error

StartTransition begins a mix/dip/wipe/stinger transition from the current program source to the given target source. Frames from both sources are routed to the transition engine which produces blended output on the program relay. wipeDirection is only used when transType is "wipe"; pass empty string otherwise.

The ctx parameter is checked before the expensive codec initialization phase; a cancelled context will abort the transition early and roll back state.

func (*Switcher) State

func (s *Switcher) State() internal.ControlRoomState

State returns a snapshot of the current control room state.

func (*Switcher) ThumbnailCache

func (s *Switcher) ThumbnailCache() *ThumbnailCache

ThumbnailCache returns the switcher's thumbnail cache for pipeline routing view JPEG captures. Never nil — created in New().

func (*Switcher) TrackCommandSeq

func (s *Switcher) TrackCommandSeq(seq uint64)

TrackCommandSeq updates lastCommandSeq to the given value if it's higher than the current value. Uses CAS loop for lock-free thread safety. Marks seqDirty so the debounced persist loop flushes the new high-water mark to disk on its next tick — see seqPersistLoop.

func (*Switcher) TransitionAbortThreshold

func (s *Switcher) TransitionAbortThreshold() float64

TransitionAbortThreshold returns the configured T-bar pull-back abort threshold in (0.05, 0.5].

func (*Switcher) UnregisterSource

func (s *Switcher) UnregisterSource(key string)

UnregisterSource removes a source from the switcher and detaches its viewer from the source Relay. If the removed source was on program or preview, those fields are cleared.

type TallyStatus

type TallyStatus string

TallyStatus represents the tally light state for a source.

const (
	TallyProgram  TallyStatus = "program"
	TallyPreview  TallyStatus = "preview"
	TallyKeyFill  TallyStatus = "key_fill"
	TallyKeyAlpha TallyStatus = "key_alpha"
	TallyIdle     TallyStatus = "idle"
)

type ThumbnailCache

type ThumbnailCache struct {
	// contains filtered or unexported fields
}

ThumbnailCache stores per-node JPEG thumbnails for the routing view.

func NewThumbnailCache

func NewThumbnailCache() *ThumbnailCache

NewThumbnailCache creates a new empty thumbnail cache.

func (*ThumbnailCache) ClearRequest

func (tc *ThumbnailCache) ClearRequest()

ClearRequest clears the requested flag. Called after a capture cycle completes so the next cycle requires a fresh RequestCapture from the client.

func (*ThumbnailCache) Drops

func (tc *ThumbnailCache) Drops() int64

Drops returns the cumulative count of thumbnail encodes skipped because an earlier encode for the same key was still in flight.

func (*ThumbnailCache) Get

func (tc *ThumbnailCache) Get(nodeID string) (ThumbnailInfo, bool)

Get retrieves a single thumbnail by node ID.

func (*ThumbnailCache) GetAll

func (tc *ThumbnailCache) GetAll() map[string]ThumbnailInfo

GetAll returns a snapshot of all cached thumbnails.

func (*ThumbnailCache) RequestCapture

func (tc *ThumbnailCache) RequestCapture()

RequestCapture marks that a client is actively polling for thumbnails. Sets the requested flag and updates the last poll timestamp.

func (*ThumbnailCache) ShouldCapture

func (tc *ThumbnailCache) ShouldCapture() bool

ShouldCapture returns true if a client has requested capture AND the last poll was within the pollTimeout window (5s). This ensures capture stops automatically when clients disconnect.

func (*ThumbnailCache) SpawnEncode

func (tc *ThumbnailCache) SpawnEncode(nodeID string, yuv []byte, srcW, srcH int) bool

SpawnEncode starts an async JPEG encode of yuv for nodeID if no encode is already running for that key. Returns true if spawned, false if dropped (drops are counted via Drops()). Callers must have taken a deep copy of yuv — the spawned goroutine owns the slice until the encode completes.

At most one encode per node key is in flight at a time, so a transient encoder stall (CPU contention, cgo libjpeg) cannot accumulate unbounded goroutines and per-frame ~3 MB YUV copies.

func (*ThumbnailCache) Store

func (tc *ThumbnailCache) Store(nodeID string, jpeg []byte, w, h int)

Store saves a JPEG thumbnail for the given node ID.

type ThumbnailInfo

type ThumbnailInfo struct {
	JPEG       []byte
	Width      int
	Height     int
	CapturedAt time.Time
}

ThumbnailInfo is the public read-only view of a cached thumbnail.

type TileCache

type TileCache struct {
	// contains filtered or unexported fields
}

TileCache holds the latest downsampled YUV420 tile for each registered source, keyed by source key. It is the server-side backing store for the multiview mosaic compositor (infrastructure only until wired into the switcher).

Thread model:

  • The `slots` map is written under `mu` (writer-serialized) and read lock-free via atomic.Pointer CoW, matching the `sourcesAtomic` pattern already in use for Switcher.
  • Each slot stores the latest tile as an atomic.Pointer to an immutable YUV420 byte slice; publishers allocate a fresh buffer per frame and atomically swap. Readers load the pointer and copy from an immutable buffer, so there is never a write-during-read hazard and the race detector stays quiet.
  • Tile buffers are sized at tile resolution (tileW*tileH*3/2). At 32 sources × 30 fps × ~48 KB the allocation rate is ~46 MB/s, comfortably within the Go GC's steady-state budget. We do not reclaim prior buffers into a sync.Pool — a reader may still hold the prior pointer when Swap returns it, and any Put-into-pool would race with that reader. Defer pool reclamation until a benchmark demands it and an epoch-reclamation scheme is wired.

func NewTileCache

func NewTileCache(tileW, tileH int) (*TileCache, error)

NewTileCache constructs a TileCache that stores tiles at (tileW, tileH). Returns ErrOddDimensions if tileW or tileH are zero or odd.

func (*TileCache) HasTile

func (tc *TileCache) HasTile(key string) bool

HasTile reports whether a tile is registered for the given source key.

func (*TileCache) Label

func (tc *TileCache) Label(key string) string

Label returns the current label for the given source, or "" if not registered. Safe for concurrent use.

func (*TileCache) PublishYUV420

func (tc *TileCache) PublishYUV420(key string, src []byte, srcW, srcH int)

PublishYUV420 downsamples the source YUV420 frame into a fresh tile buffer and atomically publishes it to the slot. No-op if the source is not registered, the source dimensions are invalid, or `src` is shorter than the expected YUV420 frame size for (srcW, srcH). The caller may reuse `src` immediately after return.

func (*TileCache) ReadInto

func (tc *TileCache) ReadInto(key string, dst []byte) (ok, stale bool)

ReadInto copies the current tile for the given source into dst. Returns (true, stale) on success where stale=true if the tile has not been updated within the configured stale threshold. Returns (false, false) if the source is not registered, dst is too small, or no tile has been published yet.

func (*TileCache) RegisterTile

func (tc *TileCache) RegisterTile(key, label string)

RegisterTile allocates a slot for the given source key. Safe to call repeatedly; a re-register with the same key keeps the existing slot but updates the label.

func (*TileCache) SetLabel

func (tc *TileCache) SetLabel(key, label string)

SetLabel updates the label shown in the mosaic for the given source. No-op if the source is not registered.

func (*TileCache) SetStaleThreshold

func (tc *TileCache) SetStaleThreshold(d time.Duration)

SetStaleThreshold overrides the "no update in X" threshold used by ReadInto to report stale tiles. Default is 500 ms.

func (*TileCache) UnregisterTile

func (tc *TileCache) UnregisterTile(key string)

UnregisterTile removes a source's tile. Readers that hold a pointer obtained before this call keep working on their loaded buffer — the slot's last buffer is returned to the pool once no one holds it (via Go GC; we do not reclaim eagerly).

type TransitionConfig

type TransitionConfig struct {
	DecoderFactory transition.DecoderFactory
}

TransitionConfig holds the codec factories needed to create transition engines.

type TransitionOption

type TransitionOption func(*transitionOpts)

TransitionOption configures optional parameters for StartTransition.

func WithEasing

func WithEasing(ec *transition.EasingCurve) TransitionOption

WithEasing sets the easing curve for the transition.

func WithStingerData

func WithStingerData(sd *transition.StingerData) TransitionOption

WithStingerData sets the stinger overlay data for a stinger transition.

func WithWipeConfig

func WithWipeConfig(wc *wipemap.WipeConfig) TransitionOption

WithWipeConfig sets gradient-map wipe parameters for a wipe transition.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL