Skip to main content

Streaming a webcam with GStreamer

GStreamer is an easy way to send real video through NanoPing. On the sender, GStreamer captures the webcam and sends it as RTP over UDP to the sender pipeline. On the receiver, NanoPing delivers the stream to a local port, where a second GStreamer command shows it in a window.

The commands below use the addresses of the example pipeline files and the Getting Started guide, which you also enter as Application In and Application Out in the UDP Video Streaming template:

SideGStreamerNanoPing
SenderSends to 127.0.0.1:5000UDP Local listens on 127.0.0.1:5000 (Application In)
ReceiverListens on port 6000Peer Writer delivers to 127.0.0.1:6000 (Application Out)

If your pipelines use other addresses, change the port in the commands to match.

Install GStreamer​

On Linux (Ubuntu 24.04 LTS or newer):

sudo apt-get install gstreamer1.0-tools gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-x v4l-utils

On macOS, with Homebrew:

brew install gstreamer

To find your camera on Linux, run v4l2-ctl --list-devices; the first camera is usually /dev/video0. On macOS, cameras are numbered from device-index=0.

Start the receiver​

Start this first, on the receiver. It works on Linux and macOS:

gst-launch-1.0 udpsrc port=6000 \
caps="application/x-rtp, media=video, encoding-name=H264, payload=96" ! \
rtph264depay ! \
avdec_h264 ! \
videoconvert ! \
textoverlay text="NanoPing" valignment=top halignment=center font-desc="Sans, 36" ! \
autovideosink

It waits for video and opens a window, labelled NanoPing, when the first frames arrive.

To record to a file instead of showing a window, use this. The -e flag finishes the file cleanly when you press Ctrl+C:

gst-launch-1.0 -e udpsrc port=6000 \
caps="application/x-rtp, media=video, encoding-name=H264, payload=96" ! \
rtph264depay ! \
h264parse ! \
mp4mux ! \
filesink location=received.mp4

Start the sender​

Test pattern. Check the path first with GStreamer's built-in test pattern, no camera needed:

gst-launch-1.0 videotestsrc is-live=true pattern=ball ! \
video/x-raw,width=640,height=480 ! \
videoconvert ! \
x264enc tune=zerolatency ! \
rtph264pay config-interval=1 pt=96 ! \
multiudpsink clients=127.0.0.1:5000

Webcam on Linux:

gst-launch-1.0 v4l2src device=/dev/video0 ! \
video/x-raw,width=640,height=480 ! \
videoconvert ! \
x264enc tune=zerolatency ! \
rtph264pay config-interval=1 pt=96 ! \
multiudpsink clients=127.0.0.1:5000

Webcam on macOS:

gst-launch-1.0 avfvideosrc device-index=0 ! \
queue ! \
video/x-raw,width=640,height=480,framerate=30/1 ! \
videoconvert ! \
x264enc speed-preset=ultrafast tune=zerolatency intra-refresh=true ! \
h264parse ! \
rtph264pay config-interval=1 pt=96 mtu=1100 ! \
multiudpsink clients=127.0.0.1:5000

Webcam with built-in H.264 (Linux). Many USB webcams can encode H.264 themselves, so you can skip the encoder:

gst-launch-1.0 v4l2src device=/dev/video0 do-timestamp=true ! \
video/x-h264,width=1920,height=1080,framerate=30/1 ! \
h264parse config-interval=-1 ! \
rtph264pay config-interval=1 pt=96 mtu=1300 ! \
multiudpsink clients=127.0.0.1:5000 sync=false

What the main settings do:

  • tune=zerolatency turns off encoder features that add delay, such as look-ahead and B-frames.
  • config-interval=1 repeats the stream headers every second, so the receiver can join at any time.
  • On macOS, speed-preset=ultrafast encodes faster, intra-refresh=true spreads keyframe data over many frames so a lost packet does not freeze the picture, and mtu=1100 sends smaller packets.

To send to several destinations at once, list them in multiudpsink clients=, separated by commas. The demo pipelines use this to send the same video through NanoPing and as plain UDP.

Over a VPN tunnel​

If NanoPing runs as a VPN with a TUN interface, send straight to the receiver's TUN IP address instead of 127.0.0.1, and use the same port on the receiver.

Lifecycle handler configuration​

The example pipeline files often start GStreamer for you, through the pipeline's lifecycle_handler. The commands in its pre_start list run in the background when the pipeline starts. Open the pipeline's Code tab to edit them, and check two things.

1. The GStreamer path​

Each cmd starts with the full path to gst-launch-1.0. Change it to match your system:

SystemPath
macOS (Apple Silicon)/opt/homebrew/bin/gst-launch-1.0
Linux (Ubuntu/Debian)/usr/bin/gst-launch-1.0

Avoid version-specific Homebrew paths such as /opt/homebrew/Cellar/gstreamer/<version>/bin/…. They stop working when Homebrew updates GStreamer.

2. The camera source​

The command that captures the camera starts with a camera source, which depends on the system:

SystemCamera source
macOSavfvideosrc device-index=0
Linuxv4l2src device=/dev/video0

Example: Lifecycle Handler JSON​

Below is the section of the JSON you need to edit. Note the cmd fields where the path and source are defined:

"lifecycle_handler": {
"pre_start": [
{
"comment": "Video Source (Camera) -> Sender",
"cmd": "/PATH/TO/gst-launch-1.0 avfvideosrc device-index=0 ! video/x-raw,width=640,height=480 ! videoconvert ! x264enc tune=zerolatency ! rtph264pay config-interval=1 pt=96 ! multiudpsink clients=127.0.0.1:5000,127.0.0.1:5001",
"policy": "RUN_IN_BACKGROUND"
},
]
}

In JSON, each command is a single line, and quotes inside it must be escaped as \".