Examples
What each example in the streamlib repository shows, what it needs, and how its stream is wired with the current API.
The streamlib repository has a dozen examples, each a small project that does one thing well: a codec round trip, a recorder, a GPU halftone, an object detector, a reverb.
Camera and display
Section titled “Camera and display”camera-display
Section titled “camera-display”Source. Your camera in a window: the smallest useful stream.
CameraSource → DisplayWindowfrom tatolab.stream import CameraSource, DisplayWindow, StreamBuilder, stream
@streamdef camera_display(stream_builder: StreamBuilder) -> None: """The camera in a window.""" camera = stream_builder.add(CameraSource, config={"max_width": 1920, "max_height": 1080}) window = stream_builder.add( DisplayWindow, config={"title": "Camera", "width": 1280, "height": 720, "scaling": "fit"} ) stream_builder.connect(camera.output("video"), window.input("video"))Needs: a camera. Both nodes are built in, so the project needs nothing but tatolab-stream.
camera-codec-roundtrip
Section titled “camera-codec-roundtrip”Source. The camera after a real hardware encode and decode, so what you see has been through an H.264 (or H.265) bitstream.
CameraSource → H264Encoder → H264Decoder → DisplayWindowfrom tatolab.stream import ( CameraSource, DisplayWindow, H264Decoder, H264Encoder, StreamBuilder, stream,)
@streamdef codec_roundtrip(stream_builder: StreamBuilder) -> None: """The camera, encoded to H.264 and decoded again, in a window.""" camera = stream_builder.add(CameraSource) encoder = stream_builder.add(H264Encoder) decoder = stream_builder.add(H264Decoder) window = stream_builder.add(DisplayWindow, config={"title": "After the round trip"}) stream_builder.connect(camera.output("video"), encoder.input("video")) stream_builder.connect(encoder.output("encoded_video"), decoder.input("encoded_video")) stream_builder.connect(decoder.output("video"), window.input("video"))Swap in H265Encoder and H265Decoder and nothing else changes; the two ends must match. Shows: an encoded stream is an ordinary link. Needs: a GPU with hardware encode and decode: Vulkan Video on Linux, VideoToolbox on a Mac. Without a decoder, the decoder node fails in setup() and the window stays black; without an encoder, the encoder logs one error and drops every frame. See Record and encode.
camera-compute-kernel and camera-halftone
Section titled “camera-compute-kernel and camera-halftone”camera-compute-kernel turns the camera black and white with a compute kernel; camera-halftone turns it into a newsprint dot screen.
CameraSource → GrayscaleCompute → DisplayWindowCameraSource → HalftoneCompute → DisplayWindowShows: writing a full compute kernel when a one-function pixel effect isn’t enough: compiling GLSL in setup() with ctx.gpu_full_access.create_compute_kernel(...), dispatching it in process(), push constants for live settings (strength, cell_size, dot_boost), and the halftone’s gather pattern, where each pixel reads neighbors it doesn’t write. Start with GPU effects; these examples are the next step.
camera-python-effects
Section titled “camera-python-effects”Source. A “breaking news” look: glitch and CRT film-grain shaders over the camera, a neon overlay drawn with Skia, and a 3D avatar that follows your pose, all composited into one window.
CameraSource ─┬─ CameraFrameToTexture → CyberpunkGlitch → CrtFilmGrain ─┐ └─ CyberpunkAvatar (MediaPipe pose, ModernGL) ─────────────┤NeonOverlaySource (Skia, every 33 ms) ───────────────────────────────────┴─ BreakingNewsCompositor → DisplayWindowShows: fan-out and fan-in, graphics (fragment) shaders loaded from .frag files, a node that composites three inputs at the video’s pace, a source that draws on its own timer (execution="continuous", interval_ms=33), pose estimation on the CPU over numpy.from_dlpack(frame, device="cpu"), and an optional recorder that writes LeRobot dataset episodes. Needs: skia-python, mediapipe and moderngl from PyPI; runs on Linux with NVIDIA and on Apple silicon.
camera-virtual-camera
Section titled “camera-virtual-camera”Source. Two virtual cameras that any other app can open, one plain and one inverted. Linux only.
CameraSource ─┬─ VirtualCameraSink ("StreamLib Camera") └─ InvertingEffect → VirtualCameraSink ("StreamLib Camera Inverted")Shows: presenting a stream as a camera for Zoom, OBS or a browser, and why an effect that sits next to other readers of the same frame must write its own output frame rather than editing the shared one. See Virtual camera.
Recording and publishing
Section titled “Recording and publishing”camera-audio-recorder
Section titled “camera-audio-recorder”Source. The camera and the microphone recorded into one MP4 file, with a live preview.
CameraSource ─┬─ H264Encoder ─┐ └─ DisplayWindow ├─ Mp4Sink (recording.mp4)MicrophoneSource → OpusEncoder ┘from pathlib import Path
from tatolab.stream import ( CameraSource, DisplayWindow, H264Encoder, MicrophoneSource, Mp4Sink, OpusEncoder, StreamBuilder, stream,)
RECORDING = Path(__file__).resolve().parent / "recording.mp4"
@streamdef recorder(stream_builder: StreamBuilder) -> None: """Camera and microphone into one MP4, with a preview window.""" camera = stream_builder.add(CameraSource) video_encoder = stream_builder.add(H264Encoder) microphone = stream_builder.add(MicrophoneSource) audio_encoder = stream_builder.add(OpusEncoder) recording = stream_builder.add(Mp4Sink, config={"path": str(RECORDING)}) preview = stream_builder.add(DisplayWindow, config={"title": "Recording"})
stream_builder.connect(camera.output("video"), video_encoder.input("video")) stream_builder.connect(camera.output("video"), preview.input("video")) stream_builder.connect(video_encoder.output("encoded_video"), recording.input("tracks")) stream_builder.connect(microphone.output("audio"), audio_encoder.input("audio")) stream_builder.connect(audio_encoder.output("encoded_audio"), recording.input("tracks"))Shows: each link into Mp4Sink’s tracks input becomes one track, with its kind (video or audio) taken from what arrives. OpusEncoder converts the microphone’s sample rate and framing for you. Needs: a camera, a microphone, and hardware H.264 encode. See Record and encode.
camera-webrtc-publish
Section titled “camera-webrtc-publish”Source. The camera and microphone published to a WHIP endpoint, such as Cloudflare Stream, as one WebRTC session.
CameraSource → H264Encoder ──┐MicrophoneSource → OpusEncoder ┴─ WhipPublisherShows: a node from a package (WhipPublisher, from tatolab-webrtc) used exactly like your own, and treating a URL that contains a stream key as a secret. See WebRTC for the current-API stream.
Models
Section titled “Models”fisheye-object-detection
Section titled “fisheye-object-detection”Source. The camera through a simulated fisheye lens in one window; in a second, the same picture straightened out, with YOLO’s boxes drawn on it.
CameraSource → SyntheticFisheyeLens ─┬─ DisplayWindow ("LensWindow") └─ UndistortingObjectDetector → DisplayWindow ("DetectionWindow")Shows: a full model-in-the-loop node: a compute kernel that undistorts the frame, ModelInputTensorKernel preparing the model’s input on the GPU, torch.from_dlpack handing it to YOLO with no copy, boxes mapped back to frame coordinates, and detections written into the output bag beside the frame. Needs: torch>=2.10 and ultralytics, and a GPU torch can use (CUDA on Linux, mps on a Mac). See Use your model.
microphone-reverb-speaker
Section titled “microphone-reverb-speaker”Source. Your voice through a room reverb, played back live. Wear headphones, or the speaker feeds the microphone.
MicrophoneSource → ReverbEffect → SpeakerSinkShows: an audio window contract (AudioWindowContract): the node asks for exactly 128 mono samples at 48 kHz and the runtime converts whatever the microphone opened at. Needs: numpy; a microphone and speakers (it runs silently with no audio devices).
audio-mixer-demo
Section titled “audio-mixer-demo”Source. A steady C major chord from three tone generators, mixed and played.
ToneSource (C4) ─┐ToneSource (E4) ─┼─ ChordMixer → SpeakerSinkToneSource (G4) ─┘Shows: three sources at three different sample rates and channel counts, converted to one window contract by the runtime, and a mixer that lines blocks up by their timestamps instead of by arrival order. See Audio.
GPU showcase
Section titled “GPU showcase”raytracing-showcase
Section titled “raytracing-showcase”Source. A split screen: the same scene rasterized on the left (“RTX OFF”) and ray traced on the right (“RTX ON”).
RasterizedSceneRenderer ─┐RayTracedSceneRenderer ──┴─ SplitScreenCompositor → DisplayWindowShows: graphics, ray-tracing and compute kernels in one stream, and two renderers kept in step by the shared clock (ctx.time). Needs: a GPU with hardware ray tracing (NVIDIA RTX, or AMD RDNA 2 and later) on Linux. Ray tracing isn’t available on macOS, and the renderer says so at setup().
Not yet examples
Section titled “Not yet examples”jpeg-psnr and screen-recorder are Rust projects kept for reference while their features are redesigned. They aren’t meant to be run.