Arducam MIPI camera exposure not changing, bad image quality on Jetson Orin Nano JetPack 6

Hi, hoping someone has seen this before. I’m running an Arducam MIPI camera on a Jetson Orin Nano with JetPack 6, and the exposure is completely off — image quality is terrible no matter what I point it at. Washed out in bright light, noisy in low light, just awful.

My pipeline is the one from the Arducam quick start guide:

gst-launch-1.0 nvarguscamerasrc ! 'video/x-raw(memory:NVMM),width=1920, height=1080, framerate=60/1, format=NV12' ! nvvidconv flip-method=2 ! 'video/x-raw,width=960, height=616' ! nvvidconv ! nvegltransform ! nveglglessink -e

And this is what it prints when I run it:

Using winsys: x11 
Pipeline is live and does not need PREROLL ...
Got context from element 'eglglessink0': gst.egl.EGLDisplay=context, display=(GstEGLDisplay)NULL;
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
GST_ARGUS: Creating output stream
CONSUMER: Waiting until producer is connected...
GST_ARGUS: Available Sensor modes :
GST_ARGUS: 4656 x 3496 FR = 9.000000 fps Duration = 111111112 ; Analog Gain range min 1.000000, max 16.000000; Exposure Range min 13000, max 683709000;

GST_ARGUS: 3840 x 2160 FR = 17.000000 fps Duration = 58823528 ; Analog Gain range min 1.000000, max 16.000000; Exposure Range min 13000, max 683709000;

GST_ARGUS: 1920 x 1080 FR = 59.999999 fps Duration = 16666667 ; Analog Gain range min 1.000000, max 16.000000; Exposure Range min 13000, max 683709000;

GST_ARGUS: 1280 x 720 FR = 120.000005 fps Duration = 8333333 ; Analog Gain range min 1.000000, max 16.000000; Exposure Range min 13000, max 683709000;

GST_ARGUS: Running with following settings:
   Camera index = 0 
   Camera mode  = 2 
   Output Stream W = 1920 H = 1080 
   seconds to Run    = 0 
   Frame Rate = 59.999999 
GST_ARGUS: Setup Complete, Starting captures for 0 seconds
GST_ARGUS: Starting repeat capture requests.
CONSUMER: Producer has connected; continuing.
Redistribute latency...
WARNING: from element /GstPipeline:pipeline0/GstEglGlesSink:eglglessink0: Pipeline construction is invalid, please add queues.
Additional debug info:
../libs/gst/base/gstbasesink.c(1249): gst_base_sink_query_latency (): /GstPipeline:pipeline0/GstEglGlesSink:eglglessink0:
Not enough buffering available for  the processing deadline of 0:00:00.015000000, add enough queues to buffer  0:00:00.015000000 additional data. Shortening processing latency to 0:00:00.000000000.
WARNING: from element /GstPipeline:pipeline0/GstEglGlesSink:eglglessink0: Pipeline construction is invalid, please add queues.
Additional debug info:
../libs/gst/base/gstbasesink.c(1249): gst_base_sink_query_latency (): /GstPipeline:pipeline0/GstEglGlesSink:eglglessink0:
Not enough buffering available for  the processing deadline of 0:00:00.015000000, add enough queues to buffer  0:00:00.015000000 additional data. Shortening processing latency to 0:00:00.000000000.
WARNING: from element /GstPipeline:pipeline0/GstEglGlesSink:eglglessink0: A lot of buffers are being dropped.
Additional debug info:
../libs/gst/base/gstbasesink.c(3143): gst_base_sink_is_too_late (): /GstPipeline:pipeline0/GstEglGlesSink:eglglessink0:
There may be a timestamping problem, or this computer is too slow.

I’m fairly sure it’s an exposure issue, but I don’t know how to actually change exposure on this board. Any pointers?

On Jetson the quickest way to poke at the MIPI camera exposure is v4l2-ctl.

First, list your video devices to identify the camera:

v4l2-ctl --list-devices

You should see something like:

NVIDIA Jetson Orin Nano (platform:tegra-camrtc-ca):
    /dev/video0

Assuming your camera shows up as /dev/video0, list its controls:

v4l2-ctl -d /dev/video0 --list-ctrls

or

v4l2-ctl -d /dev/video0 -l

Find the exposure-related control and set it, for example:

v4l2-ctl -d /dev/video0 --set-ctrl=exposure_absolute=100

One thing to be careful about: use the exact control name from your --list-ctrls output. The example above assumes it’s called exposure_absolute — yours might be different. What name do you see in the list?

Thanks. I found the exposure control in --list-ctrls and tried setting it to a whole bunch of different values with v4l2-ctl (using the exact name from my output). No change at all — the preview looks identical every time.

I’ve also spent a while searching the forums and didn’t find anything that works for this specific camera + JetPack 6 combination.

If v4l2-ctl isn’t moving the exposure, the better route on Jetson is to control it through nvarguscamerasrc directly. The v4l2 path and the argus camera path don’t always stay in sync.

First, check which parameters nvarguscamerasrc exposes:

gst-inspect-1.0 nvarguscamerasrc | less

The one you want is exposuretimerange — the unit is nanoseconds. To force a fixed exposure, make the left and right values of the range the same:

SENSOR_ID=0 # 0 for CAM0 and 1 for CAM1 ports
FRAMERATE=60 # Framerate can go from 2 to 60 for 1920x1080 mode
gst-launch-1.0 nvarguscamerasrc exposuretimerange="100 100" sensor-id=$SENSOR_ID ! "video/x-raw(memory:NVMM),width=1920,height=1080,framerate=$FRAMERATE/1" ! nvvidconv ! nvoverlaysink

I’ve also sent you an example comparison image via private message since I can’t attach it here. Give the fixed-exposure approach a try and see if the image changes at all.

Okay, so I ran that pipeline and got this:

GST_ARGUS: NvArgusCameraSrc: Setting Exposure Time Range : 100 100
WARNING: erroneous pipeline: no element "nvoverlaysink"

From what I’ve read, nvoverlaysink was removed in the newer GStreamer versions on JetPack 6 / Orin Nano. I tried nvdrmvideosink as the suggested replacement, but that errors out too.

So I went back to my original pipeline and just added exposuretimerange to it. Now the value does change the brightness, but only slightly. I’ve swept through a bunch of values and none of them give a decent image — the exposure is still way off.

Before changing more values, let’s verify the actual valid range. Can you post the exposure line from:

v4l2-ctl -d /dev/video0 -l

Also, in the GST_ARGUS log you posted earlier, the sensor reports Exposure Range min 13000, max 683709000 (that’s in ns). So a value like exposuretimerange=“100 100” is below the minimum the sensor accepts — that would explain why the brightness change is so slight, since you’re likely starting from a clamped baseline.

If you can grab two comparison shots (one at the low end of the range and one at the high end), that’ll show how much the parameter is actually moving. If the range checks out and it still barely changes, the problem may not be exposure itself.

As a general note, tweaking camera parameters on Jetson is a lot trickier than on a Raspberry Pi — some controls only exist through v4l2, some only through argus, and the values don’t always map the way you’d expect. For now, the fixed-exposure workaround is the exposuretimerange approach with matching left/right values.