System designTroubleshooting

Appsrc not-negotiated error: fix caps framerate and format

Fix the appsrc not-negotiated error: set caps before the first buffer, declare a real framerate such as 30/1, and place the converter before the capsfilter.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01Why does appsrc stop with 'streaming stopped, reason not-negotiated (-4)'?
  2. 02Which caps conflict caused the failure in this chain?
  3. 03Should appsrc caps be set at build time or with the first image?
  4. 04What framerate belongs in appsrc caps?
  5. 05Where do the capsfilter and the converter go?
  6. 06How do you verify negotiation before the first live frame?
  7. 07What keeps not-negotiated from returning after a camera change?
  8. 08Which machine should run the acquisition side?
  9. 09Questions

A pipeline that links cleanly, reaches PAUSED and then dies on its very first frame is the classic appsrc failure mode. The log prints the caps event your code just created and, one line later: 'streaming stopped, reason not-negotiated (-4)'. This page is for anyone feeding camera frames into a GStreamer chain by hand — from a ROS node, a capture callback or a file. You will read the caps event, find the empty intersection behind the error, and apply the three fixes that cover nearly every case: set the caps property before the pipeline leaves PAUSED, declare a framerate your feed really delivers instead of 0/1, and keep every restrictive capsfilter behind a converter. Each fix is a line of code; the work is knowing which one your chain needs.

Why does appsrc stop with 'streaming stopped, reason not-negotiated (-4)'?

The error means the caps event your source pushed could not be intersected with the caps the next element allows: the intersection came back empty, the push returned -4, and GStreamer parked the source task before any frame moved.

The sequence in the log is worth memorizing. The pipeline reached PAUSED without complaint because appsrc had no caps set, so the link deferred its decision. On the first push, appsrc emitted a caps event describing the buffer — video/x-raw, format=BGRA, 672×376, framerate 0/1 — and the peer pad compared it against what downstream allows: a capsfilter fixed to video/x-raw(memory:NVMM), format=I420. The two sets share no media type. The push failed with not-negotiated (-4), and the source and queue tasks went to PAUSED. The first image is only the messenger; the missed negotiation is the fault. The same startup pattern in a desktop capture stack is covered by the camera pipeline format negotiation guide.

Rank the causes before you touch code:

Rank Cause How it shows up One-line test
1 A (memory:NVMM) capsfilter sits before any converter not-negotiated (-4) on the first push rerun the chain with videotestsrc; it fails the same way
2 Caps applied with the first image, after the state change caps event timestamped with the first buffer search the log: no caps event before PAUSED
3 framerate declared as 0/1 rate-dependent elements cannot agree a fraction read the caps event: framerate=(fraction)0/1
4 Resolution or encoding drift after a camera swap width or height mismatch at push assert message dimensions against the caps string

Which caps conflict caused the failure in this chain?

Your chain declared system-memory BGRA at 672×376 while its capsfilter demanded video/x-raw(memory:NVMM), format=I420; a different memory feature and a different pixel format guarantee an empty intersection on the first frame.

(memory:NVMM) is a memory feature: it tells GStreamer the buffers live in GPU-resident memory that only the platform's converter can allocate and fill. An appsrc never produces that memory — it hands over plain process memory holding your BGRA rows. The formats differ on top of that: BGRA packs four bytes per pixel, while I420 stores the same picture as a luma plane plus two quarter-size colour planes, twelve bits per pixel in total. Both gaps must be bridged by a converter, here the nvvideoconvert element. Placement was the second half of the failure: the link order put the capsfilter between the queue and nvvideoconvert, so the demanding filter sat before anything able to satisfy it. The empty-intersection check has a Windows capture equivalent in the no matching media type walkthrough.

Should appsrc caps be set at build time or with the first image?

Set the caps property at build time, before the pipeline leaves PAUSED. Negotiation completes during the state change; caps that arrive with the first live buffer leave the link un-negotiated, and that first push is exactly the one that fails.

You know the numbers before any frame arrives: width, height and encoding are fixed by the camera mode, and a sensor_msgs/Image message carries them as plain fields. Set the caps property on appsrc in the same g_object_set call that sets is-live and do-timestamp, then change state. Treat those caps as fixed for the life of the stream. If the camera mode genuinely changes, stop the pipeline, set new caps and restart; pushing 1280×720 buffers under 672×376 caps fails the link again. Before the first push, wrap the incoming bytes at exactly the declared format — the raw camera buffer conversion walkthrough shows the stride and padding checks that step needs.

What framerate belongs in appsrc caps?

Declare the rate you actually deliver — for a 30 Hz feed, framerate=(fraction)30/1. The value 0/1 means an unknown rate: appsrc accepts it, but every downstream element that resamples or derives durations from the caps has no fraction to negotiate with.

With do-timestamp=true and is-live=true, buffer stamps come from the pipeline clock, so the caps fraction is a declaration of cadence, not a generator. Declare the number your feed holds: 30/1 for a 30 Hz camera, 25/1 for PAL-rate video, 60/1 for a high-speed mode. A genuinely irregular feed should stay in plain system memory until the one rate-dependent element in the chain — videorate is the usual choice — fixes the cadence where it is needed, because videorate negotiates a fixed output fraction on its output. Leaving 0/1 in place and hoping the muxer tolerates it only schedules a second failure behind the first.

Where do the capsfilter and the converter go?

Put a converter between appsrc and any restrictive capsfilter: appsrc ! queue ! converter ! capsfilter ! muxer. The source emits system-memory BGRA, and only the converter — here nvvideoconvert — can change the pixel format and move buffers into GPU-resident memory.

Two arrangements work. In the first, a filter right after the queue pins what you declare — video/x-raw,format=BGRA,width=672,height=376,framerate=30/1 — and nvvideoconvert performs the change to video/x-raw(memory:NVMM),format=I420 on the way into nvstreammux. In the second, you drop that first filter and let a single filter after the converter do all the pinning. Both obey the same rule: a capsfilter may only describe media the element before it can produce. No amount of extra decoders or filters around a (memory:NVMM) filter directly after appsrc will fix that link, because appsrc can never produce that media type.

How do you verify negotiation before the first live frame?

Rebuild the chain with videotestsrc standing in for appsrc and a fixed caps string. If videotestsrc at video/x-raw,format=BGRA,width=672,height=376,framerate=30/1 reaches PLAYING through your filters, negotiation is solved and appsrc only has to declare those same caps.

  1. Run the stand-in chain in gst-launch-1.0 with the exact filters from your code, including the (memory:NVMM) filter and the converter.
  2. Raise the log level with GST_DEBUG=3 and read the caps events: BGRA at 672×376 and 30/1 on the source side, I420 on NVMM after the converter.
  3. Swap videotestsrc for appsrc, set the caps property to the tested string, and push one synthetic BGRA buffer from your code before attaching the live feed.
  4. Copy the image bytes out of the callback before you queue them; the torn frames fix explains why a shared buffer corrupts frames under load.

What keeps not-negotiated from returning after a camera change?

Derive the caps string from the same fields that fill the image message — width, height, encoding — and assert the match before every push. A 1280×720 BGRA frame carries 3,686,400 bytes against the 1,010,688 declared, 3.6 times the size, and fails the link outright.

The frame arithmetic makes those assertions concrete:

frame bytes = width × height × bytes per pixel 672 × 376 × 4 = 1,010,688 bytes ≈ 0.96 MiB per BGRA frame stream rate = 1,010,688 × 30 = 30,320,640 B/s ≈ 29 MiB/s at 30 fps 10-frame queue = 10 × 1,010,688 = 10,106,880 bytes ≈ 9.6 MiB

At about 29 MiB/s for this single stream, a queue that grows without bound is a latency problem before it is a memory problem; appsrc's max-bytes set to 0 disables its built-in size limit, so the queue after it holds whatever your processing pace allows. Assert width and height on every buffer, log the settled caps once at startup, and treat a camera mode change as a renegotiation: stop the pipeline, set the new caps, restart. Keep the converter in place even when both sides already match, so the next format change — BGRA to RGB, say — costs a conversion instead of a failure.

Which machine should run the acquisition side?

Everything up to the muxer is CPU work: appsrc, the queue, the converters and x-raw negotiation run on any x86 machine, while the inference tail stays on the platform that provides GPU memory. A fanless vision controller mounted beside the cameras covers the acquisition side.

We size the acquisition box from port count, memory and mounting, not from the inference GPU. This example stream flows at about 29 MiB/s, and each controller below absorbs that copy load with room to spare. The MV-VC3301H-128G66 pairs an Intel Core i3 with 8 GB of memory and 5+ LAN ports. The MV-VC2140-128G40-1T covers a single-camera cell with an Intel N-series processor, 8 GB of memory and 4 built-in light channels. The MV-VC5525-256G70-NN provides 5+ LAN ports, PoE camera ports, 8 built-in light channels and 32 GB of memory.

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
$2,419.00View part
Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
$1,679.00View part
Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
$4,479.00 · quotedView and quote

One line to close: set appsrc caps before the pipeline starts, declare the true framerate, keep every restrictive capsfilter behind a converter, and prove the whole thing with videotestsrc before the first live frame. For the frames themselves, match the acquisition chain in the area scan cameras category, the camera finder, or the camera configuration step. More pipeline debugging lives on the systems topic page, and the GPU streaming pipeline trace shows how to follow frames through the rest of the chain.

Questions

What does the -4 in 'not-negotiated (-4)' actually mean?

-4 is the numeric value of the GST_FLOW_NOT_NEGOTIATED flow return. A pad hands that value back when data is pushed before the link's caps were agreed, the source task stops, and the bus posts the internal data stream error you saw. Treat it as a negotiation failure, not something to catch at runtime: make the caps intersect and the code path never runs.

Does framerate=0/1 ever make sense in appsrc caps?

Only for non-live, one-shot pushes where nothing downstream needs a rate, such as a single still image into a filesink. In a live chain with is-live=true and do-timestamp=true, declare the rate you actually deliver. Anything that resamples or computes durations from the caps has no fraction to negotiate against, and the failure moves downstream instead of disappearing.

Why does the same caps string work in gst-launch-1.0 but fail in my node?

The launch string usually wires the converter before the restrictive filter, and videotestsrc produces exactly the caps you typed. Your node placed the filter directly after appsrc and declared caps only when the first image arrived. Copy the launch order into your code, set the caps property up front, and both paths behave identically.

How big should the queue after appsrc be?

Size the queue from the frame math and your latency budget, not from a default. A 672×376 BGRA frame is 1,010,688 bytes, so ten frames hold 10,106,880 bytes, about 9.6 MiB, and 30 fps flows at roughly 29 MiB/s. Pick a depth your processing loop can drain, then watch the queue's real occupancy under load and adjust.

Do I need pixel-aspect-ratio and interlace-mode in the caps?

Not for a progressive sensor with square pixels: 1/1 and progressive are the defaults, so a caps string without those fields describes the same media. Declare them only when the source is genuinely interlaced or has non-square pixels, and then carry them on every element in the chain so the intersection stays non-empty.