Terminal showing gst-launch-1.0 pipeline running on Jetson Orin with camera capture and hardware encode
jetsongstreamernvarguscamerasrcnvvidconvv4l2srcpipelinecamera

GStreamer pipeline examples for Jetson: nvarguscamerasrc, nvvidconv, encode, and decode

Aaron Angulo · · Updated

GStreamer on Jetson uses NVIDIA-specific elements for hardware-accelerated camera capture, format conversion, and encode/decode. These elements, nvarguscamerasrc, nvvidconv, nvv4l2h264enc, nvv4l2decoder, have different properties and caps requirements than their generic Linux equivalents. This post is a practical reference for the GStreamer pipelines that actually work on Jetson Orin, with copy-paste commands for the most common use cases.

Key Insights

  • NVMM caps (video/x-raw(memory:NVMM)) keep frames in GPU memory between NVIDIA elements, always use them between nvarguscamerasrc, nvvidconv, and nvv4l2h264enc
  • nvarguscamerasrc is for ISP-processed camera capture; v4l2src is for raw sensor frames
  • kmssink works without X or Wayland, use it for headless display output on embedded setups
  • nvv4l2h264enc and nvv4l2decoder use Jetson’s hardware encode/decode engines; avenc_h264 and avdec_h264 are software fallbacks
  • gst-inspect-1.0 and GST_DEBUG are your primary debugging tools, inspect before troubleshooting

Prerequisites

Install NVIDIA’s GStreamer plugins:

sudo apt install nvidia-l4t-gstreamer gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-tools

Verify NVIDIA elements are available:

gst-inspect-1.0 nvarguscamerasrc
gst-inspect-1.0 nvvidconv
gst-inspect-1.0 nvv4l2h264enc
gst-inspect-1.0 nvv4l2decoder

If any of these fail, the nvidia-l4t-gstreamer package wasn’t installed or its plugins didn’t load. Check gst-inspect-1.0 --gst-plugin-path /usr/lib/aarch64-linux-gnu/gstreamer-1.0/.

Camera capture pipelines

Basic display test:

gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvvidconv ! 'video/x-raw,format=BGRx' ! \
  videoconvert ! autovideosink

Headless, capture to file:

gst-launch-1.0 nvarguscamerasrc sensor-id=0 num-buffers=300 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvv4l2h264enc ! h264parse ! qtmux ! \
  filesink location=/tmp/capture.mp4

Get ISP-processed frames as NV12 for OpenCV or inference:

gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1280,height=720,framerate=30/1' ! \
  nvvidconv ! 'video/x-raw,format=NV12' ! \
  appsink name=sink max-buffers=1 drop=true

Check available sensor IDs:

gst-launch-1.0 nvarguscamerasrc ! fakesink -v 2>&1 | grep "Available Sensor"

v4l2src (raw V4L2 path)

Basic display (format depends on sensor):

gst-launch-1.0 v4l2src device=/dev/video0 ! \
  'video/x-raw,format=YUY2,width=1920,height=1080,framerate=30/1' ! \
  videoconvert ! autovideosink

Display via KMS/DRM without X server:

gst-launch-1.0 v4l2src device=/dev/video0 ! \
  'video/x-raw,format=YUY2,width=1920,height=1080,framerate=30/1' ! \
  videoconvert ! kmssink

Check what formats your camera supports:

v4l2-ctl --list-formats-ext -d /dev/video0

Raw capture to file (useful for debugging sensor output):

gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=30 ! \
  'video/x-raw,format=RG10,width=1920,height=1080' ! \
  filesink location=/tmp/raw_frames.bin

Hardware encode

H.264 encode

Camera → H.264 file:

gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvv4l2h264enc bitrate=8000000 ! h264parse ! \
  qtmux ! filesink location=output.mp4

Camera → H.264 UDP stream (for remote viewing):

# Sender (Jetson)
gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvv4l2h264enc ! h264parse ! \
  rtph264pay ! udpsink host=192.168.1.100 port=5000

# Receiver (laptop)
gst-launch-1.0 udpsrc port=5000 ! \
  'application/x-rtp,encoding-name=H264,payload=96' ! \
  rtph264depay ! avdec_h264 ! autovideosink

H.265 encode

H.265 typically achieves the same quality at half the bitrate:

gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvv4l2h265enc bitrate=4000000 ! h265parse ! \
  qtmux ! filesink location=output.mp4

nvv4l2h264enc key properties:

  • bitrate, target bitrate in bps (default: 4000000)
  • maxperf-enable, set to true for maximum throughput at higher power
  • preset-level, 1 (ultra-fast) to 4 (best quality), default 3

Hardware decode

MP4 file → display:

gst-launch-1.0 filesrc location=video.mp4 ! \
  qtdemux ! h264parse ! nvv4l2decoder ! \
  nvvidconv ! 'video/x-raw,format=BGRx' ! \
  videoconvert ! autovideosink

H.265 decode:

gst-launch-1.0 filesrc location=video.mp4 ! \
  qtdemux ! h265parse ! nvv4l2decoder ! \
  nvvidconv ! autovideosink

Note: nvv4l2decoder outputs NVMM-format frames. Connect directly to nvvidconv (not videoconvert) to stay in the hardware path.

nvvidconv, format conversion and scaling

nvvidconv is Jetson’s hardware video converter. Use it between NVIDIA elements for format conversion and scaling without CPU involvement.

Convert YUY2 → BGRx:

gst-launch-1.0 v4l2src device=/dev/video0 ! \
  'video/x-raw,format=YUY2,width=1920,height=1080' ! \
  nvvidconv ! 'video/x-raw,format=BGRx,width=1920,height=1080' ! \
  videoconvert ! autovideosink

Scale and convert in one step:

gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvvidconv ! 'video/x-raw(memory:NVMM),width=640,height=360' ! \
  nvv4l2h264enc ! h264parse ! qtmux ! filesink location=scaled.mp4

Important: When connecting two NVIDIA elements, add (memory:NVMM) to the caps between them:

# Correct — stays in GPU memory
! nvvidconv ! 'video/x-raw(memory:NVMM),format=NV12' ! nvv4l2h264enc

# Wrong — forces CPU copy
! nvvidconv ! 'video/x-raw,format=NV12' ! nvv4l2h264enc

Multi-camera pipelines

View two cameras side by side:

gst-launch-1.0 \
  nvarguscamerasrc sensor-id=0 name=cam0 ! \
    'video/x-raw(memory:NVMM),width=1280,height=720,framerate=30/1' ! \
    nvvidconv ! 'video/x-raw,format=BGRx,width=640,height=360' ! videoconvert ! \
    compositor name=mix sink_0::xpos=0 sink_0::ypos=0 sink_1::xpos=640 sink_1::ypos=0 ! \
    autovideosink \
  nvarguscamerasrc sensor-id=1 ! \
    'video/x-raw(memory:NVMM),width=1280,height=720,framerate=30/1' ! \
    nvvidconv ! 'video/x-raw,format=BGRx,width=640,height=360' ! videoconvert ! \
    mix.

Record both cameras independently:

gst-launch-1.0 \
  nvarguscamerasrc sensor-id=0 ! \
    'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
    nvv4l2h264enc ! h264parse ! qtmux ! filesink location=cam0.mp4 \
  nvarguscamerasrc sensor-id=1 ! \
    'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
    nvv4l2h264enc ! h264parse ! qtmux ! filesink location=cam1.mp4

RTSP and network streaming

UDP streaming (lowest latency)

Send a camera stream over UDP from Jetson to a remote host:

# Jetson (sender)
gst-launch-1.0 \
  nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvv4l2h264enc bitrate=4000000 iframeinterval=30 ! \
  h264parse ! rtph264pay ! \
  udpsink host=192.168.1.100 port=5000 sync=false

# Remote host (receiver)
gst-launch-1.0 \
  udpsrc port=5000 ! \
  'application/x-rtp,encoding-name=H264,payload=96' ! \
  rtph264depay ! avdec_h264 ! autovideosink

Record locally while streaming

Use tee to split the pipeline — one branch to file, one branch to network:

gst-launch-1.0 \
  nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvv4l2h264enc bitrate=4000000 ! h264parse ! \
  tee name=t \
    t. ! queue ! rtph264pay ! udpsink host=192.168.1.100 port=5000 sync=false \
    t. ! queue ! qtmux ! filesink location=/tmp/recording.mp4

H.265 for lower bandwidth

H.265 (HEVC) achieves similar quality at roughly half the bitrate:

gst-launch-1.0 \
  nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! \
  nvv4l2h265enc bitrate=2000000 ! h265parse ! rtph265pay ! \
  udpsink host=192.168.1.100 port=5001 sync=false

Key streaming properties:

  • sync=false on udpsink — removes presentation timestamp delay
  • iframeinterval=30 — keyframe every second; lower values improve recovery from packet loss
  • max-size-buffers=1 leaky=2 on queue before udpsink — drops old frames on network congestion instead of building a backlog

Python appsink: getting frames into OpenCV

This pattern pulls GStreamer frames into Python as NumPy arrays for OpenCV processing on Jetson:

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
import numpy as np
import cv2

Gst.init(None)

pipeline = Gst.parse_launch(
    'nvarguscamerasrc sensor-id=0 ! '
    'video/x-raw(memory:NVMM),width=1280,height=720,framerate=30/1 ! '
    'nvvidconv ! '
    'video/x-raw,format=BGRx,width=1280,height=720 ! '
    'appsink name=sink max-buffers=1 drop=true emit-signals=true'
)

appsink = pipeline.get_by_name('sink')

def on_new_sample(sink):
    sample = sink.emit('pull-sample')
    if not sample:
        return Gst.FlowReturn.ERROR

    buf = sample.get_buffer()
    caps = sample.get_caps()
    w = caps.get_structure(0).get_int('width').value
    h = caps.get_structure(0).get_int('height').value

    success, map_info = buf.map(Gst.MapFlags.READ)
    if not success:
        return Gst.FlowReturn.ERROR

    frame = np.frombuffer(map_info.data, dtype=np.uint8).reshape(h, w, 4)
    frame_bgr = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)
    buf.unmap(map_info)

    # Your processing here
    cv2.imshow('Jetson Camera', frame_bgr)
    cv2.waitKey(1)

    return Gst.FlowReturn.OK

appsink.connect('new-sample', on_new_sample)
pipeline.set_state(Gst.State.PLAYING)

loop = GLib.MainLoop()
loop.run()

The format=BGRx cap on nvvidconv outputs 4-channel BGRA which reshapes cleanly into a NumPy array. The final cvtColor strips the alpha channel. Using max-buffers=1 drop=true on appsink ensures the pipeline never backs up waiting for your callback — old frames are dropped if processing is slow.

Do not call cv2.imshow inside the appsink callback in production. The callback runs on a GStreamer streaming thread. Move heavy processing to a separate thread using a queue.Queue and pass frames across.

Auto-reconnect on source failure

For production pipelines where the camera or network source may disconnect, listen on the pipeline bus and restart on error or EOS:

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
import time

Gst.init(None)

PIPELINE_DESC = (
    'udpsrc port=5000 ! '
    'application/x-rtp,encoding-name=H264,payload=96 ! '
    'rtph264depay ! avdec_h264 ! autovideosink'
)

def start_pipeline():
    pipeline = Gst.parse_launch(PIPELINE_DESC)
    pipeline.set_state(Gst.State.PLAYING)
    return pipeline

def on_message(bus, message, pipeline_ref):
    if message.type in (Gst.MessageType.ERROR, Gst.MessageType.EOS):
        pipeline_ref[0].set_state(Gst.State.NULL)
        time.sleep(2)  # backoff
        pipeline_ref[0] = start_pipeline()
        pipeline_ref[0].get_bus().add_signal_watch()
        pipeline_ref[0].get_bus().connect('message', on_message, pipeline_ref)
    return True

pipeline_ref = [start_pipeline()]
bus = pipeline_ref[0].get_bus()
bus.add_signal_watch()
bus.connect('message', on_message, pipeline_ref)

loop = GLib.MainLoop()
loop.run()

The pipeline reference is wrapped in a list so the callback can replace it. On any error or EOS, the pipeline is torn down and a fresh one is started after a 2-second backoff. For production, use exponential backoff with a cap rather than a fixed delay.

Debugging pipelines

Inspect element properties:

gst-inspect-1.0 nvarguscamerasrc
gst-inspect-1.0 nvv4l2h264enc

Verbose caps negotiation:

gst-launch-1.0 -v nvarguscamerasrc ! fakesink 2>&1 | head -50

Enable debug logging:

# Log all elements at level 3
GST_DEBUG=3 gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! fakesink

# Log specific elements
GST_DEBUG=nvarguscamerasrc:5,nvvidconv:4 gst-launch-1.0 ...

Check pipeline state manually:

# Add -v to see state changes
gst-launch-1.0 -v nvarguscamerasrc sensor-id=0 ! \
  'video/x-raw(memory:NVMM)' ! nvvidconv ! autovideosink

Common pipeline errors:

ErrorCauseFix
”No such element: nvarguscamerasrc”nvidia-l4t-gstreamer not installedsudo apt install nvidia-l4t-gstreamer
”Internal data flow error”Buffer backpressureAdd ! queue after the source
”nvarguscamerasrc: No cameras available”nvargus-daemon not runningsudo systemctl start nvargus-daemon
Pipeline hangs at PLAYINGNVMM path brokenCheck caps have (memory:NVMM) between NVIDIA elements
”Cannot identify /dev/video0”Camera not probedCheck v4l2-ctl --list-devices

For camera driver bring-up that these pipelines depend on, see CSI camera driver on Jetson: V4L2, Argus, and IMX sensor bring-up. For Argus-specific setup including nvargus-daemon, see NVIDIA Argus API camera driver on Jetson. For pipeline performance issues, nvvidconv throughput collapse under multiple processes, see nvvidconv performance with multiple GStreamer processes on Jetson.

FAQ

Why does nvarguscamerasrc say “No such element or plugin”?

The nvidia-l4t-gstreamer package isn’t installed. Install it with: sudo apt install nvidia-l4t-gstreamer. Also verify gst-inspect-1.0 nvarguscamerasrc returns the element description. If it still fails after install, nvargus-daemon isn’t running, start it with sudo systemctl start nvargus-daemon.

What is the difference between v4l2src and nvarguscamerasrc on Jetson?

v4l2src reads raw frames directly from the V4L2 kernel driver, no ISP processing. nvarguscamerasrc routes through the nvargus-daemon and Jetson ISP, providing auto-exposure, auto-white-balance, and hardware noise reduction. Use nvarguscamerasrc for production pipelines that need ISP output; use v4l2src when you need raw Bayer frames or have a sensor that isn’t Argus-compatible.

What does “video/x-raw(memory:NVMM)” mean in a Jetson GStreamer pipeline?

NVMM keeps frame buffers in GPU memory, avoiding a CPU copy between NVIDIA hardware elements. When connecting nvarguscamerasrc to nvvidconv, or nvvidconv to nvv4l2h264enc, always use NVMM caps to maintain the hardware-accelerated path. Without NVMM, frames are copied through system RAM, adding latency and CPU load.

Why does my GStreamer pipeline stall or produce “Internal data flow error”?

Usually buffer backpressure, a downstream element isn’t consuming frames as fast as they arrive. Add ! queue between the source and a slow downstream element. If the pipeline stops after a few seconds, check for missing NVMM caps, this often causes stalls in the format conversion stage.

How do I display a Jetson camera feed without X or Wayland (headless)?

Use kmssink for DRM/KMS direct rendering without a display server: gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! kmssink. Or pipe to a file or network stream instead of a display sink.


NVIDIA Jetson Expert Support

Stuck on a Jetson bring-up?

We've debugged this failure mode before. BSP, device tree, camera pipelines, OTA, most blockers clear in the first session. No long retainers. No guessing.

Frequently Asked Questions

Why does nvarguscamerasrc say 'No such element or plugin'?

The nvidia-l4t-gstreamer package isn't installed. Install it with: sudo apt install nvidia-l4t-gstreamer. Also verify gst-inspect-1.0 nvarguscamerasrc returns the element description. If it still fails, the nvargus-daemon isn't running, start it with sudo systemctl start nvargus-daemon.

What is the difference between v4l2src and nvarguscamerasrc on Jetson?

v4l2src reads raw frames directly from the V4L2 kernel driver, no ISP processing. nvarguscamerasrc routes through the nvargus-daemon and Jetson ISP, providing auto-exposure, auto-white-balance, and hardware noise reduction. Use nvarguscamerasrc for production camera pipelines that need ISP output; use v4l2src when you need raw Bayer frames or have a sensor that isn't Argus-compatible.

What does 'video/x-raw(memory:NVMM)' mean in a Jetson GStreamer pipeline?

NVMM (NVIDIA Memory Management) keeps frame buffers in GPU memory, avoiding a CPU copy between NVIDIA hardware elements. When connecting nvarguscamerasrc to nvvidconv, or nvvidconv to nvv4l2h264enc, always use NVMM caps to maintain the hardware-accelerated path. Without NVMM, frames are copied through system RAM, adding latency and CPU load.

Why does my GStreamer pipeline stall or produce Internal data flow error?

Usually a buffer backpressure problem, a downstream element isn't consuming frames as fast as they arrive. Add '! queue' between the source and a slow downstream element. If the pipeline stops after a few seconds, check for nvvidconv NVMM caps, missing NVMM often causes stalls in the format conversion stage.

How do I display a Jetson camera feed without X or Wayland (headless)?

Use kmssink for DRM/KMS direct rendering without a display server: gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! kmssink. Or pipe to a file or network stream instead of a display sink. nvarguscamerasrc also works headless, route to filesink or udpsink instead of autovideosink.

How do I stream a GStreamer pipeline over RTSP from Jetson?

For simple UDP streaming: on Jetson run gst-launch-1.0 nvarguscamerasrc ! 'video/x-raw(memory:NVMM),width=1920,height=1080,framerate=30/1' ! nvv4l2h264enc ! h264parse ! rtph264pay ! udpsink host=<receiver_ip> port=5000 sync=false. On the receiver: gst-launch-1.0 udpsrc port=5000 ! 'application/x-rtp,encoding-name=H264,payload=96' ! rtph264depay ! avdec_h264 ! autovideosink. For a proper RTSP server with multiple clients, use the gst-rtsp-server library.

How do I get OpenCV frames from a GStreamer pipeline on Jetson in Python?

Use appsink with emit-signals=True and connect to the new-sample signal. Set max-buffers=1 and drop=True so a slow callback doesn't stall the pipeline. In the callback: sample = appsink.emit('pull-sample'), buf = sample.get_buffer(), caps = sample.get_caps(), then use Gst.Buffer.extract_dup to get the bytes and numpy to reshape. The format depends on what nvvidconv outputs — use video/x-raw,format=BGRx for direct OpenCV compatibility.

How do I reconnect a GStreamer pipeline automatically when the source disconnects?

Listen for GST_MESSAGE_ERROR and GST_MESSAGE_EOS on the pipeline bus. On either event, set the pipeline to NULL state, then back to PLAYING. For network sources (udpsrc, rtspsrc), add a watchdog element (from gst-plugins-bad) that fires an error if no buffers arrive within a timeout. For camera sources, the camera disconnect usually triggers an EOS; handle it in the bus callback to restart. Wrap the restart in exponential backoff to avoid a tight loop on persistent errors.

Aarón Angulo, Co-Founder & CEO at ProventusNova

Written by

Aarón Angulo

Co-Founder & CEO · ProventusNova

Obsessed with client outcomes. Aarón ensures every engagement delivers real results, on time, on scope, no exceptions.

Connect on LinkedIn