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
- Variables
- func DefaultBitrateForResolution(width, height int) int
- func Downconvert422_10to420(src, dst []byte, width, height int) error
- func SAD10(a, b []byte, aOff, bOff, stride, bw, bh int) int
- func ScaleAndEncodeJPEG(yuv []byte, srcW, srcH, dstW, dstH int) ([]byte, error)
- func Upconvert420to422_10(src, dst []byte, width, height int) error
- func ValidFormatPreset(name string) bool
- type AsyncMetricsProvider
- type BuiltinFrameTap
- type DelayBuffer
- type FRCQuality
- type FramePool
- type FrameSynchronizer
- func (fs *FrameSynchronizer) AddSource(key string)
- func (fs *FrameSynchronizer) BufferOnly() bool
- func (fs *FrameSynchronizer) DebugSnapshot() map[string]any
- func (fs *FrameSynchronizer) FRCQuality() FRCQuality
- func (fs *FrameSynchronizer) GetSourcePTSCorrection(sourceKey string) int64
- func (fs *FrameSynchronizer) IngestAudio(sourceKey string, frame *media.AudioFrame)
- func (fs *FrameSynchronizer) IngestRawVideo(sourceKey string, pf *ProcessingFrame)
- func (fs *FrameSynchronizer) IngestVideo(sourceKey string, frame *media.VideoFrame)
- func (fs *FrameSynchronizer) ReleaseFPS() float64
- func (fs *FrameSynchronizer) RemoveSource(key string)
- func (fs *FrameSynchronizer) SetBufferOnly(enabled bool)
- func (fs *FrameSynchronizer) SetClockDriven(enabled bool)
- func (fs *FrameSynchronizer) SetFRCQuality(q FRCQuality)
- func (fs *FrameSynchronizer) SetFramePool(pool *FramePool)
- func (fs *FrameSynchronizer) SetProgramSource(key string)
- func (fs *FrameSynchronizer) SetSourceFRCQuality(key string, q FRCQuality)
- func (fs *FrameSynchronizer) SetTickRate(d time.Duration)
- func (fs *FrameSynchronizer) SourceCount() int
- func (fs *FrameSynchronizer) SourceDrift(sourceKey string) (int64, int)
- func (fs *FrameSynchronizer) Start()
- func (fs *FrameSynchronizer) Stop()
- type GPUPipelineRunner
- type GPURefCounter
- type GPUSourceManagerIface
- type GPUStingerFrame
- type MCFIState
- type PerfSourceSample
- type PerfSwitcherSample
- type Pipeline
- func (p *Pipeline) Build(format PipelineFormat, pool *FramePool, nodes []PipelineNode) error
- func (p *Pipeline) BypassState() map[string]bool
- func (p *Pipeline) Close() error
- func (p *Pipeline) RestoreBypass(state map[string]bool)
- func (p *Pipeline) Run(frame *ProcessingFrame) *ProcessingFrame
- func (p *Pipeline) SetBypass(nodeName string, bypass bool) error
- func (p *Pipeline) SetMetrics(m *metrics.Metrics)
- func (p *Pipeline) Snapshot() map[string]any
- func (p *Pipeline) TotalLatency() time.Duration
- func (p *Pipeline) Wait()
- type PipelineCodec
- type PipelineColorConfig
- type PipelineColorFormat
- type PipelineEpoch
- type PipelineFormat
- type PipelineNode
- type ProcessingFrame
- func (pf *ProcessingFrame) CbPlane() []byte
- func (pf *ProcessingFrame) CrPlane() []byte
- func (pf *ProcessingFrame) DeepCopy() *ProcessingFrame
- func (pf *ProcessingFrame) FrameDataSize() int
- func (pf *ProcessingFrame) MakeWritable(pool *FramePool)
- func (pf *ProcessingFrame) Ref()
- func (pf *ProcessingFrame) Refs() int32
- func (pf *ProcessingFrame) ReleaseYUV()
- func (pf *ProcessingFrame) SetPool(pool *FramePool)
- func (pf *ProcessingFrame) SetRefs(n int32)
- func (pf *ProcessingFrame) YPlane() []byte
- type RawVideoSink
- type SourceHealthStatus
- type State
- type Switcher
- func (s *Switcher) AbortTransition()
- func (s *Switcher) AvailableEncoders() []codec.EncoderInfo
- func (s *Switcher) AvailableEncodersInternal() []internal.EncoderInfo
- func (s *Switcher) BroadcastToProgramFunc() func(*media.VideoFrame)
- func (s *Switcher) BroadcastWithCaptionsFunc() func(*media.VideoFrame)
- func (s *Switcher) BuildPipeline() error
- func (s *Switcher) BuiltinSourceFrame(key string) ([]byte, int, int, error)
- func (s *Switcher) Close()
- func (s *Switcher) Cut(ctx context.Context, sourceKey string) error
- func (s *Switcher) DebugSnapshot() map[string]any
- func (s *Switcher) EnableWallClockVideoPTS()
- func (s *Switcher) EncoderName() string
- func (s *Switcher) FTBDurationMs() int
- func (s *Switcher) FadeToBlack(ctx context.Context) error
- func (s *Switcher) ForceNextIDRPtr() *atomic.Bool
- func (s *Switcher) GetFramePool() *FramePool
- func (s *Switcher) GetRawPreviewSink() RawVideoSink
- func (s *Switcher) GetRawVideoSink() RawVideoSink
- func (s *Switcher) GetSourceDelay(sourceKey string) int
- func (s *Switcher) IngestRawVideo(sourceKey string, pf *ProcessingFrame)
- func (s *Switcher) IngestReplayVideo(sourceKey string, pf *ProcessingFrame)
- func (s *Switcher) LastBroadcastVideoPTS() int64
- func (s *Switcher) OnStateChange(cb func(internal.ControlRoomState))
- func (s *Switcher) PerfSample() PerfSwitcherSample
- func (s *Switcher) PicTimingConfig() *caption.PicTimingConfig
- func (s *Switcher) PipelineFormat() PipelineFormat
- func (s *Switcher) PipelineSnapshot() map[string]any
- func (s *Switcher) ProgramRelay() *distribution.Relay
- func (s *Switcher) ProgramSource() string
- func (s *Switcher) RebuildPipeline()
- func (s *Switcher) RegisterMXLSource(key string)
- func (s *Switcher) RegisterRISTSource(key string)
- func (s *Switcher) RegisterReplaySource(key string)
- func (s *Switcher) RegisterSRTSource(key string)
- func (s *Switcher) RegisterSource(key string, relay *distribution.Relay)
- func (s *Switcher) RegisterVirtualSource(key string, relay *distribution.Relay)
- func (s *Switcher) RequestKeyframe()
- func (s *Switcher) SetAACBuffer(buf *clock.AACFrameBuffer)
- func (s *Switcher) SetAudioHandler(handler func(sourceKey string, frame *media.AudioFrame))
- func (s *Switcher) SetAudioTransition(handler audioTransitionHandler)
- func (s *Switcher) SetAvailableEncoders(encoders []codec.EncoderInfo)
- func (s *Switcher) SetBuiltinFrameTap(fn BuiltinFrameTap)
- func (s *Switcher) SetCaptionManager(cm captionManager)
- func (s *Switcher) SetClockDrivenSync(enabled bool)
- func (s *Switcher) SetClockRecovery(cr *clock.ClockRecovery)
- func (s *Switcher) SetCodecInfo(encoder, decoder string, hwAccel bool)
- func (s *Switcher) SetCommandQueue(q *cmdqueue.Queue, d *cmdqueue.Dispatcher)
- func (s *Switcher) SetCompositor(c *graphics.Compositor)
- func (s *Switcher) SetDVECompositor(dc *dve.Compositor)
- func (s *Switcher) SetEncoder(name string) error
- func (s *Switcher) SetEncoderOpts(opts *codec.EncoderOptions)
- func (s *Switcher) SetFRCQuality(q FRCQuality)
- func (s *Switcher) SetFTBDuration(durationMs int) error
- func (s *Switcher) SetFrameBudget(ns int64)
- func (s *Switcher) SetFrameSync(enabled bool, tickRate time.Duration)
- func (s *Switcher) SetFrameSyncBufferOnly(enabled bool)
- func (s *Switcher) SetGPUPipeline(gp GPUPipelineRunner)
- func (s *Switcher) SetGPUSourceManager(mgr GPUSourceManagerIface)
- func (s *Switcher) SetInitialCommandSeq(seq uint64)
- func (s *Switcher) SetKeyBridge(kb *graphics.KeyProcessorBridge)
- func (s *Switcher) SetLabel(ctx context.Context, sourceKey, label string) error
- func (s *Switcher) SetMetrics(m *metrics.Metrics)
- func (s *Switcher) SetMixer(m audioStateProvider)
- func (s *Switcher) SetOnCaptionBroadcast(fn func(*ccx.CaptionFrame))
- func (s *Switcher) SetOnCommandExecuted(fn func())
- func (s *Switcher) SetOnFirstVideoPTS(fn func(pts int64))
- func (s *Switcher) SetOnFormatChange(fn func(width, height, fpsNum, fpsDen int))
- func (s *Switcher) SetOnKeyframeBroadcast(fn func())
- func (s *Switcher) SetOutputAudioCallback(fn func(*media.AudioFrame))
- func (s *Switcher) SetOutputAudioCallbackDirect(fn func(*media.AudioFrame))
- func (s *Switcher) SetOutputVideoCallback(fn func(*media.VideoFrame))
- func (s *Switcher) SetPicTimingConfig(cfg *caption.PicTimingConfig)
- func (s *Switcher) SetPipelineBypass(name string, bypass bool) error
- func (s *Switcher) SetPipelineCodecs(encoderFactory transition.EncoderFactory, pipelineCodec ...PipelineCodec)
- func (s *Switcher) SetPipelineFormat(f PipelineFormat) error
- func (s *Switcher) SetPipelineVideoInfoCallback(cb func(vps, sps, pps []byte, width, height int))
- func (s *Switcher) SetPreview(ctx context.Context, sourceKey string) error
- func (s *Switcher) SetRawPreviewSink(sink RawVideoSink)
- func (s *Switcher) SetRawVideoSink(sink RawVideoSink)
- func (s *Switcher) SetReferenceClock(rc *clock.ReferenceClock)
- func (s *Switcher) SetSTMapRegistry(r *stmap.Registry)
- func (s *Switcher) SetSeqPersistFn(fn func(uint64) error)
- func (s *Switcher) SetSourceDecoderFactory(factory transition.DecoderFactory)
- func (s *Switcher) SetSourceDelay(sourceKey string, delayMs int) error
- func (s *Switcher) SetSourcePosition(sourceKey string, position int) error
- func (s *Switcher) SetSyncMixer(m syncMixerInterface)
- func (s *Switcher) SetTransitionAbortThreshold(threshold float64) error
- func (s *Switcher) SetTransitionConfig(config TransitionConfig)
- func (s *Switcher) SetTransitionEngine(engine *transition.Engine)
- func (s *Switcher) SetTransitionPosition(ctx context.Context, position float64) error
- func (s *Switcher) SetWipeMapStore(store *wipemap.WipeMapStore)
- func (s *Switcher) SourceKeys() []string
- func (s *Switcher) StartHealthMonitor(interval time.Duration)
- func (s *Switcher) StartSyncLoop()
- func (s *Switcher) StartTransition(ctx context.Context, sourceKey string, transType string, durationMs int, ...) error
- func (s *Switcher) State() internal.ControlRoomState
- func (s *Switcher) ThumbnailCache() *ThumbnailCache
- func (s *Switcher) TrackCommandSeq(seq uint64)
- func (s *Switcher) TransitionAbortThreshold() float64
- func (s *Switcher) UnregisterSource(key string)
- type TallyStatus
- type ThumbnailCache
- func (tc *ThumbnailCache) ClearRequest()
- func (tc *ThumbnailCache) Drops() int64
- func (tc *ThumbnailCache) Get(nodeID string) (ThumbnailInfo, bool)
- func (tc *ThumbnailCache) GetAll() map[string]ThumbnailInfo
- func (tc *ThumbnailCache) RequestCapture()
- func (tc *ThumbnailCache) ShouldCapture() bool
- func (tc *ThumbnailCache) SpawnEncode(nodeID string, yuv []byte, srcW, srcH int) bool
- func (tc *ThumbnailCache) Store(nodeID string, jpeg []byte, w, h int)
- type ThumbnailInfo
- type TileCache
- func (tc *TileCache) HasTile(key string) bool
- func (tc *TileCache) Label(key string) string
- func (tc *TileCache) PublishYUV420(key string, src []byte, srcW, srcH int)
- func (tc *TileCache) ReadInto(key string, dst []byte) (ok, stale bool)
- func (tc *TileCache) RegisterTile(key, label string)
- func (tc *TileCache) SetLabel(key, label string)
- func (tc *TileCache) SetStaleThreshold(d time.Duration)
- func (tc *TileCache) UnregisterTile(key string)
- type TransitionConfig
- type TransitionOption
Constants ¶
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.
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 )
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 ¶
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.
var DefaultFormat = FormatPresets["1080p29.97"]
DefaultFormat is the startup default when no --format flag is provided.
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.
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.
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 ¶
DefaultBitrateForResolution returns the minimum encoding bitrate for broadcast-quality output at the given resolution.
func Downconvert422_10to420 ¶
Downconvert422_10to420 converts a YUV422P10LE frame to YUV420P 8-bit.
func SAD10 ¶
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 ¶
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 ¶
Upconvert420to422_10 converts a YUV420P 8-bit frame to YUV422P10LE.
func ValidFormatPreset ¶
ValidFormatPreset returns true if the name is a recognized preset.
Types ¶
type AsyncMetricsProvider ¶
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.
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 ¶
Acquire returns a YUV buffer. If the pool is exhausted, allocates fresh.
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
BypassState returns the current bypass state for all nodes.
func (*Pipeline) RestoreBypass ¶
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 ¶
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 ¶
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) TotalLatency ¶
TotalLatency returns sum of all active nodes' reported latencies. Used for automatic audio delay compensation (lip-sync).
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 )
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
EncoderName returns the name of the currently active encoder.
func (*Switcher) FTBDurationMs ¶
func (*Switcher) FadeToBlack ¶
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 ¶
ForceNextIDRPtr returns a pointer to the forceNextIDR atomic for GPU encode.
func (*Switcher) GetFramePool ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
RegisterMXLSource registers a source that provides raw YUV420p frames directly (no Prism relay/viewer). Used for MXL shared-memory sources.
func (*Switcher) RegisterRISTSource ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
SourceKeys returns the keys of all registered sources. Lock-free via sourcesAtomic — no configMu acquired.
func (*Switcher) StartHealthMonitor ¶
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 ¶
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 ¶
TransitionAbortThreshold returns the configured T-bar pull-back abort threshold in (0.05, 0.5].
func (*Switcher) UnregisterSource ¶
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.
type ThumbnailInfo ¶
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 ¶
NewTileCache constructs a TileCache that stores tiles at (tileW, tileH). Returns ErrOddDimensions if tileW or tileH are zero or odd.
func (*TileCache) Label ¶
Label returns the current label for the given source, or "" if not registered. Safe for concurrent use.
func (*TileCache) PublishYUV420 ¶
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 ¶
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 ¶
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 ¶
SetLabel updates the label shown in the mosaic for the given source. No-op if the source is not registered.
func (*TileCache) SetStaleThreshold ¶
SetStaleThreshold overrides the "no update in X" threshold used by ReadInto to report stale tiles. Default is 500 ms.
func (*TileCache) UnregisterTile ¶
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.
Source Files
¶
- builtin_source.go
- cmd_errors.go
- delay_buffer.go
- doc.go
- format.go
- format_convert.go
- frame_pool.go
- frame_sync.go
- frc.go
- frc_10bit.go
- frc_me.go
- handlers.go
- health.go
- hotstate.go
- mcfi_interpolate.go
- mcfi_interpolate_10bit.go
- mcfi_warp_10bit.go
- mcfi_warp_fast.go
- node_compositor.go
- node_dve_compositor.go
- node_encode.go
- node_raw_sink.go
- node_stmap.go
- node_upstream_key.go
- perf.go
- pipeline_codecs.go
- pipeline_loop.go
- pipeline_node.go
- pipeline_thumbnails.go
- processing_frame.go
- sad10_dispatch.go
- source_decoder.go
- source_viewer.go
- switcher.go
- sync_loop.go
- testing.go
- tile_cache.go
- types.go