transition

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: 13 Imported by: 0

Documentation

Overview

Package transition implements the video transition engine for dissolves, dips, wipes, fade-to-black, and stinger transitions.

All blending operates directly in YUV420 (BT.709) space, matching hardware broadcast mixers (ATEM, Ross) and avoiding costly YUV-RGB round-trip conversions. The Engine is created per-transition and destroyed on complete or abort, returning to zero-CPU passthrough between transitions.

Key types:

  • Engine: Per-transition lifecycle (start, ingest frames, complete/abort)
  • FrameBlender: YUV420 blending for mix, dip, wipe, FTB, and stinger
  • EngineConfig: Encoder/decoder factories and transition parameters
  • StingerData: Pre-decoded PNG sequence with per-pixel alpha plane

Wipe transitions support 6 directions (horizontal, vertical, box) using per-pixel threshold masks with a 4px soft edge. The T-bar provides manual position control via throttled REST updates at 20 Hz.

Index

Examples

Constants

View Source
const (
	DefaultBitrate = 4_000_000 // 4 Mbps
	DefaultFPS     = 30.0
	DefaultGOPSecs = 2 // IDR interval in seconds (program output)

	// PreviewGOPSecs is a shorter IDR interval for preview/relay encodes.
	// Shorter GOPs mean faster recovery after transport stalls — the browser
	// waits at most 1s for the next keyframe instead of 2s.
	PreviewGOPSecs = 1

	// PreviewGOPFrames is the IDR interval in frames for preview encodes.
	// ~15 frames at 30fps = 0.5s GOPs. Halving the GOP reduces the
	// damaged-group cascade impact on jittery networks: each drop loses
	// at most ~14 frames instead of ~29.
	PreviewGOPFrames = 15
)

Default encoder parameters for the pipeline codec pool.

View Source
const DefaultTimeout = 10 * time.Second

DefaultTimeout is the default watchdog timeout for transition frame starvation. If no frames arrive from either source for this duration, the transition is aborted.

Variables

View Source
var (
	ErrActive    = errors.New("transition: already active")
	ErrFTBActive = errors.New("transition: FTB is active")
)

Sentinel errors for the transition package.

ValidEasingTypes is the set of all recognized easing types.

ValidWipeDirections is the set of all valid wipe directions.

Functions

func BlendDip10

func BlendDip10(dst, a, b []byte, width, height, pos256 int, limitedRange bool) error

BlendDip10 performs a two-phase dip-to-black transition between two YUV422P10LE frames. pos256 ranges from 0 (100% A) to 256 (100% B), with midpoint (128) being full black. Phase 1 (0-128): A fades to black. Phase 2 (128-256): black fades to B. limitedRange controls the black level: true uses Y=64, false uses Y=0.

func BlendFTB10

func BlendFTB10(dst, src []byte, width, height, pos256 int, limitedRange bool) error

BlendFTB10 fades a single YUV422P10LE source to black. pos256 ranges from 0 (full source) to 256 (fully black). limitedRange controls the black level: true uses Y=64, false uses Y=0. Chroma fades to neutral (512) in both ranges.

func BlendMix10

func BlendMix10(dst, a, b []byte, width, height, pos256 int) error

BlendMix10 performs linear interpolation between two YUV422P10LE frames. pos256 ranges from 0 (100% A) to 256 (100% B). SIMD-accelerated on amd64 (AVX2) and arm64 (NEON), ~15x faster than pure Go.

func BlendStinger10

func BlendStinger10(dst, base, overlay []byte, width, height int, alpha []byte) error

BlendStinger10 composites a stinger overlay onto a base YUV422P10LE frame using per-pixel alpha. alpha is an 8-bit per-luma-pixel alpha mask (0 = base, 255 = overlay), same dimensions as Y plane. For chroma planes, alpha is downsampled 2:1 horizontally only (4:2:2 subsampling).

func BlendUniformBytes

func BlendUniformBytes(dst, a, b []byte, pos int)

BlendUniformBytes applies uniform alpha blend across byte slices:

dst[i] = (a[i]*(256-pos) + b[i]*pos + 128) >> 8

pos must be 0-256. SIMD-accelerated on amd64 and arm64. dst, a, and b must all have the same length. Returns immediately if len(dst) == 0.

func BlendWipe10

func BlendWipe10(dst, a, b []byte, width, height int, alpha []byte) error

BlendWipe10 performs a per-pixel alpha wipe between two YUV422P10LE frames. alpha is an 8-bit per-luma-pixel alpha mask (0 = A, 255 = B), same dimensions as Y plane. For chroma planes, alpha is downsampled 2:1 horizontally only (4:2:2 subsampling).

func BoxShrink2xYUV420

func BoxShrink2xYUV420(src []byte, srcW, srcH int, dst []byte, dstW, dstH int)

BoxShrink2xYUV420 downscales a YUV420 frame by exactly 2x in both dimensions using box-average (2×2 block average for Y, 2×2 for chroma which is already at half-res). This is ~4x faster than bilinear for the same pixel count because each source pixel is read exactly once (no scatter-gather).

srcW and srcH must be even. dstW = srcW/2, dstH = srcH/2.

func RGBToYUV420

func RGBToYUV420(rgb []byte, width, height int, yuv []byte)

RGBToYUV420 converts interleaved RGB to YUV420 planar (full-range) using BT.709 coefficients.

func RGBToYUV_BT709Limited

func RGBToYUV_BT709Limited(r, g, b uint8) (y, cb, cr uint8)

RGBToYUV_BT709Limited converts an RGB pixel to limited-range BT.709 YCbCr. Output ranges: Y [16, 235], Cb [16, 240], Cr [16, 240].

func ScaleYUV420

func ScaleYUV420(src []byte, srcW, srcH int, dst []byte, dstW, dstH int)

ScaleYUV420 scales a YUV420 planar frame from (srcW x srcH) to (dstW x dstH) using bilinear interpolation. Both src and dst must be sized for their respective resolutions: video.YUV420FrameSize(w,h) bytes (Y[w*h] + Cb[w/2*h/2] + Cr[w/2*h/2]).

When source and destination dimensions match, a plain copy is performed (zero interpolation overhead).

The scaler uses 16.16 fixed-point arithmetic for sub-pixel coordinate mapping, which avoids floating-point per-pixel while maintaining accuracy for broadcast resolutions up to 4K.

func ScaleYUV420Lanczos

func ScaleYUV420Lanczos(src []byte, srcW, srcH int, dst []byte, dstW, dstH int)

ScaleYUV420Lanczos scales a YUV420 planar frame from (srcW x srcH) to (dstW x dstH) using Lanczos-3 interpolation. Each plane is scaled at its native resolution: full for Y, half for Cb/Cr.

The scaler uses separable filtering (horizontal pass then vertical pass) with precomputed kernel weights for performance. The Lanczos-3 kernel has a support radius of 3 pixels and produces sharper output than bilinear.

func ScaleYUV420Preview

func ScaleYUV420Preview(src []byte, srcW, srcH int, dst []byte, dstW, dstH int, boxBuf *[]byte)

ScaleYUV420Preview scales a YUV420 frame optimized for preview encoding. On amd64, uses box-shrink 2x + bilinear for the remainder when the source is ≥1.5x the destination. This avoids the expensive scalar bilinear gather on 75% of the source pixels.

func ScaleYUV420WithQuality

func ScaleYUV420WithQuality(src []byte, srcW, srcH int, dst []byte, dstW, dstH int, quality ScaleQuality)

ScaleYUV420WithQuality scales a YUV420 planar frame using the selected algorithm. Same buffer layout as ScaleYUV420: src and dst must be video.YUV420FrameSize(w,h) bytes.

func ScaleYUV422P10

func ScaleYUV422P10(src []byte, srcW, srcH int, dst []byte, dstW, dstH int) error

ScaleYUV422P10 scales a YUV422P10LE frame using bilinear interpolation. src is srcW*srcH*4 bytes, dst is dstW*dstH*4 bytes.

Buffer layout (YUV422P10LE planar):

  • Y plane: srcW * srcH samples × 2 bytes (offset 0)
  • Cb plane: srcW/2 * srcH samples × 2 bytes
  • Cr plane: srcW/2 * srcH samples × 2 bytes
  • Total: srcW * srcH * 4 bytes

The scaler uses 16.16 fixed-point arithmetic for sub-pixel coordinate mapping (same approach as the 8-bit ScaleYUV420 scaler) but operates on uint16 samples with 32-bit intermediate math and clamps to [0, 1023].

Chroma planes are full height (4:2:2), unlike 4:2:0 which halves vertically.

func ScaleYUV422P10Lanczos

func ScaleYUV422P10Lanczos(src []byte, srcW, srcH int, dst []byte, dstW, dstH int) error

ScaleYUV422P10Lanczos scales a YUV422P10LE frame using Lanczos-3 interpolation. Each plane is scaled at its native resolution: full for Y, half-width for Cb/Cr. SIMD optimization is deferred; this is a pure Go implementation.

func ScaleYUV422P10WithQuality

func ScaleYUV422P10WithQuality(src []byte, srcW, srcH int, dst []byte, dstW, dstH int, quality ScaleQuality) error

ScaleYUV422P10WithQuality scales a YUV422P10LE frame using the selected algorithm.

func YUV420ToRGB

func YUV420ToRGB(yuv []byte, width, height int, rgb []byte)

YUV420ToRGB converts YUV420 planar (full-range) to interleaved RGB using BT.709 coefficients. yuv layout: Y[w*h] + U[w/2 * h/2] + V[w/2 * h/2] rgb layout: R,G,B,R,G,B,... (w*h*3 bytes)

Example
package main

import (
	"fmt"

	"github.com/zsiec/switchframe/server/transition"
)

func main() {
	// 2x2 pure white frame in YUV420 full-range.
	// Y=255 for all pixels, Cb=128 (neutral), Cr=128 (neutral).
	yuv := []byte{
		255, 255, 255, 255, // Y plane (2x2)
		128, // Cb plane (1x1, subsampled)
		128, // Cr plane (1x1, subsampled)
	}
	rgb := make([]byte, 2*2*3)

	transition.YUV420ToRGB(yuv, 2, 2, rgb)

	// With neutral chroma, Y=255 maps to RGB(255, 255, 255).
	fmt.Printf("pixel[0]: R=%d G=%d B=%d\n", rgb[0], rgb[1], rgb[2])
	fmt.Printf("pixel[3]: R=%d G=%d B=%d\n", rgb[9], rgb[10], rgb[11])
}
Output:
pixel[0]: R=255 G=255 B=255
pixel[3]: R=255 G=255 B=255

func YUV420ToRGBLimited

func YUV420ToRGBLimited(yuv []byte, width, height int, rgb []byte)

YUV420ToRGBLimited converts YUV420 planar (limited-range BT.709) to interleaved RGB. Input ranges: Y [16, 235], Cb [16, 240], Cr [16, 240]. Super-white/super-black values are handled gracefully via output clamping.

func YUVToRGB_BT709Limited

func YUVToRGB_BT709Limited(y, cb, cr uint8) (r, g, b uint8)

YUVToRGB_BT709Limited converts a limited-range BT.709 YCbCr pixel to RGB. Input ranges: Y [16, 235], Cb [16, 240], Cr [16, 240]. Values outside these ranges (super-white, super-black) are handled gracefully via clamping on the RGB output.

Types

type DecoderFactory

type DecoderFactory func() (VideoDecoder, error)

DecoderFactory creates a new VideoDecoder. Allows tests to inject mock factories without cgo.

type EasingConfig

type EasingConfig struct {
	Type string  `json:"type"`
	X1   float64 `json:"x1,omitempty"`
	Y1   float64 `json:"y1,omitempty"`
	X2   float64 `json:"x2,omitempty"`
	Y2   float64 `json:"y2,omitempty"`
}

EasingConfig is the JSON-serializable easing configuration for API requests.

type EasingCurve

type EasingCurve struct {
	Type   EasingType
	X1, Y1 float64
	X2, Y2 float64
}

EasingCurve defines a timing curve for transition easing. It supports CSS-style cubic-bezier presets, the legacy smoothstep, and custom curves.

func NewCustomEasingCurve

func NewCustomEasingCurve(x1, y1, x2, y2 float64) (*EasingCurve, error)

NewCustomEasingCurve creates a custom cubic-bezier easing curve. x1 and x2 must be in [0, 1]; y1 and y2 may be any value (allows overshoot).

func NewEasingCurve

func NewEasingCurve(preset EasingType) *EasingCurve

NewEasingCurve returns an EasingCurve for the given preset type. Unknown types fall back to linear.

func (*EasingCurve) Ease

func (c *EasingCurve) Ease(t float64) float64

Ease maps a linear time value t in [0,1] to an eased position. Input is clamped to [0,1]. A nil receiver returns t (linear fallback).

type EasingType

type EasingType string

EasingType identifies the easing curve used for transition timing.

const (
	EasingLinear     EasingType = "linear"      // y = t
	EasingEase       EasingType = "ease"        // CSS: cubic-bezier(0.25, 0.1, 0.25, 1.0)
	EasingEaseIn     EasingType = "ease-in"     // CSS: cubic-bezier(0.42, 0, 1.0, 1.0)
	EasingEaseOut    EasingType = "ease-out"    // CSS: cubic-bezier(0, 0, 0.58, 1.0)
	EasingEaseInOut  EasingType = "ease-in-out" // CSS: cubic-bezier(0.42, 0, 0.58, 1.0)
	EasingSmoothstep EasingType = "smoothstep"  // Hermite: t*(3-2t)
	EasingCustom     EasingType = "custom"      // User-defined cubic-bezier
)

type EncoderFactory

type EncoderFactory func(width, height, bitrate, fpsNum, fpsDen int) (VideoEncoder, error)

EncoderFactory creates a new VideoEncoder with the given parameters. Allows tests to inject mock factories without cgo.

type Engine

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

Engine manages the dissolve pipeline lifecycle. Created when a transition starts, destroyed when it completes or aborts.

func NewEngine

func NewEngine(config EngineConfig) *Engine

NewEngine creates a new engine with the given configuration.

func (*Engine) Abort

func (e *Engine) Abort()

Abort cancels the active transition and invokes OnComplete(aborted=true). Safe to call from any goroutine. Idempotent — calling on an idle engine is a no-op.

func (*Engine) AutoAdvancePosition

func (e *Engine) AutoAdvancePosition(frameDurationMs float64) (float64, bool)

AutoAdvancePosition advances the transition position by one tick based on frame duration. Returns the eased position and whether the transition is complete (linear t >= 1.0). On completion, the engine state is set to Idle. This is the reference-clock path — no wall-clock timing, purely tick-driven.

The internal e.position tracks linear progress (0→1). The returned value has the easing curve applied, matching what currentPosition() produces in the wall-clock path.

func (*Engine) BlendRaw

func (e *Engine) BlendRaw(
	fromYUV []byte, fromW, fromH int,
	toYUV []byte, toW, toH int,
	position float64,
) ([]byte, int, int)

BlendRaw performs a synchronous blend of two YUV420 frames at the given position. This is the reference-clock path — no goroutines, no internal timing, no codec pipeline.

Returns the blended YUV420 data, output width, and output height. If frames have different dimensions, toYUV is scaled to match fromYUV. For FTB transitions, toYUV may be nil.

The returned slice is freshly allocated per call; two successive calls yield independent slices. This contract is pinned by TestBlendRawReturnsCopy — a future refactor that pools the result into an engine field would break it. Callers on the per-frame hot path should use BlendRawInto with a reusable dst buffer to avoid the ~3 MB per-frame allocation for the result.

func (*Engine) BlendRawInto

func (e *Engine) BlendRawInto(
	dst []byte,
	fromYUV []byte, fromW, fromH int,
	toYUV []byte, toW, toH int,
	position float64,
) ([]byte, int, int)

BlendRawInto is the scratch-reuse variant of BlendRaw. The blended frame is written into dst (grown if necessary, reslice-preserving any existing capacity) and the grown slice is returned. Pass nil for a freshly allocated result; pass a caller-owned buffer to reuse across frames and avoid the 3–9 MB/frame allocation pattern on resolution-mismatched stinger paths.

Single-caller invariant: BlendRawInto uses engine-scoped scratch buffers for the intermediate scaled frames. The transition state machine serializes ticks per engine, so the scratch is safe without additional locking beyond what BlendRaw already takes for the blender cache.

func (*Engine) Easing

func (e *Engine) Easing() EasingType

Easing returns the current easing type, or "smoothstep" if nil.

func (*Engine) ForceComplete

func (e *Engine) ForceComplete()

WarmupComplete marks the end of warmup. Sets flush flags so that if the first live frame from either source is a keyframe, the decoder is flushed to discard stale warmup references before decoding the fresh IDR. No-op when decoders are nil (raw-only mode). ForceComplete triggers transition completion from the caller (GPU transition path). Used in SkipBlend mode where auto-complete is deferred so the GPU blend frame is produced BEFORE the transition state changes.

func (*Engine) FromSource

func (e *Engine) FromSource() string

FromSource returns the outgoing source key.

func (*Engine) IngestFrame

func (e *Engine) IngestFrame(sourceKey string, wireData []byte, pts int64, isKeyframe bool)

IngestFrame processes a video frame from one of the two transition sources. Decodes frame, stores as latest YUV420. If sourceKey matches the incoming source (toSource), triggers blend+encode+output with the source's PTS to maintain timestamp continuity on the program stream. For FTB, the fromSource triggers blend (no toSource).

Lock scope is minimized: decode happens outside the lock so that two sources sending frames near-simultaneously don't block each other during the 3-16ms decode step.

func (*Engine) IngestRawFrame

func (e *Engine) IngestRawFrame(sourceKey string, yuv []byte, width, height int, pts int64)

IngestRawFrame accepts a pre-decoded YUV420 frame (e.g., from MXL sources). Skips H.264 decode — stores YUV directly and triggers blend. The frame is scaled to the engine's resolution if dimensions don't match.

func (*Engine) Position

func (e *Engine) Position() float64

Position returns the current transition position (0.0 to 1.0).

func (*Engine) SetColorFormat

func (e *Engine) SetColorFormat(f video.Format)

SetColorFormat sets the pipeline color format for blend operations. Must be called before Start(). When set to video.YUV422_10bit, the engine uses 10-bit blend functions and 10-bit frame sizing.

func (*Engine) SetPosition

func (e *Engine) SetPosition(pos float64)

SetPosition sets the T-bar manual position (0.0-1.0). Switches to manual control mode. pos>=1.0 triggers completion. pos<=0.0 triggers abort (only if previously moved past 0).

func (*Engine) SetTimeout

func (e *Engine) SetTimeout(d time.Duration)

SetTimeout configures the watchdog timeout. If no frames arrive from either source for this duration during an active transition, the transition is aborted. Must be called before Start().

func (*Engine) Start

func (e *Engine) Start(from, to string, ttype Type, durationMs int) error

Start initializes the transition pipeline. Creates decoders and blender. Returns error if already active.

func (*Engine) StartSync

func (e *Engine) StartSync(from, to string, ttype Type, durationMs int)

StartSync configures the engine for synchronous blending (no goroutines). Sets transition type, duration, and direction but does NOT spawn any goroutines — no watchdog, no decoders, no codec pipeline. This is the StartSync initializes a transition for the reference-clock path. Unlike Start(), it does NOT use wall-clock timing — the caller drives position via AutoAdvancePosition. No watchdog goroutine is started because the sync loop handles completion directly.

func (*Engine) State

func (e *Engine) State() State

State returns the current engine state.

func (*Engine) StingerFrameAt

func (e *Engine) StingerFrameAt(pos float64) (yuv, alpha []byte, width, height int, cutPoint float64)

StingerFrameAt returns the stinger overlay YUV, alpha, and cut point for the given transition position. Returns nil slices if no stinger is configured. Used by the GPU transition path to upload stinger data to GPU.

func (*Engine) Stop

func (e *Engine) Stop()

Stop tears down decoders and resets state.

func (*Engine) Timeout

func (e *Engine) Timeout() time.Duration

Timeout returns the current watchdog timeout.

func (*Engine) Timing

func (e *Engine) Timing() map[string]any

Timing returns a snapshot of the engine's timing instrumentation. Safe to call from any goroutine (all fields are atomic).

func (*Engine) ToSource

func (e *Engine) ToSource() string

ToSource returns the incoming source key.

func (*Engine) TransitionType

func (e *Engine) TransitionType() Type

TransitionType returns the current transition type.

func (*Engine) WarmupComplete

func (e *Engine) WarmupComplete()

func (*Engine) WarmupDecode

func (e *Engine) WarmupDecode(sourceKey string, wireData []byte)

WarmupDecode feeds a frame to the decoder for the given source side, populating latestYUVA/latestYUVB so the first live IngestFrame can produce blended output immediately. Produces no output callbacks. No-op if the engine is not active.

func (*Engine) WipeDirection

func (e *Engine) WipeDirection() WipeDirection

WipeDirection returns the wipe direction for the current transition.

func (*Engine) WipeGradient

func (e *Engine) WipeGradient() []byte

WipeGradient returns the resolved gradient map (single-channel, w*h bytes), or nil if no gradient-map wipe is active.

func (*Engine) WipeMapConfig

func (e *Engine) WipeMapConfig() *wipemap.WipeConfig

WipeMapConfig returns the current wipe map config, or nil if using legacy directional wipe.

type EngineConfig

type EngineConfig struct {
	DecoderFactory DecoderFactory
	Output         func(yuv []byte, width, height int, pts int64, isKeyframe bool)
	OnComplete     func(aborted bool)

	// WipeDirection specifies the wipe direction when Type is "wipe".
	// Ignored for other transition types. Used only for legacy directional wipes;
	// gradient-map wipes use WipeConfig instead.
	WipeDirection WipeDirection

	// WipeConfig specifies gradient-map-based wipe parameters. When set and
	// Type is "wipe", the engine resolves the gradient map from WipeMapStore
	// and uses BlendWipeMap instead of BlendWipe. Takes precedence over WipeDirection.
	WipeConfig *wipemap.WipeConfig

	// WipeMapStore resolves gradient maps by pattern ID. Required when
	// WipeConfig is set; ignored otherwise.
	WipeMapStore *wipemap.WipeMapStore

	// Stinger holds the pre-decoded stinger overlay data. Required when
	// Type is "stinger", ignored for other types.
	Stinger *StingerData

	// Easing sets the easing curve for the transition. If nil, the engine
	// falls back to legacy smoothstep for backward compatibility.
	Easing *EasingCurve

	// HintWidth/HintHeight pre-initialize the blender at Start() time.
	// When set, the engine can produce output (via black frame fallback)
	// even before any decode succeeds. Set from the pipeline's known
	// resolution to eliminate output gaps during B-frame reorder warmup.
	HintWidth  int
	HintHeight int

	// SkipBlend skips the CPU pixel blend in blendAndOutput but still
	// tracks position and handles auto-complete timing. Used when GPU
	// transitions are active — the GPU pipeline performs the blend, and
	// the CPU engine only manages transition state.
	SkipBlend bool
}

EngineConfig configures the Engine.

type FrameBlender

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

FrameBlender alpha-blends two YUV420 planar byte slices directly, avoiding the cost and chroma resampling error of a YUV->RGB->YUV round-trip. This matches how hardware broadcast mixers (ATEM, Ross, Datavideo) and FFmpeg's xfade filter operate: blending in the native Y'CbCr domain.

All blend loops use fixed-point integer arithmetic (0-256 weight with >>8 shift) instead of float64, eliminating per-pixel float conversions. The result of blending two [0,255] values with a [0,256] weight always fits in [0,65280], so no clamping is needed after the shift.

Five blend modes are supported:

  • Mix: linear interpolation between source A and source B
  • Dip: two-phase blend through black (A fades out, then B fades in)
  • Wipe: precomputed alpha map with 4px soft edge (6 directions)
  • FTB: fade to black (single source fades to black)
  • Stinger: per-pixel alpha composite from stinger overlay

The blender pre-allocates its output buffer at construction time and reuses it across frames. All blend methods return the internal yuvBufOut slice -- callers must consume it before the next call.

YUV420 layout: Y[w*h] + Cb[w/2*h/2] + Cr[w/2*h/2] Black in full-range YUV: Y=0, Cb=128, Cr=128

func NewFrameBlender

func NewFrameBlender(width, height int) (*FrameBlender, error)

NewFrameBlender creates a FrameBlender with a pre-allocated output buffer sized for the given resolution in YUV420 format. Returns an error if width or height is odd (YUV420 requires even dimensions).

func (*FrameBlender) BlendDip

func (fb *FrameBlender) BlendDip(yuvA, yuvB []byte, position float64) []byte

BlendDip performs a two-phase dip-to-black transition in YUV420 space. Phase 1 (position 0.0-0.5): source A fades to black. Phase 2 (position 0.5-1.0): source B fades up from black. At position 0.5, the output is fully black. Black level depends on SetLimitedRange: Y=0 (full-range) or Y=16 (limited-range). Uses fixed-point integer math: weight 0-256 with >>8 division.

func (*FrameBlender) BlendFTB

func (fb *FrameBlender) BlendFTB(yuvA []byte, position float64) []byte

BlendFTB fades a single source to black in YUV420 space. position 0.0 = full source, position 1.0 = fully black. Black level depends on SetLimitedRange: Y=0 (full-range) or Y=16 (limited-range). Chroma neutral is 128 in both ranges. Uses fixed-point integer math: weight 0-256 with >>8 division.

func (*FrameBlender) BlendMix

func (fb *FrameBlender) BlendMix(yuvA, yuvB []byte, position float64) []byte

BlendMix performs linear interpolation between yuvA and yuvB in YUV420 space. position 0.0 = all A, position 1.0 = all B. Uses fixed-point integer math: weight 0-256 with >>8 division.

func (*FrameBlender) BlendNAM

func (fb *FrameBlender) BlendNAM(yuvA, yuvB []byte, position float64) []byte

BlendNAM performs a Non-Additive Mix between yuvA and yuvB in YUV420 space. For each pixel, the output is the brighter (higher gained Y) of the two sources. position 0.0 = all A, position 1.0 = all B. At any position, gain_a = 1-position and gain_b = position. Per luma pixel: if A*gain_a >= B*gain_b, output A*gain_a (and use A's chroma); otherwise output B*gain_b (and use B's chroma). Chroma is resolved per 2x2 block by majority vote of the luma winners. Uses fixed-point integer math for the luma comparison and scaling.

func (*FrameBlender) BlendStinger

func (fb *FrameBlender) BlendStinger(baseYUV []byte, stingerYUV []byte, alpha []byte) []byte

BlendStinger composites a stinger frame (with alpha) over a base YUV420 source. The stinger frame's YUV data is blended with the base using per-pixel alpha. alpha is a per-luma-pixel alpha map [0-255], same dimensions as the Y plane. stingerYUV is YUV420 planar format matching base dimensions. Uses integer math with >>8 shift (GPU-standard approximation for /255).

func (*FrameBlender) BlendWipe

func (fb *FrameBlender) BlendWipe(yuvA, yuvB []byte, position float64, direction WipeDirection) []byte

BlendWipe performs a directional wipe transition between yuvA and yuvB in YUV420 space. A precomputed alpha map is generated once per call at the Y-plane resolution, then the fast integer blend loop applies it. For linear wipes, alpha is constant along the perpendicular axis, so only one value per row/column is computed and replicated.

Wipe directions:

  • h-left: wipes from left to right (B reveals from the left)
  • h-right: wipes from right to left (B reveals from the right)
  • v-top: wipes from top to bottom (B reveals from the top)
  • v-bottom: wipes from bottom to top (B reveals from the bottom)
  • box-center-out: B reveals from center expanding outward
  • box-edges-in: B reveals from edges contracting inward

func (*FrameBlender) BlendWipeMap

func (fb *FrameBlender) BlendWipeMap(yuvA, yuvB []byte, position float64, gradientMap []byte, config wipemap.WipeConfig) []byte

BlendWipeMap performs a gradient-map-based wipe transition between yuvA and yuvB. The gradientMap is a single-channel byte buffer (w*h) where 0 = revealed first and 255 = revealed last. The config controls soft edge, border, reverse, and multiply.

Approach:

  1. Convert gradient map → per-pixel alpha map (applying reverse, multiply, soft edge)
  2. Handle border pixels with a second pass
  3. Use existing SIMD blendAlpha for Y plane
  4. Downsample alpha to chroma, blendAlpha for Cb/Cr planes
  5. Overwrite border pixels with border color

func (*FrameBlender) SetLimitedRange

func (fb *FrameBlender) SetLimitedRange(limited bool)

SetLimitedRange configures the blender for limited-range (broadcast) or full-range YUV. Limited-range uses Y=16 for black; full-range uses Y=0. The default is limited-range (Y=16) to match BT.709 broadcast standard.

type ScaleQuality

type ScaleQuality int

ScaleQuality selects the scaling algorithm.

const (
	// ScaleQualityFast uses bilinear interpolation. Suitable for real-time
	// preview or when CPU budget is tight. This is the zero value so that
	// uninitialized ScaleQuality defaults to the cheap path.
	ScaleQualityFast ScaleQuality = iota

	// ScaleQualityHigh uses Lanczos-3 interpolation for broadcast-quality
	// scaling. Produces sharper output than bilinear, especially on downscales,
	// at the cost of ~3-4x more computation.
	ScaleQualityHigh
)

type State

type State int

State tracks whether a transition is currently running.

const (
	StateIdle   State = 0
	StateActive State = 1
)

type StingerData

type StingerData struct {
	// Frames holds YUV420 + alpha data for each stinger frame.
	Frames []StingerFrameData
	// Width and Height of the stinger frames.
	Width, Height int
	// CutPoint is the position [0.0-1.0] where the underlying source switches from A to B.
	CutPoint float64
	// Audio is optional stinger audio (interleaved float32 PCM).
	Audio           []float32
	AudioSampleRate int
	AudioChannels   int
}

StingerData holds pre-decoded stinger overlay frames for use during a stinger transition. Populated by the switcher from a stinger.Clip.

type StingerFrameData

type StingerFrameData struct {
	YUV   []byte // YUV420 planar
	Alpha []byte // per-luma-pixel alpha [0-255]
}

StingerFrameData is a single stinger overlay frame.

type Type

type Type string

Type identifies the visual transition effect.

const (
	Mix        Type = "mix"
	Dip        Type = "dip"
	FTB        Type = "ftb"
	FTBReverse Type = "ftb_reverse"
	Wipe       Type = "wipe"
	Stinger    Type = "stinger"

	// DVE transition types
	TypeDVEPushLeft  Type = "dve-push-left"
	TypeDVEPushRight Type = "dve-push-right"
	TypeDVEPushUp    Type = "dve-push-up"
	TypeDVEPushDown  Type = "dve-push-down"
	TypeDVESqueeze   Type = "dve-squeeze"
	TypeDVESpin      Type = "dve-spin"
	TypeDVEZoom      Type = "dve-zoom"
	TypeDVEFly       Type = "dve-fly"
	TypeDVECube      Type = "dve-cube"
	TypeDVEFlip      Type = "dve-flip"
	TypeDVEDoor      Type = "dve-door"
	// Extended 2D
	TypeDVESwingLeft   Type = "dve-swing-left"
	TypeDVESwingRight  Type = "dve-swing-right"
	TypeDVERevealLeft  Type = "dve-reveal-left"
	TypeDVERevealRight Type = "dve-reveal-right"
	TypeDVERevealUp    Type = "dve-reveal-up"
	TypeDVERevealDown  Type = "dve-reveal-down"
	TypeDVEScaleRotate Type = "dve-scale-rotate"
	TypeDVECrossZoom   Type = "dve-cross-zoom"
	TypeDVEShrinkGrow  Type = "dve-shrink-grow"
	TypeDVEBarnH       Type = "dve-barn-h"
	TypeDVEBarnV       Type = "dve-barn-v"
	TypeDVEMosaic      Type = "dve-mosaic"
	TypeDVECornerPeel  Type = "dve-corner-peel"
	// Extended 3D
	TypeDVECarousel  Type = "dve-carousel"
	TypeDVETumble    Type = "dve-tumble"
	TypeDVESwingDoor Type = "dve-swing-door"
	TypeDVEFold      Type = "dve-fold"
	TypeDVECubeRoll  Type = "dve-cube-roll"
	TypeDVEGlobe     Type = "dve-globe"
	// B2 extended transitions
	TypeDVEPageTurn     Type = "dve-page-turn"
	TypeDVEBounce       Type = "dve-bounce"
	TypeDVEBlurDissolve Type = "dve-blur-dissolve"
	// NAM is a blend mode, not a DVE transition
	TypeNAM Type = "nam"
)

type VideoDecoder

type VideoDecoder interface {
	// Decode decodes encoded video data and returns YUV420 planar bytes,
	// width, height, and any error. The returned YUV buffer length is
	// video.YUV420FrameSize(width, height) bytes for YUV420 8-bit.
	Decode(data []byte) (yuv []byte, width, height int, err error)

	// Close releases decoder resources.
	Close()
}

VideoDecoder decodes AVC1/Annex B wire data into YUV420 planar buffers. Implementations: codec.FFmpegDecoder (cgo), codec.OpenH264Decoder (cgo+openh264), mockDecoder (tests).

func NewMockDecoder

func NewMockDecoder(width, height int) VideoDecoder

NewMockDecoder creates a mock decoder for cross-package testing.

type VideoEncoder

type VideoEncoder interface {
	// Encode encodes a YUV420 planar frame. pts is the presentation
	// timestamp in 90 kHz MPEG-TS units, passed through to the encoded
	// bitstream for A/V sync. If forceIDR is true, the encoder produces
	// a keyframe. Returns encoded data, whether the frame is a keyframe,
	// and any error.
	Encode(yuv []byte, pts int64, forceIDR bool) (data []byte, isKeyframe bool, err error)

	// Close releases encoder resources.
	Close()
}

VideoEncoder encodes YUV420 planar frames into AVC1/Annex B wire data. Implementations: codec.FFmpegEncoder (cgo), codec.OpenH264Encoder (cgo+openh264), mockEncoder (tests).

func NewMockEncoder

func NewMockEncoder() VideoEncoder

NewMockEncoder creates a mock encoder for cross-package testing.

type WipeDirection

type WipeDirection string

WipeDirection specifies the direction for a wipe transition.

const (
	WipeHLeft        WipeDirection = "h-left"
	WipeHRight       WipeDirection = "h-right"
	WipeVTop         WipeDirection = "v-top"
	WipeVBottom      WipeDirection = "v-bottom"
	WipeBoxCenterOut WipeDirection = "box-center-out"
	WipeBoxEdgesIn   WipeDirection = "box-edges-in"
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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