---
title: "Built-in nodes"
description: "Every node that ships inside the runtime, with its ports, settings and defaults, and what it does on Linux and macOS."
type: "docs-page"
url: "https://tatolab.com/docs/reference/built-in-nodes"
site: "Tatolab"
section: "Reference"
---

# Built-in nodes

> Every node that ships inside the runtime, with its ports, settings and defaults, and what it does on Linux and macOS.

Built-in nodes are native code inside the runtime. Their per-frame work never enters Python, so they suit the parts of a stream with deadlines: capture, display, codecs and recording. Import them from `tatolab.stream` and add them like any node:

```python
from tatolab.stream import CameraSource

camera = stream_builder.add(CameraSource, config={"max_width": 1280, "max_height": 720})
```

Each one's settings are a `TypedDict` named after it (`CameraSourceConfig`, and so on), so your editor completes the keys and a type checker flags typos. Every setting is optional unless it says otherwise.

## Video

### `CameraSource`

Captures live video from a camera: V4L2 on Linux, AVFoundation on macOS. Frames go straight to the GPU when the device can hand them over, and through one CPU upload when it can't.

| | |
|---|---|
| Outputs | `video`: frames, stamped with the moment the device captured them |

| Setting | Default | |
|---|---|---|
| `device_id` | the first camera found | A V4L2 device path on Linux (`/dev/video0`), or a camera's unique ID on macOS. On macOS the default is the first built-in camera. |
| `max_width` | `1920` | The largest width to capture |
| `max_height` | `1080` | The largest height to capture |

A `device_id` that can't be opened is refused when the node starts. On macOS, the first run asks for camera access without waiting: the stream starts, and frames begin when access is allowed.

Two limits to know on Linux. `CameraSource` reads uncompressed NV12 or YUYV only: cameras that send only MJPEG, monochrome or raw Bayer aren't read, and most USB 2.0 webcams send uncompressed HD at 5 to 10 frames per second. It picks the largest size within `max_width` and `max_height`, whatever its frame rate, so lower them (640 × 480 usually runs at 30) or use a USB 3 camera; `v4l2-ctl --list-formats-ext` shows what yours offers. And there are no exposure, gain, focus, white-balance or frame-rate settings: set those with `v4l2-ctl` before the stream starts.

### `TestPatternSource`

Color bars, for a stream on a machine without a camera.

| | |
|---|---|
| Outputs | `video` |

| Setting | Default |
|---|---|
| `width` | `1280` |
| `height` | `720` |

### `DisplayWindow`

Shows video in a window, in step with the display's refresh. Add as many as you like.

| | |
|---|---|
| Inputs | `video` (newest) |

| Setting | Default | |
|---|---|---|
| `title` | `"StreamLib"` | The window's title |
| `width` | `1280` | Starting width, in the desktop's logical pixels |
| `height` | `720` | Starting height, in logical pixels |
| `scaling` | `"fit"` | `"fit"`: the whole frame, with black bars; `"fill"`: fill the window, cropping the frame; `"stretch"`: stretch the frame to the window |

A window that can't open logs `DisplayWindow: no window — running degraded` with the reason, and still reads its input, so the nodes before it keep running. On a machine with no display, leave windows out and look at frames with `tatolab exchange`.

### `VirtualCameraSink` (Linux)

Presents video as a camera other applications can select. The camera exists while the node runs. See [Virtual camera](https://tatolab.com/docs/build/virtual-camera).

| | |
|---|---|
| Inputs | `video` (newest) |

| Setting | Default | |
|---|---|---|
| `name` | `StreamLib Camera` plus a short ID | The camera's name in applications, cut to 31 bytes |
| `door` | `"auto"` | `"v4l2loopback"`, `"pipewire"`, or `"auto"`: v4l2loopback when permitted, PipeWire otherwise |

On macOS, a stream containing one is refused when it loads.

## Audio

### `MicrophoneSource`

Captures audio as timestamped blocks of samples. With no audio system at all, it produces silence, so the same stream runs on a server.

| | |
|---|---|
| Outputs | `audio`: blocks that `AudioBlock` reads |

| Setting | Default | |
|---|---|---|
| `device_id` | the system default (on macOS, following changes) | The audio system's device name. On PipeWire, `<sink>.monitor` captures what that output plays. On macOS, a Core Audio device UID. |

A `device_id` that can't be opened is an error, never a quiet switch to another device. On macOS, the first run asks for microphone access.

### `SpeakerSink`

Plays audio blocks. It converts each block to the device's sample rate and period, and mixes channels down to mono or up from mono as needed. With no audio system, it discards its input.

| | |
|---|---|
| Inputs | `audio` (ordered) |

| Setting | Default | |
|---|---|---|
| `device_id` | the system default (on macOS, following changes) | As for `MicrophoneSource` |

Blocks must be `f32` or `i16`. A channel conversion that isn't to or from mono, such as stereo into a five-channel device, is refused.

## Codecs

### `H264Encoder`, `H265Encoder`

Compress video with the GPU's hardware encoder: Vulkan Video on Linux, VideoToolbox on macOS. Each output bag is one compressed frame.

| | |
|---|---|
| Inputs | `video` (ordered) |
| Outputs | `encoded_video` |

| Setting | Default | |
|---|---|---|
| `bitrate_bps` | a fixed quantizer, with no target bitrate | Target bitrate, in bits per second |
| `keyframe_interval_seconds` | `2` | Seconds between keyframes. `0` is refused on macOS. |
| `fps` | `60` | Frame rate to assume when frames don't carry one |
| `width`, `height` | from the first frame | The expected size: a check, not a resize. A frame of another size is encoded as it is, with a warning. |
| `effort_level` | the codec's default | How hard the hardware works. Refused on macOS. |

The encoder starts from the first frame's size, and starts again if the size changes. Without a hardware encoder it logs one error, keeps running, and drops every frame.

### `H264Decoder`, `H265Decoder`

Decompress video with the hardware decoder, back into ordinary frames.

| | |
|---|---|
| Inputs | `encoded_video` (ordered) |
| Outputs | `video` |

| Setting | Default | |
|---|---|---|
| `max_width`, `max_height` | from the stream | The largest coded size to prepare for. Set both or neither. |

The decoder must match the encoder: H.264 with H.264, H.265 with H.265. Without a hardware decoder, it fails when the stream starts.

### `OpusEncoder`

Compresses audio with Opus, in 20 ms packets. It converts its input to Opus's 48 kHz itself, keeping the source's channel count (1 to 8).

| | |
|---|---|
| Inputs | `audio` (ordered) |
| Outputs | `encoded_audio` |

| Setting | Default | |
|---|---|---|
| `application` | `"audio"` | `"audio"` for fidelity, `"voip"` for speech, `"lowdelay"` for the least latency |
| `bitrate_bps` | chosen by Opus | Target bitrate, in bits per second |

### `OpusDecoder`

Decompresses Opus packets into audio blocks: 48 kHz, `f32`, in the packet's channel count. It has no settings. A gap in the packets is logged and left as a gap; nothing is invented to fill it.

| | |
|---|---|
| Inputs | `encoded_audio` (ordered) |
| Outputs | `audio` |

## Recording

### `Mp4Sink`

Records encoded video and audio into one fragmented MP4 file. Every link into `tracks` becomes one track: H.264 or H.265 makes a video track, Opus an audio track. Each track is named after the link's source, `<producer node ID>/<port>`, as `tatolab graph` shows it. See [Record and encode](https://tatolab.com/docs/build/recording).

| | |
|---|---|
| Inputs | `tracks` (ordered), any number of links |

| Setting | Default | |
|---|---|---|
| `path` | required | The file to write. Use a full path: the node runs inside the runtime, not in your project folder. The folder must exist. |

The file is created, or emptied, each time the stream loads, so a kept stream reloaded after a restart starts its file over; see [Recordings and restarts](https://tatolab.com/docs/build/recording#recordings-and-restarts). If the process dies, the file plays up to its last complete fragment. A track that can't continue, such as one whose video parameters change mid-file, stops with a message naming it while the others keep recording.

## Not built in

Nodes from packages are used the same way. `tatolab-webrtc` adds `WhipPublisher` and `WhepPlayer`; see [WebRTC](https://tatolab.com/docs/build/webrtc). Anything else is a [node you write](https://tatolab.com/docs/build/nodes).
