--- myst: html_meta: description: "Stream live video from cameras, files, networks or your own code to a video channel." --- # Live video streaming {.lead} {py:class}`~nominal.experimental.video.VideoStream` sends live video to a video channel on a dataset, from a camera, a file, a network stream or your own code. Each frame carries its capture time, so the video plays in sync with telemetry in [workbooks](https://docs.nominal.io/core/documentation/platform/workbooks/live-video-streaming). The examples also need the [`video` extra](/guides/install.md#extras). Live video is experimental, so the API may change between releases. ```{include} /guides/_snippets/connect-the-client.md ``` ## Install GStreamer The `video` extra also needs [GStreamer](https://gstreamer.freedesktop.org/) 1.20 or later: ::::{tab-set} :::{tab-item} macOS ```shell brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly libnice-gstreamer ``` ::: :::{tab-item} Ubuntu / Debian ```shell sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-nice libssl3 ``` ::: :::{tab-item} Windows Install the runtime from the [GStreamer downloads page](https://gstreamer.freedesktop.org/download/#windows). ::: :::: ## Stream a camera ```python from nominal.experimental.video import Src, VideoStream dataset = client.create_dataset("Live camera") with VideoStream.create(dataset, Src.camera(), channel="camera") as stream: stream.run(30) # seconds ``` Video goes to Nominal over WebRTC, using the WHIP protocol. ## Sources Raw sources are encoded with the stream's options. Encoded sources pass through as they are. | Source | Type | Input | | --- | --- | --- | | `Src.camera(device_index=0)` | Raw | A local camera | | `Src.display(monitor_index=0)` | Raw | The screen | | `Src.file(location)` | Raw | A video file, such as MP4 or MKV | | `Src.images(path, fps)` | Raw | A folder of PNG or JPEG images | | `Src.test()` | Raw | A test pattern | | `Src.app(width, height, format="RGB")` | Raw | Pixel frames from your code | | `Src.app_image(format)` | Raw | JPEG or PNG bytes from your code | | `Src.app_encoded(codec=Codec.H264)` | Encoded | H.264 or H.265 frames from your code | | `Src.rtsp(url)` | Encoded | An RTSP stream | | `Src.udp_rtp(port)`, `Src.udp_mpegts(port)` | Encoded | RTP or MPEG-TS over UDP | | `Src.custom(description)` | Encoded | A GStreamer bin that ends with a parser such as `h264parse` | ## Send frames from your code `Src.app` takes raw pixels. With `auto_timestamp=False`, give each frame its capture time in epoch nanoseconds: ```python import time with VideoStream.create(dataset, Src.app(640, 480, format="RGB", auto_timestamp=False), channel="custom") as stream: stream.send_frame(frame_bytes, timestamp_ns=time.time_ns()) # 640 × 480 × 3 bytes ``` By default, `auto_timestamp=True` stamps frames with the time they're sent. ## Stream options `StreamOptions` sets encoding and reconnection: ```python from nominal.experimental.video import Codec, ReconnectOptions, StreamOptions opts = StreamOptions( codec=Codec.H264, # or Codec.H265 bitrate=4000, # kbps resolution="1280x720", fps=30, keyframe_interval=30, # frames overlay=False, # draw a timestamp on the video reconnect=ReconnectOptions(retry_delay_s=2.0, max_retries=None), # None retries forever ) with VideoStream.create(dataset, Src.camera(), opts, channel="camera") as stream: stream.run(30) ``` `bitrate`, `resolution`, `fps`, `overlay`, `flip` and `crop` apply to raw sources. For encoded sources, set `transcode=True` to decode and encode them again so the options apply. `congestion_control`, on by default, lowers the bitrate when packets are lost. ## View the stream To watch the stream in a workbook, see [Live video in workbooks](https://docs.nominal.io/core/documentation/platform/workbooks/live-video-streaming).