Skip to content

sensing: Argus capture backend and encoder DMA-BUF input - #1429

Closed
gareth-morgan-nv wants to merge 22 commits into
gmorgan/RearchBackendfrom
gmorgan/Argus
Closed

gareth-morgan-nv wants to merge 22 commits into
gmorgan/RearchBackendfrom
gmorgan/Argus

Conversation

@gareth-morgan-nv

@gareth-morgan-nv gareth-morgan-nv commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Description

Stacked on #1428 (base is gmorgan/RearchBackend), which is stacked on #1461. This branch holds the Argus capture backend and the encoder's DMA-BUF input. The Argus path was built and run on an AGX Orin with three GMSL cameras by a reviewer (see Testing and their report); the later review-driven edits listed under Testing have not been built or run. It was written against the Jetson Multimedia API and the repository's existing Argus example as the reference.

  • Argus capture (core/argus_capture.*, built only with -DSENSING_WITH_ARGUS=ON, off by default): one capture session per camera, an Argus buffer stream into a pool of NvBufSurface NV12 DMA-BUFs, a repeating request pinned to the sensor mode's frame rate, and a non-blocking acquire that keeps the newest frame and returns older ones to the camera. Frames carry a lease that returns the buffer to the camera, and keeps the Argus objects alive until it does. The sensor timestamp and capture id are passed through unscaled.
  • Encoder DMA-BUF input (JetsonEncoder::submit_dmabuf): the output plane switches to V4L2 DMA-BUF memory for an encoder created for it (one input mode per encoder). The capture buffer's lease is held until the plane returns that input buffer, and released at once for a dropped frame. No CPU or CUDA copy.
  • Output: a leased-frame path next to the CUDA snapshot path; DMA-BUF input cannot be combined with --livekit-scale-div above 1.
  • Startup: --camera-backend=auto|sipl|argus probes both backends; SENSING_WITH_SIPL=OFF builds an Argus-only rig that needs no SIPL headers or configuration; --argus-mode=K picks the sensor mode.
  • Contract: the DMA-BUF is an NvBufSurface allocation whose layout (block-linear) only the hardware encoder reads; the header says so instead of describing pitches.

Not done

  • Any measurement of the Argus path (latency, resource use).
  • A supported per-frame correlation between encoder input and output; units are still paired by submission order.
  • Dynamically loaded backend modules.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Testing

  • Built and run on an AGX Orin (JetPack 6.2, three GMSL cameras, Argus-only build) by a reviewer, whose build fixes are included here; the later review-driven edits (pool sizing, early scale check, device-tolerant query, capture-id fallback, probe order) have not been built or run. The new and edited files are clang-format-14 clean; the capture factory, output, sensing camera and main.cpp pass a g++ syntax check (with and without SENSING_HAS_SIPL). argus_capture.cpp and jetson_encoder.cpp could not be syntax-checked here (they need the Argus and Multimedia API headers).
  • Backend-selection and frame-lease unit tests (from sensing: capture-backend rearchitecture (interface, selection, SIPL adapter) ahead of Argus (draft) #1428) are re-run standalone; the CMake test target itself was not configured.

Checklist

🤖 Generated with Claude Code

@gareth-morgan-nv gareth-morgan-nv self-assigned this Nov 5, 2026
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@gareth-morgan-nv gareth-morgan-nv changed the title sensing: Argus capture backend plan (draft, Argus untested, no Argus access) sensing: Argus capture backend and encoder DMA-BUF input (draft; unbuilt, no Argus access) Nov 5, 2026
@ivany-nv ivany-nv added the NVIDIA label Nov 5, 2026
@NVshawnhwang
NVshawnhwang marked this pull request as ready for review November 7, 2026 02:25
gareth-morgan-nv and others added 19 commits November 10, 2026 14:38
…ord the NV12M plane contract as unverified

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…d modules conditional

SENSING_WITH_ARGUS now finds libnvargus_socketclient (the nvargus-daemon client,
as the repository's Argus example does); the single-process libnvargus is an
explicit SENSING_ARGUS_SINGLE_PROCESS option. The plan keeps build-time backend
options as the path and adds a shared-module boundary only if a target shows
the executable cannot start with the unused SDK linked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…e acquire loop

start() stops the sessions already repeating if a later camera fails to start,
and acquire_latest() takes at most one pool's worth of buffers per call and no
longer leaks the frame it holds when an acquire fails. Not built or run (no
Argus SDK).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
The extra message() and endif() made CMake fail to configure with 'Flow
control statements are not properly nested'. Reported by the Orin build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
The plan lives in the git-ignored agentPlans/ folder instead.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…e only the selected backend

The initialised EGL display is owned by a member, so a constructor that throws
after eglInitialize no longer leaks it. An explicit --camera-backend initialises
only that SDK instead of probing both.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
Built and run on an AGX Orin (JetPack 6.2 / L4T R36.4.3, SENSING SHW5G
GMSL cameras, Argus backend) publishing three 2560x1984 tracks to a
LiveKit room consumed by the egocap backend. Four changes were needed
to get from source to a running binary:

- livekit_video_output: FrameMeta had no stamp_us member, so the file
  did not compile. Carry the frame's CLOCK_MONOTONIC time through
  track_in_flight and use it for the transport timestamp.
- CMake: find the Multimedia API tree before the Argus block, whose
  header search is relative to it; previously SENSING_WITH_ARGUS=ON
  failed on a fresh configure unless JETSON_MMAPI_DIR was passed.
- CMake: argus_capture.cpp uses the NvBufSurf allocation wrapper, so
  compile samples/common/classes/NvBufSurface.cpp and link
  libnvbufsurftransform that it depends on.
- CMake: the LiveKit SDK prebuilt is tied to its Ubuntu release's glibc;
  pick the 22.04 or 24.04 tarball by the host's /etc/os-release
  (LIVEKIT_SDK_UBUNTU overrides) instead of always 24.04, which does
  not link on JetPack 6.

README: list the build prerequisites the top-level CMake needs on a
stock Jetson (CMake >= 3.24, uv, patchelf) and note the JetPack 6.2
Argus-only configuration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: nvShawnHwang <265103681+NVshawnhwang@users.noreply.github.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…vice-tolerant query

- Share the encoder's input-buffer count with the Argus pool (pool = encoder + 4,
  refused below encoder + 3) so the headroom cannot drift apart.
- Refuse DMA-BUF input with a scale divisor before any LiveKit connection.
- query() skips a device that lacks the requested sensor mode instead of failing
  the probe for every camera; null-check the sensor-mode interface.
- Number frames from a counter when Argus returns no capture metadata, so they are
  not all dropped as one re-read frame.
- Remove DISPLAY before the backend probes load libargus or EGL.
- Update comments that described only SIPL.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…rdown

- Each camera has its own thread that blocks on acquireBuffer (finite wait) and keeps
  only the newest frame; a camera that delivers nothing no longer holds up the others
  or spams the daemon with zero-timeout polls. Frames are stamped on arrival, not
  when the polling loop collects them.
- stop() joins the acquisition threads, stops every session before waiting on any,
  uses a finite waitForIdle per camera, and times each blocking call so a slow one is
  named in the log. A session that does not go idle is marked unclean and its buffers
  are leaked, never freed while the hardware may still write them. A whole-teardown
  watchdog ends the process if the SDK never returns.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
- If creating an acquisition thread fails, join the ones already started and stop
  the sessions, so no thread outlives its camera.
- One teardown deadline covers the whole stop(), including returning held frames to
  their streams, and the object destruction has its own; the call in progress is
  recorded before it is entered, so a call that never returns is named when the
  process ends.
- Return the frames waiting in the slots before waiting for the sessions to go idle:
  an in-flight request may need a free buffer to finish.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…he process

- A failed start() now uses the same teardown as stop(): it joins the workers and
  returns the frames they hold, which breaks the Impl -> frame lease -> Impl cycle.
- A session that does not go idle after stopRepeat is fatal in this dedicated
  producer (the service supervisor restarts it) instead of being marked and leaked
  while the process retries.
- The EGL display is terminated inside the destruction deadline.
- Record the lease-ownership rule in the sensing AGENTS.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
Frame leases are released from any thread, so they now only post the buffer's slot
to a per-camera mailbox; one owner thread makes every runtime acquire, release and
stop call on that camera's stream, so those calls cannot overlap. When every buffer
is out the owner waits on the mailbox instead of in an acquire that needs a returned
buffer.

Capture stops in two parts: stop() ends capture on the owner and clears the held
frame but keeps returning buffers; the owner ends when the capture object is
destroyed, after the last lease is dropped. A failed start() undoes the same way.

The ownership logic is SDK-free (buffer_owner.hpp) and covered by tests with a fake
buffer port, run under ThreadSanitizer and AddressSanitizer.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…d, fail on a doubled return

- The owner returns the cleared latest frame's buffer before it enters the blocking
  idle wait, since it cannot return anything inside that wait.
- A buffer the stream refuses (or whose release throws) stays counted as out and ends
  acquisition, instead of being counted free while acquiring continues.
- A rejected return from a lease (a buffer returned twice) fails the owner instead of
  being ignored.
- The fatal-exit comment no longer says a supervisor restarts the producer; the unit's
  restart policy is stated where the unit is defined.
- Tests for the stop ordering and the refused/throwing release.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…r is back

CameraCapture::stop() now stops new captures and returns; the wait for the sessions
to go idle happens when the owner ends, after every posted buffer has been returned,
so it never depends on a buffer a consumer still holds. close() reports whether every
buffer came back and the stream went idle; anything else ends the process.

The test fake keeps a refused buffer acquired instead of freeing it, and the tests
cover the wait_idle ordering, held leases at close, and the idle outcome.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
The Argus adapter compared stopRepeat's return value to STATUS_OK, which is not a
valid success predicate. Acknowledge that the call returned and keep the Status
check for waitForIdle.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…_usage's layout

Move the check that every requested camera exists out of main.cpp into
match_requested_cameras in camera_backend, so it is testable without the
Argus SDK, and cover it. Shorten one usage line so clang-format keeps
print_usage's existing layout instead of re-indenting the whole block.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…r helper

Logger::get returns std::shared_ptr<spdlog::logger>, not a shared_ptr of
isaaccapture::Logger, so the helper did not compile when the Argus backend
was enabled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
…sample wrapper

The Argus capture compiled NVIDIA's sample NvBufSurface.cpp from the installed tree
only for NvAllocate, which pulled in libnvbufsurftransform. Call the driver's
NvBufSurfaceAllocate directly, as that wrapper does, so the build uses neither the
sample source nor the transform library.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Gareth Morgan <gmorgan@nvidia.com>
@NVshawnhwang

Copy link
Copy Markdown
Collaborator

Built 6502215 on an AGX Orin, L4T R36.4.3 (JetPack 6.2), -DSENSING_WITH_SIPL=OFF -DSENSING_WITH_ARGUS=ON -DJETSON_MMAPI_DIR=/usr/src/jetson_multimedia_api, ENABLE_CLANG_FORMAT_CHECK=OFF. Configure passes. Compile fails in the vendored third_party/jetson_multimedia_api/classes/NvVideoEncoder.cpp: NvBufSurfaceDeviceInfo, NvBufSurfaceGetDeviceInfo and NVBUF_DRIVER_TYPE_NVGPU (lines 99-102) and V4L2_CID_MPEG_VIDEOENC_H265_DISABLE_SAO (line 808) are not declared by the R36.4.3 headers. The vendored sources are from R39.2.1 and there is no CMake switch to compile the installed /usr/src samples instead. So the Argus path can’t be run on JetPack 6.2 with this layout, which is the configuration the earlier hardware report used. Is JetPack 6.2 still meant to be supported? If so, the vendored classes need to match R36, or the build needs a fallback to the installed samples. Argus runtime (three-camera capture, owner threads, bounded shutdown) is untested here for that reason.

@gareth-morgan-nv

Copy link
Copy Markdown
Contributor Author

Closing as superseded: the camera path is now the camera_streamer architecture (SIPL/Argus sender, AVS1 over TCP, PC receiver and LiveKit bridge; see #1473-#1479). Not merged; the branch and commits are kept.

🤖 Generated with Claude Code

@gareth-morgan-nv

Copy link
Copy Markdown
Contributor Author

This PR is superseded by the camera_streamer PRs:

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants