Skip to main content

Camera on Ubuntu 24.04

Ubuntu 24.04 Only

This guide covers camera usage on Ubuntu 24.04 using libcamera. For Ubuntu 20.04, see the Camera - 20.04 guide which uses the Qualcomm QMMF stack.

This guide walks through setting up CSI cameras on Tachyon using libcamera and GStreamer. While we use the IMX335 sensor as our example, the principles apply to other MIPI CSI cameras.

By the end of this guide, you'll have:

  • Real-time camera preview (GStreamer)
  • JPEG photo capture (single frame with auto-exit)
  • MP4 video recording (H.264 encoded, timed auto-stop)

Core pipeline: V4L2 (CAMSS) → libcamera (simple pipeline) → ipa_soft_simple (software ISP) → GStreamer (libcamerasrc)

IMX335 camera connected to Tachyon

Prerequisites

1. Flash Ubuntu Desktop v1.1.32 or Later

Ensure you're running a recent Tachyon Ubuntu 24.04 desktop image:

particle flash --tachyon /path/to/tachyon-ubuntu-24.04-RoW-desktop-1.1.41.zip

2. Configure Device Tree Overlays

CSI cameras require device tree overlays to be loaded at boot. This tells the kernel how to communicate with your specific camera sensor.

Mount the userdata partition and create the overlay configuration:

mount /dev/sda10 /mnt/userdata
mkdir -p /mnt/userdata/boot && cd /mnt/userdata/boot
nano overlays.txt

Add your camera overlays (example for IMX335 on both CSI ports):

overlays=qcm6490-tachyon-camera-imx335-csi1.dtbo
qcm6490-tachyon-camera-imx335-csi2.dtbo
sync

Copy the overlay files from the device's boot overlays directory:

adb shell cp /boot/overlays/qcm6490-tachyon-camera-imx335-csi1.dtbo /mnt/userdata/boot
adb shell cp /boot/overlays/qcm6490-tachyon-camera-imx335-csi2.dtbo /mnt/userdata/boot

Reboot. On success, you should see log messages like:

Found 'userdata' partition 0:10 block size=4096/4096
Read overlay '/boot/qcm6490-tachyon-camera-imx335-csi1.dtbo' size=3628
Processing overlay 'qcm6490-tachyon-camera-imx335-csi1.dtbo': 0

3. Use Xorg (X11) for Better Stability

GStreamer's video sinks have better compatibility with Xorg than Wayland on this platform. Using Wayland may result in display errors or failed pipelines when previewing camera output.

Verify your session type:

echo $XDG_SESSION_TYPE

Expected output: x11. If it shows wayland, select "Ubuntu on Xorg" at the login screen.

4. Verify Camera Recognition

Check that the kernel recognizes your camera:

v4l2-ctl --list-devices

You should see your camera listed among the video devices.


Quick Start

Follow these steps in order to get your camera working.

Step A: Install Dependencies

First, connect to a network. You can use ethernet over USB, or connect to WiFi:

nmcli dev wifi connect "wifi_name" password "your_wifi_password"

Install the required packages:

sudo apt update
sudo apt install -y \
git build-essential meson ninja-build pkg-config \
python3 python3-pip python3-setuptools \
python3-ply \
qt6-base-dev qt6-base-dev-tools \
libqt6opengl6-dev \
libjpeg-dev libtiff-dev \
libdrm-dev libgbm-dev libevent-dev \
libglib2.0-dev \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
gstreamer1.0-tools \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-libav

Step B: Build and Install libcamera

We build libcamera from source to enable qcam (the Qt-based camera application), which is disabled by default. This also ensures GStreamer integration is properly configured.

Clone and build libcamera:

git clone https://git.libcamera.org/libcamera/libcamera.git
cd libcamera/
mkdir -p build

meson setup build \
--prefix=/usr/local \
-Dpipelines=auto \
-Dipas=simple \
-Dgstreamer=enabled \
-Dtest=true \
-Dcam=enabled \
-Dqcam=enabled

ninja -C build
sudo ninja -C build install
sudo ldconfig

Step C: Configure Environment Variables

Add libcamera to your environment:

export PATH=/usr/local/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/lib/aarch64-linux-gnu:/usr/local/lib:$LD_LIBRARY_PATH
export GST_PLUGIN_PATH=/usr/local/lib/aarch64-linux-gnu/gstreamer-1.0:$GST_PLUGIN_PATH
export LIBCAMERA_IPA_PATH=/usr/local/lib/aarch64-linux-gnu/libcamera/ipa
tip

Add these exports to your ~/.bashrc to make them permanent.

Step D: Configure udev Permissions

By default, the DMA heap devices have restrictive permissions. Configure udev rules to allow regular users to access the camera without sudo.

Check current permissions:

ls -l /dev/dma_heap* /dev/dri/renderD* 2>/dev/null

You'll likely see:

crw------- 1 root root 249, 1 Jan 7 08:14 /dev/dma_heap/reserved
crw------- 1 root root 249, 0 Jan 7 08:14 /dev/dma_heap/system

1. Add your user to required groups:

sudo usermod -aG render,video $USER

2. Create udev rules for persistent permissions:

sudo tee /etc/udev/rules.d/99-camera-dmaheap.rules >/dev/null <<'EOF'
SUBSYSTEM=="dma_heap", KERNEL=="system", GROUP="render", MODE="0660"
SUBSYSTEM=="dma_heap", KERNEL=="reserved", GROUP="render", MODE="0660"
SUBSYSTEM=="misc", KERNEL=="udmabuf", GROUP="render", MODE="0660"
SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="render", MODE="0660"
EOF

3. Reload udev rules:

sudo udevadm control --reload-rules
sudo udevadm trigger --subsystem-match=dma_heap
sudo udevadm trigger --subsystem-match=drm
sudo udevadm trigger --name-match=udmabuf || true

4. Verify permissions:

ls -l /dev/dma_heap/system /dev/dma_heap/reserved /dev/dri/renderD128
ls -l /dev/media0 /dev/video0 2>/dev/null
id
warning

Log out and log back in (or reboot) to activate group membership before proceeding.

Step E: Validate the Setup

Test that everything works with a live preview:

gst-launch-1.0 -v \
libcamerasrc \
! video/x-raw,format=RGBA,width=1280,height=720,framerate=30/1 \
! videoconvert \
! autovideosink
GStreamer live preview window

List available cameras:

cam -l

Example output:

Available cameras:
1: 'imx335' (/base/soc@0/cci@ac4b000/i2c-bus@0/camera@1a)
2: 'imx335' (/base/soc@0/cci@ac4a000/i2c-bus@1/camera@1a)

To use a specific camera, specify it by name:

# CSI1 (camera 1)
gst-launch-1.0 -v \
libcamerasrc camera-name="/base/soc@0/cci@ac4b000/i2c-bus@0/camera@1a" \
! video/x-raw,format=RGBA,width=1280,height=720 \
! videoconvert ! autovideosink

# DSI/CSI2 (camera 2)
gst-launch-1.0 -v \
libcamerasrc camera-name="/base/soc@0/cci@ac4a000/i2c-bus@1/camera@1a" \
! video/x-raw,format=RGBA,width=1280,height=720 \
! videoconvert ! autovideosink

You can also use the Qt-based camera application:

qcam --renderer=gles --stream pixelformat=YUYV,width=1280,height=720
qcam --renderer=gles --stream pixelformat=YUYV,width=1920,height=1080

Usage Examples

Live Preview

Basic preview at 720p @ 30fps:

gst-launch-1.0 -v \
libcamerasrc \
! video/x-raw,format=RGBA,width=1280,height=720,framerate=30/1 \
! videoconvert \
! autovideosink

Capture a Photo

Capture a single frame using the cam utility:

cam --camera=1 --capture=1 --orientation=rot180 --file=photo.ppm \
--stream role=viewfinder,width=1280,height=720

Convert to JPEG:

ffmpeg -y -i photo.ppm photo.jpg

Capture RAW/DNG format:

# Camera 1 (CSI1)
cam -c1 -C1 -s role=raw,width=2592,height=1944 -Fframe_raw.dng

# Camera 2 (CSI2)
cam -c2 -C1 -s role=raw,width=2592,height=1944 -Fframe_raw.dng

Record Video

Record H.264 encoded MP4 video:

gst-launch-1.0 -e \
libcamerasrc \
! video/x-raw,format=RGBA,width=1280,height=720,framerate=30/1 \
! videoconvert \
! x264enc tune=zerolatency bitrate=4000 speed-preset=ultrafast \
! h264parse \
! mp4mux \
! filesink location=video.mp4

Record for a specific duration (20 seconds) with live preview:

timeout -s INT -k 5s 20s \
gst-launch-1.0 -e -v \
libcamerasrc \
! video/x-raw,format=RGBA,width=1280,height=720,framerate=30/1 \
! videoflip method=rotate-180 \
! videoconvert \
! tee name=t \
t. ! queue ! autovideosink \
t. ! queue ! x264enc tune=zerolatency speed-preset=veryfast bitrate=4000 key-int-max=30 \
! h264parse ! mp4mux faststart=true \
! filesink location=video_20s.mp4

This pipeline:

  • Captures video from the camera
  • Rotates 180 degrees (adjust as needed)
  • Splits the stream: one branch for live preview, one for recording
  • Records for exactly 20 seconds with proper file termination

Troubleshooting

DMA-buf / DMA-heap Permission Issues

Symptoms:

  • Could not open any dma-buf provider
  • dma-heap allocation failure

Solution:

  1. Ensure udev rules are in place (see Step D above)
  2. Verify your user is in the render and video groups: id
  3. Log out and log back in to activate group membership

MP4 File Won't Play

Cause: The mp4mux element didn't receive an end-of-stream (EOS) signal, leaving the moov atom incomplete.

Solution: Always use:

  • The -e flag with gst-launch-1.0 (ensures EOS on interrupt)
  • timeout -s INT to send SIGINT for graceful shutdown

Single Frame Capture Doesn't Exit

Cause: The pipeline continues running instead of stopping after one frame.

Solution: Use identity eos-after=1 in GStreamer pipelines to trigger EOS after a single frame, or use the cam utility with --capture=1.

"Configuration file 'imx335.yaml' not found for IPA module 'simple'"

Cause: libcamera's Image Processing Algorithm (IPA) module doesn't have tuning data for your specific sensor.

Status: This is a known limitation. The camera will still work but may not have optimal color/exposure tuning. Creating sensor-specific YAML tuning files is an area for future improvement.

Camera Not Detected

  1. Verify device tree overlays are loaded (check boot logs)
  2. Ensure the camera ribbon cable is properly seated
  3. Check dmesg | grep -i imx (or your sensor name) for driver messages
  4. Verify with v4l2-ctl --list-devices

Additional Resources