Historical evidence, superseded guidance. This is the earlier article, preserved with its original workload, measurements and revision links. Its defaults, branch/deployment labels and recommendations are not current. Read the current recipe.

AVPlumber · Mixer optimization cookbook

One main CUDA stream, one per monitor

Implementation links are pinned to develop at c20464a5 (2026-10-09). The mixer has changed since; this page was checked against develop at 8fa15c92 on 2026-10-13. Measurements retain their original workload and scope. Watch the current 192-input L4 demo.

Current scope. AUX streams isolate stream ordering, not GPU capacity: kernels, memory bandwidth and encoders still compete with program. CUarray decode and raw uploads (the direct node, the pinned filter and v210) also have producer-stream synchronization requirements.

The program runs on one CUDA stream; each aux monitor gets its own non-blocking stream in the same CUDA context, so a slow monitor cannot hold up the program or the other monitor.

Status: Implemented

The diagram below shows the linear decode layout. Opt-in NVDEC CUarray output adds a private producer stream per decoder in the same context. Compositors wait on each source's advertised stream before reading its array; program and AUX output streams keep their existing roles.

Three CUDA streams in one context The default stream carries decode copies, scaling, browser imports, compositors, wipe, keyer and program encoders. Two aux streams each carry one monitor's compositor, colour conversion and encoder. Event waits go only from the default stream to the aux streams. One CUDA context on the mixer GPU (device mixer_gpu) Default stream · program NVDEC copies scale / convert browser imports M/E A and B wipe · keyer PGM and clean NVENC most of the graph's nodes event wait, on the GPU event wait, on the GPU aux_<id>_gpu · Program preview own non-blocking stream compositor SDR conversion NVENC H.264 aux_<id>_gpu · Multiviewer own non-blocking stream compositor SDR conversion NVENC H.264 Upload streams · one private stream per v210, direct or pinned raw source
Red runs the show. Green and blue are the two monitors in the demo. Dashed arrows are the only links: an aux stream waits for the frames it reads. Nothing points back, so the program never waits for a monitor.
A slow monitor does not delay program ticks On the default stream, program ticks keep a steady rhythm. On one aux stream a frame takes much longer. The other aux stream and the program continue on time. time → Default (PGM) Preview aux slow frame Multiviewer aux Illustration, not a measurement. All streams share the GPU's engines.
Queued work on one stream does not block the others. With a single shared stream, the slow preview frame would sit in front of the next program ticks.

At a glance

Why not a stream per node?

Most of the graph's GPU work sharing one stream is not the bottleneck by itself. The limit measured at high source counts was a lock inside the CUDA library, not stream ordering. Uploads queue for it (Why uploads limit this mix), and at 192 sources its holder was the browser importer's release (Browser frames go back one per new frame).

Raw uploads are another exception to the single stream. The patched FFmpeg hwupload_cuda v210 path creates a private non-blocking stream per source and synchronizes it before publishing, so consumers get a complete frame. NV12 and P010 sources do the same on both paths that avoid the stock upload: canvas.raw_upload: "direct", which every show the setup builds uses and where raw_video_file_cuda does it in CudaPinnedUploader, and "pinned", the patched filter. With the library default, "hwupload", NV12 and P010 upload through FFmpeg hwupload on the default stream; see Raw frames read straight into the memory the GPU reads.

Where in the code