1 output: av::Packet
url(string of URL)options(dictionary) - options for libavformattimeout(float, seconds) - packet read timeoutinitial_timeout(float, seconds) - URL open timeout
1 output: av::Packet
This node is a special case on the input node with some changes required to plae & seek recoding files.
It has the same parameters as the input node and some additional
url(string of URL)options(dictionary) - options for libavformattimeout(float, seconds) - packet read timeoutinitial_timeout(float, seconds) - URL open timeoutlive_delay(float, seconds) - time to delay between latest available packet and currently displayed in live mode (default1)start_ts(string) - start timestampstop_ts(string) - stop timestamploop(bool) - if set to true, video will be played in a looploop_continuous_ts(bool, default false) - withloop, shift each forward pass by the span of the previous one (earliest timestamp to latest timestamp + duration of the video stream, or of all streams in a file without video), so timestamps keep rising across loops instead of jumping back to the start. All streams get the same shift to stay in sync. Seek targets andstop_tsstill refer to file timestamps, while output packets carry the shifted ones; intended for free-running loops.stop_delay(int, miliseconds) - time to hold on last read frame before reporting end od processingseek_table(string of URL) - file with fast-seek offsets (may be generated by theoutputnode)ts_offsets(string of URL) - file with timestamp offsets (may be generated by thesentinelnode)timestamp_source(string) - timestamp source used to synchronize multiple videos. Possible values arewallclockandinput. Both values are calculated from the timestamps offsets file (ts_offsets)preseek(float, seconds, default 0) - how many seconds to preseek back to increase chance of finding a keyframe, when seek to timestamp (seekcommand) is requestedteam(string, name of instance-shared object) - if specified, seeks on theinputnodes may be synchronized inside specified teamframe_metadata(list of strings, default: all of them) - which keys the node stamps on the frames its decoders hand back:video_ts,video_pts,input_ts,output_ts,wallclock_ts,wallclock,frame_no,frame_tson video frames (frame_no, andframe_tsfrom the table, needseek_table),frame_tsandsample_rateon audio frames.realtimereadsframe_no,frame_tsandwallclockfor its team and playback info andspeed_videoreadsframe_ts; a graph that reads none of them may pass[]and spare every frame the stamping and every hop the dictionary copyrouting(dictionary of string => string) andstreams_filter(string) - as indemux: each routed stream goes straight to its own queue, given instead ofdst. Packets of streams without a route are dropped and the EOF marker goes to every routed queue. Spares thedemuxhop for a file whose streams the node reads anyway;wait_for_keyframestays withdemux
Runtime control: node.object.set <name> request-eof true interrupts further
input reads while keeping the producer alive to drain buffered demux/parser
packets. It then sends one common packet EOF and finishes (respecting
stop_delay), even if looping or ordinary EOF notification was disabled.
This is irreversible and intended for a running forward input whose live
publisher has finished but whose relay keeps the connection open. It preserves
normal downstream backpressure; the caller must keep consumers running and
enforce a shutdown deadline. node.stop remains an immediate abort.
Rate limit output packets/frames to wallclock. This way, DTS (in packets) or PTS (in frames) differences will equal wallclock differences at this node's sink.
Also, allows inter-stream synchronization (as long as timestamps in them are synchronized) and automatic flushing if too much data is buffered in queues before this node.
This node is non-blocking.
1 input, 1 output: anything
leak_after(float, seconds) - if specified, bypass rate limiting after having input packets available immediately (in other words, at least one packet was always enqueued) for specified time. Intended for segmented inputs for which realtime node is useful to prevent bursts, while we don't want clock drift problems and missed segments in long running streams.speed(float) - default 1, implemented by scaling wallclock's timebase (millisecond precision) so values between ~0.9995 and ~1.0006 are treated as 1.tick_period(string of rational, seconds) - if specified andtick_sourceis also specified, anti-jitter filter will be enabled, assuming that tick source emits a tick everytick_period. Generally should be set to 1/FPS, e.g.1/60. The filter maintains its own clock independent of wallclock, but will resync to the wallclock if it drifts too much. Withouttick_source, pacing still follows wallclock, but a specifiedtick_periodselects an internal time base of one quarter of that period. To choose only the time base, usetime_base.time_base(string of rational, seconds, optional) - the unit the node counts in andset_ptsstamps in. Default: the millisecond wallclock time base.1/120000represents 1/30, 1001/30000, 1001/60000 and 1001/24000 exactly. Nothing ticks at this rate: the node still wakes for a frame and for its release time. The clock it reads counts milliseconds, so a frame whose release time falls inside the current millisecond is released then, up to a millisecond early, and stamped with its exact time. Not together withtick_period. With a finer time base, setnegative_time_toleranceanddiscontinuity_thresholdexplicitly, in seconds, to keep their durations; the built-in values assume milliseconds.set_pts(bool, default false) - set PTS to wallclock timestamps corresponding to time when packets are outputted (or, more precisely, when they would be outputted if there was no jitter)forward_eof(bool, default false) - pass the end-of-stream marker on to the output, in order behind the last frame and never stamped byset_pts. By default the marker is consumed here; a consumer that must tell the end of a file from a stalled producer (clip_cache) needs it. Do not enable it in front of a node that finishes on the marker unless that is intended.
Input tolerance parameters:
negative_time_tolerance(float, seconds) - default0.25. Do not resync if newly arrived packet should have been emitted at most that much time in the past. 0 to disable and always resync in such situation - effectively increasing latency until sufficient buffering for smooth output is achieved.negative_time_discard(float, seconds) - if specified, if newly arrived packet should have been emitted at least that much time in the past, discard this packet. Discarding has lower priority than resyncing (negative_time_tolerance), so the value must be less thannegative_time_toleranceto make sense, equal or higher values disable discarding.discontinuity_threshold(float, seconds) - default1. If we need to wait for more than specified time, treat as discontinuity and resync. Default value may be unsuitable (too small) for multiple source synchronization in case more data is buffered.jitter_margin(float, seconds) - default0. When (re)syncing, add this value to the time to be waited. This prevents frequent resyncing and visible jitter at the cost of higher latency. It makes sense only with unbuffered output (e.g. display)initial_jitter_margin(float, seconds) - default =jitter_margin.jitter_marginto use for the first frame received after node start, after discontinuity or afterleak_after-triggered bypass, but not after "negative time to wait (...), resyncing"
Inter-stream synchronization parameters:
team(string, name of instance-shared object) - if specified, realtime nodes with the same team will cooperate to have their output synchronized. Use only if timestamps are synchronized.master(bool) - defaulttrue. Only masters are allowed to resync in case of discontinuity. A team can have multiple masters.
Automatic flushing parameters (experimental):
input_ts_queue(string, name of queue) - which queue should be treated as the beginning of buffering chain. Usually should be set to the output of the demuxer. If unspecified, automatic flushing is disabled.intermediate_queues(list of strings) - list of intermediate queues that will be examined whether they contain packetsmax_buffered(float, seconds, default 5.5) - start flushing when the buffering chain has more than this amount buffered (calculated as difference between timestamp of last packet inserted into theinput_ts_queueand timestamp of the frame coming to this node)min_buffered(float, seconds, default 0.5) - stop flushing when the buffering chain has less than this amount buffered, orinput_ts_queueandintermediate_queuesare all empty
For each passing packet, time to wait is computed (how long should we sleep before outputting that packet, to maintain realtime output rate) and different actions are performed based on its value
- if timeToWait <= 0:
- if timeToWait < -negative_time_tolerance:
- resync and emit packet
- else if timeToWait < -negative_time_discard:
- discard this packet
- else: /* -negative_time_discard <= timeToWait <= 0
*/
- emit packet immediately
- if timeToWait < -negative_time_tolerance:
- else: /* timeToWait > 0 */
- if timeToWait < discontinuity_threshold:
- normal behavior - wait timeToWait and emit packet
- else: /* timeToWait >= discontinuity_threshold */
- resync and emit packet
- if timeToWait < discontinuity_threshold:
Note: This pseudocode omits leak_after logic.
Recommended options for encoding from segmented input (HLS, DASH):
- speed: 1.01
- leak_after: segment length * 2
Recommended options for displaying live video:
- negative_time_tolerance: 0.001 - 0.02 depending on clock precision of your system
- jitter_margin: 0.1 or some more
1 input, many outputs: av::Packet
streams_filter(string): if present, filter input streams according to ffmpeg syntax (for example "p:1" to select Program 1 - useful for HLSes) before parsing routing keysrouting(dictionary of string => string): stream mapping, keys are streams in input file, values are queues, like{ "v:0": "videoin", "a:0": "audioin" }, used instead ofdst- keys may be prefixed with
?to indicate optional input (ignore the route if stream not found)
- keys may be prefixed with
wait_for_keyframe(bool): default false, discard packets until a keyframe appears in any video stream
input_rec accepts the same routing and streams_filter and then needs no demux.
1 input: av::Packet, 1 output: av::VideoFrame or av::AudioSamples respectively
codec(string) - optional, codec name, auto-detected by libavcodec if not specifiedcodec_map(dictionary of string => string) - optional, use specified decoder for matching stream's codec, for example to use cuvid for h264 and hevc streams:"codec_map": {"h264": "h264_cuvid", "hevc": "hevc_cuvid"}pixel_format(string) - optional, if unspecified, libavcodec and/or codec will select best possible pixel format for given input stream. As seen inpix_fmtfield of FFmpeg'sAVPacket, so it doesn't have to be any real pixel format (e.g.yuv420p) but can also be hardware acceleration specification (e.g.cuda)- if starts with
?, prefer specified pixel format, e.g.?cuda, but allow use of any - if doesn't start with
?, force specified pixel format, fail if it's incompatible with codec or stream
- if starts with
hwaccel(string, name of instance-shared object) - optional, name of hwaccel previously created withhwaccel.inithwaccel_only_for_codecs(list of strings) - use hwaccel only for specified input stream codecs, useful because apparently settinghw_device_ctxin normally-software libavcodecs triggers frame corruption bugsoptions(dictionary) - optional, options passed to libavcodec
Set PTS to timecode from video frame's side data.
1 input, 1 output: av::VideoFrame
-
team(string, name of instance-shared object) - if specified, multipleextract_timestampsandextract_timestamps_slavenodes within the same team will share the same offset, so streams not containing timecode side data (e.g. audio) will be synchronized to the same timecode, too (as long as their PTSes are synchronized to each other). -
passthrough_before_available(bool) - defaultfalse, meaning that processing will be blocked (and input queue will grow) as long as timecode isn't available. If true, frames will be passed through with PTS unchanged in such case. -
drop_before_available(bool) - discard incoming packets before timecode is available. Disabled by default. Has lower priority thanpassthrough_before_available. -
timecodes(list of strings), default["S12M"]- side data to get timecodes from. If specified timecode doesn't exist, next one in the list is tried. Possible items:S12M.1orS12M- SMPTE 12M = SEIS12M.2S12M.3GOP
-
liveu(bool, default false) - workaround for LiveU-encoded SMPTE 12M. Treat drop bit as a part of frames field. -
frame_rate_source(string) - timecodes have frame numbers, so to calculate PTS from them, number of frames per second must be known. Possible values:fps- default. Use FPS from nearest node implementing IFrameRateSource (decoder, filter orforce_fps)timebase- use 1/timebase from nearest node implementing ITimeBaseSource (decoder, filter orforce_fps)
both values may yield the same or different results depending on input stream. Some video streams are generated by skipping every second frame from higher FPS stream, with S12M side-data preserved in remaining frames. In such cases
fpsis wrong andtimebaseis correct.
Set PTS to timecode extracted by extract_timestamps node.
1 input, 1 output: av::VideoFrame or av::AudioSamples
Supports parameters working the same as in extract_timestamps node:
team- mandatorypassthrough_before_availabledrop_before_available
1 input, 1 output: av::VideoFrame or av::AudioSamples, respectively
graph(string) - FFmpeg filter graphhwaccel(string, name of instance-shared object) - optional (mandatory for some filters), name of hwaccel previously created withhwaccel.initthreads(int >= 0, default 0) - slice threads of the FFmpeg filter graph (nb_threads); 0 starts one per CPU for every graph. Set 1 for graphs of GPU filters,setparams,settband the like, which do no CPU slice work.
Change timestamps so that playback speed changes in real-time.
1 input, 1 output: av::VideoFrame or av::AudioSamples, respectively
team(string, name of instance-shared object, default"default") - team name that will be used for changing the speed usingspeed.setcommanddiscard_when_speed_changed(bool, default false) - discard frames when speed isn't equal to 1. Intended for audio streams.timebase(string of rational) - optional, if specified, will scale incoming timestamps to this timebase. Otherwise original timebase will be preserved. To get as smooth output as possible, set it to your native output timebase (1/fps).speed(float) - initial speed, default: 1sync_team(string, name of instance-shared object) -realtimeteam name. Required by smooth change playback direction (eg. from forward to backward) to flush all the queues betweeen input and therealtimenodes.
Stop passing through frames/packets and resume on request (pause, resume commands).
1 input, 1 output: anything
team(string, name of instance-shared object, default"default") - team name that will be used for controlpaused(bool) - sets the team initially in paused state
Duplicate and drop frames to achieve requested FPS
1 input, 1 output: av::VideoFrame
fps(string of rational) - target FPS as a string, e.g.25or30000/1001center_phase(bool, defaultfalse) - shift input timestamps by a constant, chosen on the first frame and again after a discontinuity, so that frame lands exactly on an output tick. Timestamp jitter below half a frame (e.g. fromrealtimewithset_pts) then never duplicates or drops frames, whatever the source's phase. Output timestamps move by less than half a frame.
Overwrite timestamps with a smoothed monotonic timeline (previous + duration), while keeping A/V sync by resyncing to input timestamps when averaged drift grows too large.
This node is non-blocking.
1 input, 1 output: av::VideoFrame or av::AudioSamples
For video:
duration(string of rational, seconds) - frame duration, typically1/FPS(e.g.1/25,1001/30000), orfps(string of rational) - frames per second (alternative toduration)
For both video and audio:
resync_threshold(float seconds, default0.02) - resync output timeline to input PTS when averaged drift exceeds thisdrift_window(int, default300) - drift averaging window size (samples); larger = smoother, smaller = more reactivemin_samples_before_resync(int, default150) - ignore drift until this many frames/samples have been observeddiscontinuity_threshold(float seconds, default2.0) - hard reset (resync) when input PTS jump exceeds this
Set initial metadata to allow nodes that rely on them to start when real metadata aren't available yet.
1 input, 1 output: av::VideoFrame or av::AudioSamples
Shared parameter (also supported by fake_video_format / fake_audio_metadata):
ignore_eof(bool, defaultfalse) - discard upstream EOF markers and keep processing subsequent frames. Enable only at a live reconnect boundary before a sentinel. The default forwards EOF and finishes; explicit shutdown and errors are unaffected.
Parameters for video:
width(int) - default 1920height(int) - default 1080pixel_format(string) - defaultyuv420preal_pixel_format(string) - specify only ifpixel_formatis hardware-accelerated (e.g.cuda)
Parameters for audio:
sample_rate(int) - default 48000sample_format(string) - defaults32pchannel_layout(string) - defaultstereo
A sentinel
- guards streams against wild timestamps - PTSes jumping forward or backward, or repeating timestamps
- inserts backup frames when input signal is not available for specified time:
- in audio stream: silence
- in video stream, for maximum time of
freezeparameter: repeated last frame - in video stream: custom slate, can be used for adding "we'll be back shortly" card
Sentinel's output has "ideal" timestamps with tolerance specified in sentinel's parameters. In other words, it ensures output stream continuity.
1 input, 1 output: av::VideoFrame/av::AudioSamples
-
timeout(float, seconds) - default 1, seconds to wait for input frame before inserting frozen or backup frame -
correction_group(string, name of instance-shared object) - optional, defaults to"default", used for sharing the clock between streams -
forward_start_shift(bool):true: if input streams are present when starting the sentinels, forward relative shifts of their first packets (i.e. A-V offset) to output.false(default): start all output streams at exact PTS =start_ts
-
max_streams_diff(float, seconds) - default0.001, tolerance in seconds -
start_ts(float, seconds) - default10, first output timestamp -
lock_timeshift(bool) - after receiving first PTS, maintain constant input-output PTS difference. Disabled by default. Enable only if you're sure that input timestamps are synchronized to a real-time clock. -
eof_passthrough(bool) - defaultfalse. Whentrue, an upstream EOF marker is forwarded downstream and this node finishes instead of treating EOF as a stall and generating backup frames. Use for VOD / faster-than-realtime graphs that need encoder flush and output trailers. Live graphs should leave this off. -
reporting_url(optional, string of URL) - if specified, correction time shift changes will be reported to this URL as HTTP POST with JSON body:{"changed_at":128.1,"input_pts_offset":126.75999999999999,"output_pts_offset":10.0}changed_at- output timestamp of the change relative to first output PTS (output_pts_offset)input_pts_offset- what sentinel needs to add to input timestamp to achieve output PTS, minusoutput_pts_offsetoutput_pts_offset= first output PTS, constant through processing, changeable usingstart_tsparameter
For video only:
freeze(float) - default 5, seconds to duplicate last good frame before outputting backup framebackup_frame(string of URL) - backup frame (slate) imagebackup_picture_buffer(string, name of instance-shared object) - read backup frame (slate) from this buffer. Usepicture_buffer_sinkto write frame to the buffer. Sentinel will reload the slate from the picture buffer every 64 frames and on every input signal break triggering slate insertion.initial_picture_buffer(string, name of instance-shared object) - initialize last frame buffer with this buffer, so that at the beginning of stream it will be used for at mostfreezeduration. Useful to insert black frame instead of slate at the beginning whenforward_start_shiftis set to false. If unspecified, regularbackup_frameorbackup_picture_bufferwill be used.
For sentinel_video, either backup_frame or backup_picture_buffer must be provided.
Dynamic video scaler, maintains constant output dimensions and pixel
format even if input stream changes parameters. Does not resample
FPS (see force_fps node)
1 input, 1 output: av::VideoFrame
dst_width(int)dst_height(int)dst_pixel_format(string)flags(list of strings) - list of possible flags: https://www.ffmpeg.org/doxygen/3.2/swscale_8h_source.html#l00057
Dynamic audio resampler, maintains continuity of output stream even if input stream changes parameters.
1 input, 1 output: av::AudioSamples
dst_sample_rate(int)dst_channel_layout(string)dst_sample_format(string)compensation(float)0(default) means brutal compensation using built-in avplumber's sample dropping algorithm- any negative value means brutal compensation using libswresample, may not work correctly
- positive value between 0 and 1 means soft compensation using libswresample, value means fraction of samples to compensate, may not work correctly
1 input, multi outputs: anything
drop(bool) - drop packets if output queue is full, disabled by default
1 input, multi outputs (at most 32): anything. Each frame is handed to the outputs selected by a mask that can change while the node runs.
outputs(int or bit string, default1) - the outputs that receive frames, bit n for the n-th name indst;node.object.set <node> outputs <mask>changes itdrop(bool, defaultfalse) - drop a frame for an output whose queue is full instead of waiting for roomframe_color(object, optional) - colour fields written on every video frame before it is handed out:color_trc,color_primaries,colorspace,color_rangesubscribed_outputs(object, optional) - output name => frame subscription name: that output receives frames while its subscription is enabled, whatever the mask says, and never waits
one_to_many runs on a thread of its own, woken by every frame. one_to_many_nb runs on an event
loop (event_loop, as realtime does) when its input signals a frame, so a graph with many of them
has no thread per fan-out; it cannot wait for room in an output, so it requires drop: true.
Join metadata from an auxiliary stream into a primary stream by exact timestamp match.
2 inputs, 1 output: av::VideoFrame or av::AudioSamples
srcmust contain exactly 2 queues in this order:[primary, auxiliary]- timestamps are compared exactly (
primary.pts == auxiliary.pts) - empty queue is treated as "not ready yet" (the node waits), not as missing frame
- when both heads are present:
- if timestamps match: copy auxiliary metadata to primary (
av_dict_copy) and emit primary - if
primary.pts < auxiliary.pts: emit primary unchanged (auxiliary missing for that primary timestamp) - if
auxiliary.pts < primary.pts: drop auxiliary frame (primary missing for that auxiliary timestamp)
- if timestamps match: copy auxiliary metadata to primary (
- output timestamp/timebase remains the same as primary input
Useful for running heavy processing (e.g. neural network inference) on downscaled frames and merge produced metadata back onto original-resolution frames.
Metadata-driven CUDA crop. Reads a bounding-box from per-frame metadata (e.g. produced by a detection node) and crops the frame to that region using an FFmpeg CUDA filter graph. Intended for reframing workflows such as face-detection crop.
Requires HAVE_CUDA=1.
1 input, 1 output: av::VideoFrame (CUDA)
metadata_key(string, default"reframer_bbox") - frame metadata key containing the crop coordinates. Expected value format:{"x": 100, "y": 50, "w": 640, "h": 360}(pixel coordinates in input frame space).dst_width(int) - fallback output width used when no valid metadata is present on a framedst_height(int) - fallback output height used when no valid metadata is present on a frameoffset_log_path(string, default"reframer.log") - path to write per-frame crop offset log (for debugging)debug_log_every_n(int, default0) - if nonzero, log crop parameters every N frames to the avplumber log
Set keyframe flag in frame to make encoder output keyframe. Unlike -g
encoder option in FFmpeg, works with non-integer FPS.
1 input, 1 output: av::VideoFrame
interval_sec(int / float / string of rational) - optional, keyframe interval, in seconds. If omitted, periodic forcing is disabled and the node acts as a pass-through until triggered externally.min_interval_ms(non-negative int, default0) - minimum media-PTS spacing between forced keyframes, shared by periodic and external requests.0preserves unrestricted forcing. Requires valid PTS when enabled; a backwards jump before the last forced PTS resets the limiter. The Janus mixer output defaults to150(9 frames at 60 fps), configurable with--keyframe-min-interval-msorJanusVideoConfig.keyframe_min_interval_ms.
While rate-limited, ordinary frames pass through without waiting. Requests coalesce and remain pending until the first eligible frame, including requests from RTCP PLI/FIR; these may wait up to the minimum interval plus frame rounding. Periodic forcing cannot bypass the limit. This limits the node's forced frames, not keyframes independently inserted by an encoder, and is not a wall-clock RTP packet pacer.
Runtime control (via node.object.set / node.object.get):
node.object.set <name> trigger true— request one keyframe on the next eligible frame (one-shot, edge-triggered). Also accepted as keyforceorrequest.node.object.get <name> status— returns a JSON object:{ "requested_generation": 3, "forced_generation": 3, "pending": false, "triggered_frames": 2, "periodic_frames": 60, "interval_enabled": true, "min_interval_ms": 150 }node.object.get <name> pending— bool,trueif a trigger has been requested but not yet applied to a frame.
Encodes video or audio frames.
1 input: av::VideoFrame or av::AudioSamples, 1 output: av::Packet
codec(string) - mandatoryoptions(dictionary) - options passed to libavcodechwaccel(string, name of instance-shared object) - optional (mandatory for some encoders), name of hwaccel previously created withhwaccel.inithdr_metadata(object,enc_videoonly;enc_audiorejects it) - static HDR metadata serialised verbatim as side data before the encoder opens:primaries(three[x, y]pairs),white_point([x, y]),max_luminance/min_luminanceandmax_cll/max_fallin nits.hevc_nvencandav1_nvencemit the HDR10 SEIs from it when FFmpeg is built against nv-codec-headers 13 (driver >= 570); older headers silently emit none. The mixer fills it for PQ renditions (BT.2020 / D65) frompyplumber.mixer.color.hdr_metadata.timestamps_passthrough(bool) - defaultfalse, intended for codecs that don't buffer data (otherwise bad things like repeated timestamps may happen), replace PTS & DTS in outgoing packet with incoming PTS
Insert it between demuxer and muxer to remux packets without transcoding.
1 input, 1 output: av::Packet
no parameters
1 input, 1 output: av::Packet
bsf(string) - name of bsf to use
multiple inputs, 1 output: av::Packet
fix_timestamps(bool) - shift PTSes and DTSes so that DTSes are always increasing and (PTS >= DTS). Disabled by default.ts_sort_wait(float, seconds) - default2.5, maximum time to wait for all streams to select the packet with least DTS. Set to0to emit packets as soon as they arrive.stream_ids(list of positive integers) - if specified, will set custom stream ids in output container. Useful formpegtsformat output.metadata(list of dictionaries) - if specified, will set metadata in output container's streams.
1 input: av::Packet
format(string) - mandatoryurl(string of URL) - mandatoryoptions(dictionary) - format options that will be passed to libavformatseek_table(string of file name) - if specified, binary file with native endianness with seek table will be written.seek_table_text(string of file name) - if specified, text file with seek table will be written.hold(boolean, optional, defaultfalse) - start held: packets are taken from the input and not written, so everything before the output runs (an encoder opens on its first frame) while nothing leaves.node.object.set <name> hold falsereleases it; writing then starts at the next key packet, so ask the encoder for one.hold trueholds it again.
Format of binary seek table: 16-byte records containing:
- timestamp of the frame in milliseconds: int64_t
- bytes offset in the output file: uint64_t
Format of text seek table: values as above separated by space, each record in one line
Pending seek-table entries are flushed and the canonical seek-table links are published when a finite input reaches EOF, including a final partial batch.
1 input: av::AudioSamples (sample format must be fltp, sample rate must be equal to JACK's)
Output audio to JACK Audio Connection Kit server. Can be used for both inter-app routing and outputting audio to the sound card. Note that JACK has its own clock (soundcard clock or in case of dummy device - OS monotonic clock) so in long running streams underruns or overruns may occur, unless the stream's clock and JACK clock are synchronized (for example, input is AES67 RTP and the hardware audio interface is synchronized to AES67 master clock using wordclock or S/PDIF).
Both sample format and sample rate must match configuration of the JACK server (sample format is always fltp in JACK). Use resample_audio node to convert.
This node has an internal buffer to ensure that JACK thread can run in real time. In case of bursty streams (e.g. coming from the Internet) this buffering may be insufficinent. The realtime node with a small negative_time_tolerance (below JACK period size) will help in such cases.
channels_count(int, default 2) - number of JACK ports to create. If incoming audio stream has less channels, the remaining ones will be filled with silence. If more, the excessive channels will be discarded.port_prefix(string, default empty string) - if specified, will append channel index to this string and it'll become the JACK port name. If unspecified, JACK port name will be only the channel index.connect_port_prefix(string) - if specified, will append port number to this string and try to connect to an input port with that name in the JACK graphclient_name(string) - mandatory, name of the JACK client. If multiplejack_sinknodes are created with the same client name, ports will belong to the same JACK client (make sure to setport_prefixin such cases). If they have different client names, multiple JACK clients will be created. What is better depends on the use case - multiple JACK clients allow parallel processing but put more overhead on the CPU.
Write stream of raw packets or frames to file or pipe. Unlike output
node, can be used anywhere in graph. Outputs whatever will be thrown on
it. Use nodes rescale_video or resample_audio to convert to required
format.
1 input: anything
path(string) - file/pipe name, not URL, must be reachable via Unix file structure, libavformat protocols liketcp://aren't supportedoutput_group(string) - default "default". Name of output group for control (seeoutput.startandoutput.stopcommands) and synchronization. Since raw output doesn't have PTSes, avplumber will try to synchronize audio with video by cutting first audio frame to make it start together with first video frame.
Reinterpret plane pointers (like reinterpret_cast) without copying data. Works like a controlled cast of frame layout:
- For video, re-map data planes and change destination pixel format metadata.
- For audio (not tested yet), re-map planar channels and change destination sample format metadata.
No memory copies are performed. Plane pointers and buffer references are reused. Dimensions and linesizes are validated using av_pix_fmt_ functions.
Intended as a more versatile alternative to shuffleplanes which does not support hardware frames.
1 input, 1 output: av::VideoFrame or av::AudioSamples respectively
plane_map(object) - optional,{ "dst_plane": src_plane }mapping. If omitted, planes are mapped 1:1 up to the minimum plane count (e.g.yuva420p->yuv420pdrops alpha by default).
For video only:
dst_pixel_format(string, required) - destination software pixel format name (e.g.gray,yuv420p). When using hardware frames, this updates onlyhw_frames_ctx->sw_format;AVFrame::format(the hardware pixel format, e.g.cuda) stays unchanged.hw_frames(bool, video) - defaultfalse. Iftrue, the node expects a hardware input frame and keeps the hardwareAVFrame::formatunchanged. It cloneshw_frames_ctxand sets itssw_formattodst_pixel_format.
For audio only:
dst_sample_format(string, required) - destination sample format name (e.g.fltp). Interleaved audio is supported only when source and destination sample formats match exactly (no copying is performed).
Constraints:
- No conversions are performed. The node does not copy, up/download, or resample; it only reinterprets pointers and metadata.
- For video, mapping between hardware and non-hardware must be consistent:
hw_frames=trueis required for hardware inputs;hw_frames=falserejects hardware inputs. Otherwise pointers would become invalid. - For audio, planar-to-planar remaps are supported; interleaved requires identical formats and is effectively pass-through.
Examples:
Map V component of yuv420p to grayscale:
{"type":"reinterpret_planes_video","name":"v_to_gray","group":"proc",
"src":"v_in","dst":"v_out",
"dst_pixel_format":"gray",
"plane_map":{"0":2}}
Drop alpha from yuva420p to yuv420p (default plane map) in hardware frames:
{"type":"reinterpret_planes_video","name":"drop_alpha","group":"proc",
"src":"v_in","dst":"v_out",
"dst_pixel_format":"yuv420p",
"hw_frames":true}
Audio: select channel 1 (second plane) as mono FLT planar:
{"type":"reinterpret_planes_audio","name":"pick_ch1","group":"proc",
"src":"a_in","dst":"a_out",
"dst_sample_format":"fltp",
"plane_map":{"0":1}}
Take a frame and write it to picture buffer that can be later used by sentinel_video.
1 input: av::VideoFrame
buffer(string, name of instance-shared object) - mandatory, picture buffer nameonce(bool) - default true, finish after getting a single frame
Discards incoming packets just like /dev/null.
1 input: anything
no parameters
Writes audio envelope data for waveform display (no images; data only). One binary file per granularity level (like mipmaps for different zoom), plus an index.json describing layout. Files are appended incrementally; recording length need not be known in advance. To align envelope samples to video frames, use the video frame duration as one of the granularities (e.g. "1/25" for 25 fps).
1 input: av::AudioSamples
path(string) - directory (or base path) for output files; must be a filesystem pathgranularities(list of strings) - segment duration per envelope sample, as rationals in seconds (e.g.["1/25", "1/2", "1"]). One file per entry
The node writes index.json (version, sample_rate, channels, levels map keyed by duration_sec with file name per level, metrics map keyed by id with offset_bytes and stride_bytes).
Each sample is stored as interleaved records with channel data, currently containing: positive peak, negative peak, RMS. To read one metric across channels, use offset_bytes and stride_bytes from index.json. For forward compatibility, do not assume that each channel's record will always contain 3 bytes.
Example for 2 channels (one envelope sample = currently 6 bytes):
byte index: 0 1 2 | 3 4 5 | 6 ...
^ ^ ^ | ^ ^ ^ | ^
metric: pos neg rms | pos neg rms | pos
channel: ch0 ch0 ch0 | ch1 ch1 ch1 | ch0
time range: \________________________________/ \____...
0..1 1... (unit: segment duration)
positive_peak: offset_bytes=0, stride_bytes=3 --> read at 0, 3
negative_peak: offset_bytes=1, stride_bytes=3 --> read at 1, 4
rms: offset_bytes=2, stride_bytes=3 --> read at 2, 5
Each envelope sample is 1 byte per value, 0.5 dB resolution, -127..0 dB range: 0 = -127dB or less, 254 = -0.5..=0dB, 255 = clip
{
"channels": 2,
"levels": {
"1": { "file": "level_2.bin" },
"1/60": { "file": "level_0.bin" },
"1/8": { "file": "level_1.bin" }
},
"metrics": {
"negative_peak": { "offset_bytes": 1, "stride_bytes": 3 },
"positive_peak": { "offset_bytes": 0, "stride_bytes": 3 },
"rms": { "offset_bytes": 2, "stride_bytes": 3 }
},
"sample_rate": 48000,
"version": 1
}
The renderer needs to:
- read
index.json - open binary file corresponding to the needed time resolution (recommended: greater or equal to waveform image resolution), specified in
levelsmap - for each needed metric, initially jump to the
offset_bytesand then jump bystride_bytesto read subsequent interleaved samples (so the actual stride for a single channel isstride_bytes*channels) - if waveform is to be rectified, compute
max(positive_peak, negative_peak) - if mono waveform is to be displayed, compute maximum (for peaks) or RMS (for RMS) of all channels
- if the read time resolution was greater than resolution of the waveform to be displayed, compute maximum or RMS of waveform values that correspond to the same “pixel” on the resulting image
Get video frames from CUDA IPC memory. Frame pointer and parameters are read from named pipe. See src/nodes/cuda/ipc_cuda_source.cpp for structure.
1 output: av::VideoFrame (frame's pixel format will always be cuda)
pipe(string) - mandatory, path to named pipehwaccel(string, name of instance-shared object) - mandatory, name of hwaccel previously created withhwaccel.init
Play a headerless raw NV12 or P010 file as CUDA frames. Each picture is read from the file
straight into page-locked host memory and the device takes it from there, so the samples are
copied once on the CPU (page cache to that memory). The chain input_rec (rawvideo) → demux
→ dec_video → filter_video (hwupload_cuda=pinned=1) copies them twice and runs four
threads; this node runs one. Nothing is kept between passes: every picture shown is read from
the file and transferred for that showing.
1 output: av::VideoFrame (hardware "pixel format" cuda). The node blocks while its output
queue is full, so the consumer (typically realtime) paces the reading; give the queue a
capacity of 1 to hold one uploaded picture ahead. Timestamps count pictures in a time base of
one frame period.
Parameters:
url(string, required) - path of the file. A trailing part of a picture is not shown.pixel_format(string, required) -nv12orp010le; rows carry no padding.video_size(string, required) -WIDTHxHEIGHTframerate(string, required) - rational, e.g.30000/1001hwaccel(string, required) - CUDA device created withhwaccel.initloop(bool, defaultfalse) - start again after the last picture. Without it the node sends the end-of-file marker (unlesssend_eofisfalse) and finishes, whichauto_restartacts on.loop_continuous_ts(bool, defaultfalse) - timestamps count on across passes instead of starting from zero at each oneframe_color(object, optional) - colour fields written on every frame, as forone_to_many:color_trc,color_primaries,colorspace,color_rangewrite_combined(bool, defaultfalse) - allocate the page-locked memory write-combined. Whether that makes the read cheaper depends on the host; measure before using it.pool_frames(int, default0: no bound) - at most this many device frames. FFmpeg's pool grows to the most frames ever out at once and keeps them; bounded, the node waits (up to five seconds, as it does for device memory) until one comes back.statsreportsframes_allocated.resident(bool, defaultfalse) - withloop: keep the file's pictures in the page-locked memory. Each is read when it is first shown and only transferred after that, so a pass after the first reads nothing. Where a pass starts the node compares the file's modification time and reads the pictures again if it has changed. The memory is the size of the file and cannot be swapped out; the node fails to start if it cannot be had.
The read is a blocking local pread; there is no read timeout, and a file that ends inside a
picture after it was opened stops the node with an error. node.object.get <node> stats
returns frames, passes, the number of pictures read from the file (file_reads) and the
duration of the last read and upload (read_us, upload_us).
The upload half is src/nodes/hwaccel/CudaPinnedUploader.hpp, which any source able to write
its samples where it is told can use.
Get audio frames from named pipe. See src/nodes/ipc_audio_source.cpp for header structure. Header must be followed by interleaved audio samples.
1 output: av::AudioSamples
pipe(string) - mandatory, path to named pipe
1 input: av::VideoFrame, 1 output: av::Packet
Extract ATSC A53 Part 4 Closed Captions data from video frame. Subtitle codec is usually EIA-708 or 608 in such side data. When outputted to UDP with libavformat's special data 'muxer' (see examples/extract_cc_data.avplumber), subtitles can be parsed using CCExtractor or GStreamer (YMMV).
no parameters
Drop data with invalid timestamps; pass through otherwise. Useful to prevent malformed inputs from propagating downstream.
1 input, 1 output: anything
no parameters
Receive GPU frames via a UNIX domain socket with FD passing (DMA-BUF). Produces DRM PRIME frames with metadata taken from the sender.
Frame references retain their release-ACK connection through node shutdown or
reconnection. The matching dma-browser sender stops deliveries on Drain and
reclaims outstanding frames on Drained, sent after the last reference is freed.
An unclean disconnect quarantines unacknowledged browser textures; see
DMA-BUF recovery and counters.
Each frame is a 48-byte TexInfo header with one DMA-BUF fd sent via SCM_RIGHTS.
Stock Electron waits for its GPU work before delivering the paint event.
1 output: av::VideoFrame (hardware "pixel format" DRM_PRIME)
Parameters:
socket(string, required) - path to the UNIX domain sockethwaccel(string, optional) - name ofhwaccelobject; when set, a matchinghw_frames_ctxis attached for downstream filters/encodersfps(rational string, optional) - publishes the sender's requested frame rate to downstream nodes; frame timestamps use a1/1000000time basereturn_queue(integer 0 to 64, optional, default0) - frames that wait to go back to the sender.0sends each release ACK when the graph lets its frame go. WithN, a finished frame waits in the return queue and one goes back per frame that arrives; only beyondNwaiting does the oldest go at once. Chromium's capture pool keeps two idle buffers and destroys the rest, so a consumer that lets frames go in bunches makes it destroy and allocate buffers all the time, and every new allocation is a new import (and a released one) downstream. One buffer back per buffer handed out keeps the pool on a fixed set. Queued frames count against the sender'sringSize: open the window with the frames in flight plusN.
node.object.get <node> frame_stats reports the received-frame count, plus
released frames, sent ACK messages, pending ACK messages and frames in the return queue (returns_queued) on the current
connection. ACK counters reset on reconnection; the received count does
not. Lifecycle drain markers also count as sent messages. Pending ACKs distinguish
frames already released by the graph from frames still referenced downstream.
Receive audio frames over a UNIX domain socket. Expects a simple header followed by interleaved float32 PCM.
1 output: av::AudioSamples
Parameters:
socket(string, required) - path to the UNIX domain socketsample_rate(int, default48000)channels(int, default2)
reconnect_delay_ms(int, default50)
Convert CUDA av::VideoFrame to EglImageFrame (RGBA) using CUDA kernels and an EGL-backed texture pool. Intended for zero-copy rendering paths (e.g. OBS).
1 input: av::VideoFrame (pixel format cuda), 1 output: EglImageFrame
Parameters:
pool_id(name of instance-shared object, default"default") - shared EGL image pool idpool_size(int, default8) - pool capacity
Import DRM PRIME frames into CUDA frames via EGL/GL interop, without copying: the output frame is the mapped buffer and keeps the input frame, and with it the producer's buffer, until the last consumer releases it. Non-DRM PRIME frames are passed through unchanged. A tiled (array) import is a texture-backed frame that only the CUDA compositor's RGB-to-YUV path samples; the compositor rejects other combinations before submitting draw work.
1 input: av::VideoFrame (expects DRM_PRIME hardware "pixel format", pass-through otherwise), 1 output: av::VideoFrame (hardware "pixel format" cuda)
Parameters:
hwaccel(string, required) - CUDA device created withhwaccel.init
Retired imports are released on a cleanup worker shared by the instance. Incoming frames needing a new import are dropped while cleanup is pending or their import budget is full; cached imports continue normally. Dropped inputs release their browser capture slots.
node.object.set <node> purge_idle <ms> releases, within a quarter of a second, every import that
no frame holds and none has used for that many milliseconds, without waiting for import_ttl_ms
or for the next frame: for a caller that knows a burst of allocations is over, such as a show that
has finished starting. They count as expired.
node.object.get <node> import_stats includes an admission_dropped frame
counter, a texture_failed frame counter (an array import whose texture object
could not be created; the frame is dropped) and instance-wide cleanup counts. These counters distinguish admission
drops from EGL/CUDA import work without per-frame logs.
Apply ByteTrack to object detections already attached to a video frame. The node is
model- and domain-neutral: by default it tracks every detection in yolo_detections.
1 input: av::VideoFrame, 1 output: the same frame with track annotations added
Parameters:
metadata_key(string, optional, defaultyolo_detections) - detection payload to updatetarget_labels(array of strings, optional) - track only matching labelstarget_class(int, optional) - track only this class IDmin_conf(number, optional, default0.01) - minimum detection confidenceframe_rate,track_buffer,track_thresh,high_thresh,match_thresh,low_match_thresh- ByteTrack tuningcamera_shot_metadata_key(string, optional) - reset tracks on shot transitionspredict_on_empty,emit_lost_tracks(bool, optional) - lost-track output behavior
Draw detection results that are already attached to a video frame as JSON
metadata: boxes, labels of 5x7 bitmap text (A-Z, 0-9, : _ - .), a
trail of line segments, pose keypoints, segmentation masks. Built with
HAVE_CUDA=1 and NEURAL_NET=1. The box and label parameter tables are in
draw_bbox and
draw_bbox_labels.
1 input: av::VideoFrame, hardware "pixel format" cuda or cuarray, software
format nv12 (anything else is an error), 1 output: av::VideoFrame, always
cuda, with the input's timestamps, metadata and side data
The node never draws on a frame another reader may hold. Which frame it draws on depends on its input:
cudafrom the picture pool of a draw node above, when no other frame references it: the input frame itself, without a copy. This is checked for every frame. Asplit, a wiretap or a node that keeps frames between two draw nodes leaves a second reference, and the node then copies, into a frame of the pool the picture came from.- any other
cudaframe: a copy in a frame of a pool the node owns. The pool has the device, the software format and the width and height of the input's frames context, so the copy has the layout and the size a frame of the input's own pool has (the size of the frames context, which for a decoder can be the coded size, 1088 lines for 1080p H.264). Both planes are copied with one call when the two frames hold them in a single buffer at one pitch, otherwise with one call each. cuarray(an NVDEC surface, which is also the decoder's reference picture): a linear picture of it at the frame's size, read once through the rect compositor's array reader (cuda_rect_draw, so it needsHAVE_NVCC=1) into a frame of the same pool. The stream is synchronized and the surface released before the frame is put, also when the draw fails, so a draw node holds one decoder surface while it processes a frame and none while it waits.
So the first draw node of a chain makes the picture and the nodes below draw
on it: one picture per frame and chain, whatever the input storage. This is
the usual case, not a guarantee: a node releases its own reference to a frame
only after it has put it, and a node below that takes the frame before that
sees two references and copies. The pictures object counts both.
The output frames of a chain belong to the frames context of its first node's
pool, not to the one the input came from. It is recreated when the input's
device, width or height changes and when the node is restarted. A consumer
that binds to one frames context, such as an hwdownload filter, has to be
set up again then, as it has to when the context of its input changes.
All nodes of a chain run on the CUDA stream of the frame's device, for a
CUarray decoder its own stream; the nodes take no hwaccel parameter. A node
synchronizes that stream after its kernels. A node with nothing to draw on a
linear frame launches nothing and does not synchronize: a copy it made is
still queued on that stream when the frame is put.
node.object.get <node> pictures returns how many frames the node drew on a
picture made of a CUarray (from_array), on a copy (copied) and on its input
frame (in_place).
Coordinates: detections[].xyxy is in model pixels unless the payload has a
coord_space other than model, in which case it is in pixels of the frame
the node receives. Model pixels are mapped with model_content_width,
model_content_height, model_content_offset_x, model_content_offset_y when
set (a letterboxed model input), otherwise scaled by the frame size over the
payload's model_width, model_height.
Parameters common to all:
metadata_key(string) - the frame metadata entry to draw; defaultreframer_bbox(draw_bbox),yolo_players(draw_bbox_labels),yolo_detections(draw_trail,draw_segmask),yolo_pose(draw_keypoints).draw_bboxanddraw_bbox_labelsalso takemetadata_keys(array).model_content_width,model_content_height,model_content_offset_x,model_content_offset_y(number, default0: unused)debug_log_every_n(int, default0)
Everything a model says about a frame, drawn in one pass: detection boxes,
their labels, pose or face keypoints and a trail. layers (array, required)
are painted in the order given. A layer has a kind and the parameters of the
draw node of that kind, which reads the frame's metadata for it:
kind |
Reads and takes the parameters of |
|---|---|
boxes |
draw_bbox |
labels |
draw_bbox_labels |
keypoints |
draw_keypoints |
trail |
draw_trail |
{"type": "ml_debug", "src": "detected", "dst": "drawn", "layers": [
{"kind": "boxes", "metadata_key": "yolo_players", "label_colors": {"Player": "green"}},
{"kind": "labels", "metadata_key": "yolo_players", "label_template": "ID:{track_id}", "font_scale": 1},
{"kind": "keypoints", "metadata_key": "yolo_pose", "radius": 3, "color": "green"},
{"kind": "trail", "metadata_key": "ball_trail", "thickness": 1, "color": "light_blue"}
]}The frame comes out as it would from a chain of those draw nodes in the same
order, pixel for pixel, and the node handles frames as they do (input and
output formats, the picture it draws on, the pictures object). What differs
is the work: a metadata key that several layers read is parsed once; the items
are sorted on the CPU into the 16x16-pixel tiles they touch, and one kernel
launch visits only those tiles, each thread only the items of its tile; the
stream is synchronized once. A draw node launches a thread for every pixel of
the frame and every thread tests every item, so 118 face landmarks on a
1920x1080 frame are 306 million tests there and some tens of thousands here.
node.object.get <node> draw returns the items and tiles of the last frame
and the frames that had anything drawn on them.
tests/cuda/nvdec/draw_arrays.py runs chains on linear and on CUarray decode
on an NVIDIA GPU: an undrawn chain against the plain decode, three draw_bbox
against one, one ml_debug against the chain of four draw nodes and against
three draw_bbox, CUarray against linear, the pictures counters, and with
--expect the linear cases against a report of another build.
tests/test_draw_picture_frame.py checks without a GPU which frames count as
private, which pool a copy comes from, when one call may copy both planes, and
that mask side data keeps its buffer.
Import DRM PRIME (DMA-BUF) frames into an EGLImageKHR via EGL_EXT_image_dma_buf_import and output them as EglImageFrame (no CUDA processing).
1 input: av::VideoFrame (expects DRM_PRIME hardware "pixel format"; non-DRM PRIME frames are dropped), 1 output: EglImageFrame
Supported DRM formats (layer0/plane0 only):
DRM_FORMAT_ABGR8888DRM_FORMAT_ARGB8888
Cache behavior:
- Identifies a physical allocation by
st_dev,st_ino, dimensions, DRM fourcc, modifier, plane offset, and plane pitch. FD integers are not identity because they can be reused. - Keeps a duplicated DMA-BUF FD alive with every cached
EGLImageKHR. - Reuses the same immutable
EGLImageKHRwhen that allocation returns. The producer/consumer protocol must retain each delivered frame until downstream GPU reads finish;ipc_dmabuf_sourceprovides that ordering through release acknowledgements. - Evicts idle entries after
ttland the least-recently-used entry at the configured limit.
Parameters:
cache_mode(string, optional, default"reuse") -reuseoroffttl(float seconds, optional, default3.0) - cache entry time-to-livemax_cache_entries(integer, optional, default64) - maximum retained importsdebug_log_every_n(integer, optional, default0) - periodically log aggregate cache counters
Scale and compose multiple cached EglImageFrame inputs directly into one
pitched CUDA RGB0 frame (or RGBA with output_alpha). Each stable input image
is registered once with
cuGraphicsEGLRegisterImage; its CUeglFrame and CUDA texture object are then
kept for that physical allocation. CUDA EGL image resources do not require
per-frame map/unmap. Every output tick samples the cached texture objects with
bilinear scaling and writes the configured rectangles directly. There are no
per-input CUDA frames, scale filters, or pixel copies.
The node owns its output clock. It keeps the latest frame independently for each input, so a stalled input repeats its last image without blocking other inputs or the program output. The frame lifetime holders are released only after a CUDA event confirms that the sampling kernels have completed.
N inputs: EglImageFrame, 1 output: CUDA av::VideoFrame with RGB0 or RGBA
software format
Parameters:
src(array of edge names, required) - input EGL image edgesdst(string, required) - output CUDA video edgehwaccel(string, required) - CUDA device created withhwaccel.initwidth,height(integers, required) - output canvas dimensionslayers(array, required, one per input) - objects containingdst_x,dst_y,dst_w, anddst_hfps(ratio string, optional, default"60/1") - independent compositor output rateoutput_alpha(boolean, optional, defaultfalse) - advertise RGBA output, clear uncovered pixels to transparent black, and copy each layer's source alpha when its EGL format exposes one (otherwise opaque). The default keeps the existing opaque RGB0 output for encoder compatibility. Layers overwrite earlier rectangles; this option preserves alpha for downstream composition, it does not add source-over blending between overlapping layers.cache_ttl(float seconds, optional, default3.0) - idle interop-slot lifetimemax_cache_entries(integer, optional, default440) - maximum registered slotsdebug_log_every_n(integer, optional, default0) - periodically log aggregate compositor counters
tests/cuda/test_egl_overlay_alpha.cu checks the production kernels on an
NVIDIA GPU, including opaque compatibility, alpha lane layouts, transparent
uncovered pixels, bilinear alpha sampling, and pitched output. It does not
replace an end-to-end DMA-BUF/browser interop test.
Compose CUDA frames into one canvas: each layer scales one input's frame, or a
crop of it, into a rectangle; the background and every layer are drawn in one
kernel launch. The node has no clock: each output frame draws the active
inputs' frames of one timestamp, matched across inputs, and an input without a
frame at that timestamp repeats its previous one. Upstream owns pacing. The
mixer's clocked compositor is mixer_compositor, its
downstream keyer mixer_keyer.
N inputs: CUDA av::VideoFrame (src, at most 193), 1 output: CUDA av::VideoFrame with software format sw_format
Parameters:
hwaccel(string, required) - CUDA device created withhwaccel.initwidth,height(int, required) - canvas sizesw_format(string, defaultnv12) - canvas storage: semiplanar YUV (nv12,p010le,p210le) or packed 8-bit RGB (rgb0,bgr0,rgba,bgra). A semiplanar YUV input of another depth or chroma subsampling is converted as it is drawn: 8-bit codes are multiplied up to a 10-bit canvas, 10-bit codes divided down to an 8-bit one and rounded to nearest (min(255, (v + 2) >> 2)), without dithering. That is a depth change only, not tone mapping, so a deeper input tagged PQ or HLG is an error on an 8-bit canvas (the text carrieshdr_source_transfer=pqor=hlg): tone map it before the node. Withcolorset it is refused like any input whose colour tags differ from the canvas's.layers(array of objects, required) - at mostmax_layers; each drawsinput(an index insrc; omitted, the layer's position) atdst_x,dst_y,dst_w,dst_h(size omitted: the source's), optionally fromcrop({"x", "y", "w", "h"}), withfitstretch(default) orcontain(source_canvas{"w", "h"}letterboxes into a virtual source canvas), inzorder (lower first),blendhonouring the source's alpha.filterresamples a YUV source on a YUV canvas withbilinear(default),bicubic(4x4 cubic, no prefilter when shrinking),multisample(the mean ofsamplesbilinear samples,4(default) or8, spread over the source area of one output pixel) orauto:multisamplewhen the layer shrinks by more thanmultisample_above(default2.0),bicubicwhen it enlarges by more thanbicubic_above(default1.3), elsebilinear; the axis with the larger factor decides, shrinking first.bicubic_param(default0) is the cubic's coefficient as FFmpegscale_cuda'sparam:0is that filter's default bicubic, which weighs only the two inner taps per axis (Hermite),0.5is Catmull-Rom. Packed RGB sources and canvases are always bilinear. Per-frame metadata keeps a layer's filter settings unless it names them. The format is parsed insrc/mixer/primitives/compositor_layers.hpp.max_layers(int, default256) - layers per frame; sizes the GPU rect tableactive_inputs(mask, default all) - inputs drawn: a JSON number, or a least-significant-bit-first bit string ("1011") beyond 64 inputsmetadata_key(string, defaultrect_overlay_v1) - frame metadata key with per-frame layer changes,{"layers": [...]}insrcorder or{"<index>": {...}}, read from the frame the output copies its properties fromcolor(sdr,hlgorpq, optional) - canvas colour contract: inputs' colour tags are checked against it and the output carries it; HDR needs 10-bit storage. Unset, the output takes its tags from that metadata frame.sdr_white,hdr_peak(float nits, default203,1000) - graphics white and peak withcolorwarmup_timeout_ms(int, default0: no bound) - how long to wait for every active input's first frame before drawing without the missing onesdebug_log_every_n(int, default0) - log every n-th output frame
node.object.set <node> active_inputs <mask> and
node.object.set <node> layers [...] change the mask and the layers. The
parameters fps, aux_mode, clock_input, subscriptions and
pgm_delay_frames fail, as do the objects prewarm_inputs, warm_reset,
composition and fade_inputs: they belong to mixer_compositor and mixer_keyer.
The mixer's clocked compositor: its scene slots, the wipe overlay and the AUX
buses. Parameters as for cuda_rect_overlay, but the node
owns its output clock: inputs are live with monotonic timestamps, and for every
tick of fps a shared playout (src/mixer/Playout.hpp) picks each input's
frame stamped nearest that tick, drawn latency_ms after it. A late input
repeats its previous frame rather than delaying the output. See
mixer.md.
Parameters, in addition:
fps(ratio string, required) - output ratelatency_ms(float, default two frames, at most six) - how long after a tick its frame is drawn: the inputs' arrival budgetpgm_delay_frames(int, optional) - match the last input that many ticks back (a Program preview's finished program arrives one frame after its sources)input_poll_ms(int, default0) -0: the output thread is woken by every frame an input gives. Above0: it sleeps until the next deadline or a control change and looks for frames at most this often in between. A tick is drawn at its deadline in both cases; the second spares a compositor with many inputs thousands of wake-ups a second. Each input's queue must hold what arrives within one interval.node.object.get <node> statusreports the setting andinput_passes, how often the thread has looked.aux_mode(bool, defaultfalse) - an AUX bus: a private CUDA stream, subscribed inputs, layers fromcompositiononly (no per-frame metadata layers), and frames dropped rather than waited for when the output edge is fulloutput_hwaccel(string, required withaux_mode) - name the bus's device with that stream is published under, for its conversion and encodersubscriptions(array of strings, required withaux_mode) - one shared frame subscription per input, insrcorder; sources never wait for the busmixer(string, optional withaux_mode) - the mixer whose scene definitions are frozen from then on
clock_input fails: it is mixer_keyer's.
Objects (node.object.set):
active_inputs,layers- as forcuda_rect_overlayprewarm_inputs(mask) - inputs whose frames advance through the playout without being drawn, so a switch to them is immediatewarm_reset- reject frames stamped over a tick before the one being drawn; prewarmed inputs keep their held picturescomposition(aux_modeonly) -{"layers": [...], "active_inputs": <mask>, "revision": "<string>"}: applied on the tick its new inputs are ready; if they are not ready within max(250 ms, twicelatency_ms), the previous layout stays andcomposition_errorsays so.revision(optional) names it in the status once it is drawn.
node.object.get <node> status: output_drops (frames dropped for a full
output edge; the bus keeps playing and draws again once it has room), playout
(frames, repeats, discarded, overflow, missed_deadlines: totals over the inputs; node.object.get <node> per_input gives repeats, discarded and overflow of each input, for finding the one that
misbehaves), with aux_mode composition_pending, composition_error and
composition_revision, the revision of the composition being drawn (set when
one is applied at once or a staged one becomes ready, unchanged when a staged
one is dropped; empty for one without a revision).
node.object.set <node> reset_counters true starts missed_deadlines, the per-input counters,
output_drops and playout.timing again from zero, for a caller that has finished starting the
show; frames keeps counting and the playout is not disturbed.
playout.timing says where each drawn tick's time went: period_ms, bounds_ms (the upper
bounds of all buckets but the last: a quarter, a half, three quarters, one, one and a quarter,
one and a half, two and three frame periods) and four histograms, each with counts per bucket,
count, mean_ms and max_ms since the node started: draw (the whole draw), issue (queueing
it: the node's own work and the CUDA library's locking), gpu_wait (waiting for the GPU to finish
it and the frames it reads) and downstream (waiting for room on the output edge). A draw in the
first four buckets fitted its slot.
The mixer's downstream keyer (DSK): blended key layers over the program.
Parameters as for cuda_rect_overlay; the program input
drives the output. Each program frame is drawn at once over every key above
fade level 0, with each key's newest frame stamped at or before that program
tick. Keys are never waited for; with no key visible the program frame passes
through untouched.
With keys visible the whole canvas is composed in one pass. Keys named in
bounded_keys may instead be composed alone: when every visible key is one of
them, the output is a copy of the program frame on which only the rectangles
those keys cover are composed, so a key on a third of the picture costs a third
of the compositing. Keys that overlap share a rectangle. This needs the program
layer to be the program frame unchanged (the canvas's format and size, drawn
whole and unscaled) and the keys to lie in at most four separate rectangles
covering at most 60 % of the canvas; otherwise the whole canvas is composed.
The picture is the same either way.
Parameters, in addition:
clock_input(int, required) - the program's index insrcbounded_keys(array of ints or bool, default none) - the inputs of the keys whose rectangles may be composed alone;truenames every keyfps(ratio string, required) - the program's rate; paces the key subscriptions and the fadessubscriptions(array of strings, optional) - one shared frame subscription per input, insrcorder; an empty name (the program) subscribes nothingmixer(string, optional) - the mixer whose background transitions the Next Transition keys follow (seeprepare_inputs)ftb_input(int, optional) - an input nothing feeds: the keyer holds one opaque black frame there, which its layer stretches over the canvas above every key. Its key fade is the fade to black of the keyed program; the clean feed, taken before the keyer, is never faded.
aux_mode, latency_ms and pgm_delay_frames fail: the keyer has no playout.
Objects (node.object.set):
active_inputs <mask>cuts the keys to the mask;fade_inputs {"active_inputs": <mask>, "duration_ms": 0..10000, "curve": <name>}fades the keys the mask changes (0 cuts)key_changes {"inputs": <mask>, "active_inputs": <mask>, "toggle": bool, "duration_ms": 0..10000, "curve": <name>}changes the keys ininputstogether on the next program frame, to the mask or (toggle) to the opposite of their state; a cut also ends a fade they are runningprepare_inputs {"inputs": <mask>}selects the Next Transition keys. From the first program frame of each following background take ofmixer, a key turning on is at the take's progress and one turning off at its complement: they mix with a mix, hold when it is held, follow the lever, cut with a cut and switch at a wipe clip's cut point, both ways with the lever's wipe. A completed take leaves them switched; a cancelled one returns them to the state they had. A command for such a key during the take ends the take for it first.
node.object.get <node> keys lists, per key input, what the last program frame
showed: on (the key's state), on_air, phase (on, off, fading-in,
fading-out, or unknown before the first frame), opacity, available (it
has a frame), pending (the state is not on air yet) and next_take.
The only writer of an AUX bus compositor's composition: each one is the
layers of the mixer's preview scene followed by a base, so the preview changes
on the bus frame that leaves it with the take's first program frame (see
mixer.md). What the layers draw is the control
side's: this node knows no cells. No edges.
Parameters:
mixer(string, required) - the mixer whose preview changes it followscompositor(string, required) - the bus'smixer_compositor(aux_mode), resolved once at creationfps(ratio string, required),latency_ms(float, required) - the bus compositor's rate and playout latencymain_latency_ms(float, defaultlatency_ms) - the main mixer's playout latency: when a program frame leaves its compositorpgm_delay_frames(int, default 0) - the bus's program input delayalign(string, defaultprogram) -program: change with the program output;pgm_tile: with the bus's program input,pgm_delay_frameslaterlayout(object, optional) - the initiallayoutobject
Objects (node.object.set):
layout-{"revision": "<string>", "pvw": {"<scene>": {"layers": [...], "active_inputs": <mask>}, ...}, "base": {"layers": [...], "active_inputs": <mask>}}, replaced as a whole and applied at the next wake. Every composition carries itsrevision.
node.object.get <node> status: the last change's pvw_scene, pgm_scene,
kind, align, target_tick and latencies (pvw_latency_ms,
pgm_latency_ms, pvw_minus_pgm_ms, last_change_to_apply_ms,
last_target_error_ticks, target_unreachable), the layout_revision it
holds and error.
Objects (node.object.set):
active_inputs(mask) - the keys on air, switched with a cutfade_inputs({"active_inputs": <mask>, "duration_ms": 0..10000, "curve": "linear"}) - the keys the mask switches fade overduration_ms;curveislinear,ease-in,ease-outorease-in-outlayers- as forcuda_rect_overlay
Enabled only if avplumber is compiled with BUILD_TYPE=Debug. Delay packets
or frames for a random time. Timestamps aren't modified. Delay will be
gradually decreased down to 1ms if a congestion is detected.
1 input, 1 output: anything
Enabled only if avplumber is compiled with BUILD_TYPE=Debug. Delay packets or frames for specified time. Timestamps aren't modified.
1 input, 1 output: anything
delay(float) - mandatory, delay in seconds