nvsiplsrc: Hands-On Guide for the GStreamer SIPL Camera Source#
nvsiplsrc is a multi-pad GStreamer source element that captures ISP-processed
frames from Sensor Input Processing Library (SIPL) cameras and delivers them
as NVMM (video/x-raw(memory:NVMM), NV12) buffers to a standard GStreamer
pipeline. It supports both Gigabit Multimedia Serial Link (GMSL) and
Camera-over-Ethernet (CoE), as a camera system through a single unified code path.
The element creates one source pad per discovered sensor (src_0, src_1,
and so on), so a multi-sensor module (for example, a HAWK module with two AR0234 sensors)
fans out to multiple independently timestamped and independently pushed pads that
can be branched to preview, encode, or file sinks.
Motivation#
SIPL delivers ISP output in NvSci buffers (NvSciBufObj), whereas the rest
of the NVIDIA multimedia stack—nvvidconv, nvv4l2h264enc, nv3dsink, and
other NVMM elements—operates on NvBufSurface (NvRM) memory. The central
job of nvsiplsrc is therefore the NvSci-to-NvBufSurface interop: exposing
SIPL camera output as standard NVMM buffers so that it can be consumed directly by the
GStreamer multimedia pipeline. Understanding this interop (refer to
Buffer and Interop API (NvSci ↔ NvBufSurface)) is key to integrating the source with
downstream components.
Goals#
This guide is intended to make you productive quickly. It covers the following topics:
Installing and verifying the plug-in.
Understanding the runtime architecture and the NvSci-to-NvBufSurface buffer/interop model.
Running ready-to-use pipelines for common use cases (preview, YUV capture, and video encode).
Prerequisites and Build#
At a high level, the setup is four steps:
Install GStreamer-1.0 and its development packages.
Install the accelerated (NVIDIA L4T) GStreamer plug-ins.
Build and install the
nvsiplsrcplug-in.Verify the plug-in is registered.
Each step is detailed in the following sections.
Install GStreamer-1.0#
Install the base GStreamer tooling and development headers on the target (refer to Accelerated GStreamer):
sudo apt-get update
sudo apt-get install gstreamer1.0-tools gstreamer1.0-alsa \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
gstreamer1.0-libav
sudo apt-get install libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-good1.0-dev \
libgstreamer-plugins-bad1.0-dev
Install the Accelerated GStreamer Plug-ins#
sudo apt-get update
sudo apt-get install nvidia-l4t-gstreamer
sudo ldconfig
rm -rf ~/.cache/gstreamer-1.0/
Build and Install nvsiplsrc#
cd gst-nvsiplcamera
make
sudo make install # installs libgstnvsiplsrc.so to the GStreamer plug-in dir
# or: DEST_DIR=<dir> make install
The plug-in installs to /usr/lib/aarch64-linux-gnu/gstreamer-1.0.
Verify Plug-in Registration#
gst-inspect-1.0 nvsiplsrc
Note
If gst-inspect-1.0 cannot find the plug-in, clear the registry cache
(rm -rf ~/.cache/gstreamer-1.0) and retry.
Element Properties#
Property |
Type |
Default |
Description |
|---|---|---|---|
|
string |
|
Required. Platform config name passed to |
|
string |
|
Required for GMSL. Hex link-enable mask selecting active deserializer
links (such as |
|
string |
|
Optional path to a CoE/GMSL JSON config. Not needed when the config is in the SIPL database. |
|
Boolean |
|
|
|
uint (4–32) |
|
Number of NVMM buffers allocated in the per-pad output pool. |
|
int |
|
Frames to push per pad before sending EOS ( |
Discover the sensors and pads dynamically:
GST_DEBUG=nvsiplsrc:4 gst-launch-1.0 nvsiplsrc config-name=<CFG> link-mask=<MASK> ! fakesink
# Look for "Creating pad src_<i> for sensor ... : WxH @ fps"
Architecture and Call Flow#
High-Level Design#
Multi-pad
GstElement(notGstBaseSrc). Lifecycle is driven byGstElement::change_state.SIPL is a singleton: A single
Init()/Start()/Stop()covers all sensors.Per-sensor isolation: Each pad owns its own NVMM buffer pool, a producer–consumer buffer queue, and a dedicated push thread. There is no cross-sensor mutable state.
Copy-and-release: Each ISP0 frame is VIC-transformed from the SIPL surface into a GStreamer-owned NVMM buffer. The SIPL buffer is released immediately afterward.
The following diagram shows how the single SIPL instance fans out per-sensor ISP0 frames into fully independent per-pad pipelines inside the element:
Per-Frame Data Flow (Steady State)#
Buffer and Interop API (NvSci ↔ NvBufSurface)#
This is the most important part to understand for anyone extending the element. There are two distinct buffer domains, a zero-copy interop bridge between the domains, and one explicit copy per frame.
The Two Buffer Domains#
Domain |
Producer |
Type |
Purpose |
|---|---|---|---|
SIPL / NvSci |
SIPL ISP0 |
|
ISP writes processed frames here. |
GStreamer / NvRM |
|
|
The NVMM buffers pushed downstream. |
The SIPL ISP0 output is allocated as an NvSciBufObj. The downstream GStreamer
elements (nvvidconv, nvv4l2h264enc, nv3dsink, …) understand only
NvBufSurface-backed NVMM memory. The bridge between them is the NvSci-to-NvRM
interop.
Interop Bridge: NvmmNvSciBufToNvBufSurface#
The core interop call is as follows:
int NvmmNvSciBufToNvBufSurface(NvSciBufObj bufObj, NvBufSurface **nvbuf_surf);
It wraps an existing NvSciBufObj in an NvBufSurface view that shares the
same underlying memory—no pixel copy. For this to be valid, the NvSciBufObj
must have been allocated with attributes that satisfy both SIPL and
NvBufSurface. That reconciliation is the key setup step.
Attribute Reconciliation (Setup, Once Per Sensor)#
SetupBufferAttributes() builds a single reconciled attribute list from two
sources and allocates buffers against it:
SetNvSciBufAttributes(bl_output) SetNvBufNvSciBufAttributes(&nvbufAttr, sipl)
(YUV420, 8bpc, REC709_ER, BL/PL, (reads SIPL surf keys, maps NvSci→NvBuf
CPU access + cache) color format, NvmmImageFillNvSciBufAttrList)
│ │
▼ ▼
GetImageAttributes(sensorId, ISP0) ──► siplAttrList nvbufAttrList
│ │
└──────┬───────┘
│
▼
NvSciBufAttrListReconcile()
│
▼
sp.bufAttrList (reconciled)
│
▼
NvSciBufObjAlloc(sp.bufAttrList, &bufObj) [x N buffers]
│
▼
NvmmNvSciBufToNvBufSurface(bufObj, &nvbuf_surf)
│
▼
interop buffer = { NvSciBufObj, NvBufSurface, dmabuf fd }
Key helper functions (in NvNvSciBufHelper.cpp):
SetNvSciBufAttributes(&attrList, bl_output): Sets the SIPL-side image attributes—NvSciBufType_Image, ReadWrite, CPU access + cache, YUV, semi-planar 420, 8 BPC,REC709_ER, and layout (block-linear whenbl-output=true; otherwise, pitch-linear).SetNvBufNvSciBufAttributes(&attrList, coeAttrList): Reads the SIPL surface keys back (width/height base, sample type, color std, memory layout, BPC, layout), maps the NvSci color info to anNvBufSurfaceColorFormat(MapNvSciToNvBufColorFormat, currentlyNV12_709_ER/YUV420_709_ER), and then callsNvmmImageFillNvSciBufAttrList()to add NvBufSurface-required attributes.Reconciling both lists guarantees a single allocation is simultaneously a valid SIPL ISP0 target and a valid
NvBufSurface.
The registration side then makes SIPL write into the following buffers:
NvSciBufObjAlloc(sp.bufAttrList, &bufObj); // allocate NvSci buffer
NvmmNvSciBufToNvBufSurface(bufObj, &interop.nvbuf_surf); // NvSci → NvRM view (zero-copy)
m_upCamera->RegisterImages(sensorId, ISP0, sp.bufObjects); // SIPL renders here
EOF Synchronization (NvSciSync)#
SIPL signals ISP completion through an EOF fence, not a blocking call. Per sensor,
SetupSyncObjects() does the following:
Creates a CPU waiter attribute list and a SIPL signaler attribute list (
FillNvSciSyncAttrList(..., ISP0, SIPL_SIGNALER)).Reconciles the lists and allocates an
NvSciSyncObjobject.Registers the
NvSciSyncObjobject withRegisterNvSciSyncObj(sensorId, ISP0, NVSIPL_EOFSYNCOBJ).
At runtime, ProcessISP0Frame() calls GetEOFNvSciSyncFence() and
NvSciSyncFenceWait() before touching the frame. This sequence guarantees that the ISP has
finished writing before the CPU/VIC reads it.
The Per-Frame Copy (SIPL to NVMM) and Release#
Although the interop view is zero-copy, the element still performs one explicit VIC copy per frame so that the SIPL buffer can be recycled quickly and downstream owns an independent NVMM buffer:
// nvsipl_sensor_frame_callback() [runs in SIPL ISP0 thread]
gst_buffer_pool_acquire_buffer(pad->pool, &nvmm_gst_buffer, NULL); // NvBufSurface pool buffer
gst_memory_map(nvmm_mem, &map_info, GST_MAP_WRITE);
NvBufSurfTransform(nvbuf_siplsurf, nvmm_nvbuf_surf, &transform_params); // VIC blit, copy
nvsipl_buffer_queue_push(pad->buffer_queue, info, 1000 /*ms*/);
// After callback returns, SIPL interface calls ReleaseFrame() → SIPL buffer recycled
The NVMM pool buffers themselves are allocated by the element’s custom allocator:
// gst_nvsipl_memory_allocator_alloc()
NvBufSurfaceAllocate(&nvbuf->surf, 1, &input_params); // NV12_709_ER, BL or PL
nvbuf->dmabuf_fd = nvbuf->surf->surfaceList[0].bufferDesc;
// Downstream maps it back:
NvBufSurfaceFromFd(fd, (void**)&nvbuf_surf);
Note
Why a copy at all? SIPL owns a small fixed pool of ICP buffers. Copying to a GStreamer-managed NVMM pool decouples downstream buffering (queues, encoder latency) from the ISP pipeline and lets SIPL recycle its buffers immediately, avoiding ISP stalls and frame drops. In the current plug-in design, this is also applicable for GMSL.
VIC throughput: NvBufSurfTransform uses the default global VIC
session, whose internal mutex serializes concurrent blits across all sensor
callback threads. This is safe. For many-sensor throughput, call
NvBufSurfTransformSetSessionParams() once per callback thread to give each
its own VIC handle.
Buffer API Quick Reference#
API |
Domain |
Role in |
|---|---|---|
|
NvSci |
Allocate ISP0 output buffer from reconciled attributes. |
|
NvSci |
Merge SIPL + NvBufSurface attribute requirements. |
|
NvSci→NvRM |
Zero-copy interop: NvSciBufObj → NvBufSurface view. |
|
NvRM |
Add NvBufSurface attributes to an NvSci attribute list. |
|
SIPL |
Tell SIPL to render ISP0 into the NvSci buffers. |
|
NvSciSync |
Wait for ISP completion before read. |
|
NvRM |
Allocate the downstream NVMM pool buffers. |
|
VIC |
Copy/convert SIPL surface → NVMM surface. |
|
NvRM |
Map dmabuf fd back to |
Pipelines by Use Case#
The following pipelines cover the common use cases for AR0234 (HAWK), IMX728,
and IMX623 GMSL sensors. Adjust the config-name and link-mask values to
match the sensor and platform.
Sensor Configuration Cheat Sheet#
Sensor |
|
|
Pads |
|---|---|---|---|
AR0234 (HAWK) |
|
|
|
IMX728 |
|
|
|
IMX623 |
|
|
|
AR0234 / HAWK (Dual Sensor: src_0 + src_1)#
Fakesink (functional / throughput):
# Timed run with per-buffer logging
timeout 5 gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! fakesink sync=false \
cam.src_1 ! identity silent=false ! queue ! fakesink sync=false -v
# Plain
gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 name=cam \
cam.src_0 ! queue ! fakesink sync=false \
cam.src_1 ! queue ! fakesink sync=false
Preview (dual-tile on screen):
gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=0 window-width=1920 window-height=540 \
cam.src_1 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=540 window-width=1920 window-height=540 -v
# Block-linear output variant
gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=0 window-width=1920 window-height=540 \
cam.src_1 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=540 window-width=1920 window-height=540 -v
YUV capture (I420 to file):
timeout 3 gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! "video/x-raw, format=I420" ! filesink location=test_1.yuv \
cam.src_1 ! identity silent=false ! queue ! nvvidconv ! "video/x-raw, format=I420" ! filesink location=test_2.yuv
Video capture (H.264 / H.265):
# H.264
gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test-ar0234_h264-0.mp4 \
cam.src_1 ! identity silent=false ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test-ar0234_h264-1.mp4
# H.265
gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test-ar0234_h265-0.mkv \
cam.src_1 ! identity silent=false ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test-ar0234_h265-1.mkv
# H.264, block-linear
gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=false ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test-ar0234_h264-bl-0.mp4 \
cam.src_1 ! identity silent=false ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test-ar0234_h264-bl-1.mp4
# H.265, block-linear
gst-launch-1.0 nvsiplsrc config-name=AR0234CS_HAWK_NON_STEREO link-mask=1000 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=false ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test-ar0234_h265-0-bl.mkv \
cam.src_1 ! identity silent=false ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test-ar0234_h265-1-bl.mkv
IMX728 (Single Sensor: src_0)#
Fakesink:
timeout 5 gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! fakesink sync=false -v
gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 name=cam \
cam.src_0 ! queue ! fakesink sync=false
Preview:
gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=0 window-width=1920 window-height=1080 -v
# Block-linear
gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=0 window-width=1920 window-height=1080 -v
YUV capture:
timeout 3 gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=10 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! "video/x-raw, format=I420" ! filesink location=test_728.yuv -v
Video capture (H.264 / H.265, with block-linear variants):
# H.264
gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test_728-h264.mp4 -v
# H.265
gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test_728-h265.mkv -v
# H.264, block-linear
gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test_728-h264-bl.mp4 -v
# H.265, block-linear
gst-launch-1.0 nvsiplsrc config-name=R0SIM728S3RU2120NB2 link-mask=0100 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test_728-h265-bl.mkv -v
IMX623 (Single Sensor: src_0)#
Fakesink:
timeout 5 gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! fakesink sync=false -v
gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 name=cam \
cam.src_0 ! queue ! fakesink sync=false
Preview:
gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=0 window-width=1920 window-height=1080 -v
# Block-linear
gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! nv3dsink sync=false window-x=0 window-y=0 window-width=1920 window-height=1080 -v
YUV capture:
timeout 3 gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=10 name=cam \
cam.src_0 ! identity silent=false ! queue ! nvvidconv ! "video/x-raw, format=I420" ! filesink location=test_623.yuv -v
Video capture (H.264 / H.265, with block-linear variants):
# H.264
gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test_imx623-h264.mp4 -v
# H.265
gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test_imx623-h265.mkv -v
# H.264, block-linear
gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h264enc ! h264parse ! qtmux ! filesink location=test_imx623-h264-bl.mp4 -v
# H.265, block-linear
gst-launch-1.0 nvsiplsrc config-name=R0SIM623S5RU1197NB3 link-mask=010 num-buffers=100 bl-output=true name=cam \
cam.src_0 ! identity silent=true ! queue ! nvv4l2h265enc ! h265parse ! qtmux ! filesink location=test_imx623-h265-bl.mkv -v
CoE Example (Built-in / JSON Config)#
For CoE cameras, link-mask is not required; select the config by name (and
optionally a JSON file):
# Single CoE sensor via database config
gst-launch-1.0 nvsiplsrc config-name=VB1940 ! fakesink
# Dual CoE sensors defined in one JSON (fans out to src_0 + src_1)
gst-launch-1.0 nvsiplsrc config-name=VB1940 json-file=vb1940_0.json name=cam \
cam.src_0 ! queue ! nv3dsink \
cam.src_1 ! queue ! nv3dsink
Tips and Troubleshooting#
Playing a YUV dump:
test_*.yuvis raw I420. To view it, supply the resolution; for example,gst-launch-1.0 filesrc location=test_728.yuv ! videoparse format=i420 width=W height=H ! videoconvert ! autovideosink.identity silent=falseprints per-buffer info (timestamps, offsets). Useful for confirming both pads of a dual-sensor module produce frames.sync=falseon sinks avoids clock-based frame dropping for a live source; keep it for functional/throughput testing.Pad names are dynamic (
GST_PAD_SOMETIMES): They appear only after NULL→READY sensor discovery. Always reference them vianame=cam+cam.src_<i>ingst-launch.No frames or stalls: The push thread warns
src_<i>: no frames for 5 seconds. Checklink-maskand sensor power/cabling, and verify thatconfig-namematches your platform.Enable SIPL verbose trace (developer builds): Set the SIPL trace level to
LevelDebugingst_nvsipl_change_state()(NULL→READY), or run withGST_DEBUG=nvsiplsrc:5.
References#
Sources:
gstnvsiplplugin.cpp,NvSiplCameraInterface.{h,cpp},NvNvSciBufHelper.cpp, andgstnvsipl_queue.cpp.Related SIPL documentation: Introduction to SIPL, SIPL Camera Application Developer Guide, and Accelerated GStreamer.