Skip to content

fix(uvc): detect capture interfaces beyond index0 - #364

Merged
mryel00 merged 1 commit into
mainsail-crew:developfrom
grigory51:fix/uvc-multi-interface
Aug 2, 2026
Merged

mryel00 merged 1 commit into
mainsail-crew:developfrom
grigory51:fix/uvc-multi-interface

Conversation

@grigory51

@grigory51 grigory51 commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #363.

What changed

UVC discovery now registers symlinked V4L2 nodes that enumerate capture formats instead of assuming the usable interface always ends in index0.

This supports composite cameras such as Creality CAM, where index0 exposes H264/HEVC and index2 exposes MJPEG, while excluding metadata-only nodes with no capture formats.

Validation

  • Reproduced with Creality CAM 1d6c:0103
  • Confirmed MJPEG capture from video-index2 using v4l2-ctl
  • Confirmed updated crowsnest starts ustreamer and streams from video-index2 on the physical hardware
  • python -m compileall -q crowsnest
  • ruff format --check
  • ruff check
  • basedpyright

AI disclosure

This PR was prepared with assistance from OpenAI Codex. I reproduced and tested the issue on the physical Creality CAM hardware, reviewed the proposed change, and verified the resulting service behavior.

@mryel00

mryel00 commented Aug 1, 2026

Copy link
Copy Markdown
Member

Thanks for the PR. It's actually a very clean solution ^^
Please also add a small notice that this PR was done with AI, if you used it (at least your text kinda looks like AI).

I knew of those Creality cams exposing two different nodes but as I don't own one, I didn't bother to log them correctly as I would have to assume things.
Can you please share the resulting log file that I see the correct logging?

@grigory51

grigory51 commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Can you please share the resulting log file that I see the correct logging?

ozhegov@orange-pi-zero3:~$ sudo systemctl restart crowsnest
ozhegov@orange-pi-zero3:~$   log=/home/pi/printer_data/logs/crowsnest.log
  start=$(sudo wc -l "$log" | awk '{print $1}')

  sudo systemctl restart crowsnest
  sleep 5

  sudo tail -n "+$((start + 1))" "$log" > ~/crowsnest-pr364.log
ozhegov@orange-pi-zero3:~$ cat ~/crowsnest-pr364.log
[02/08/26 02:10:09] Received signal 15. Shutting down...
[02/08/26 02:10:10] INFO: [cam 1] exited with code 0
[02/08/26 02:10:10] Shutdown or Killed by User!
[02/08/26 02:10:10] Please come again :)
[02/08/26 02:10:10] Goodbye...
[02/08/26 02:10:10] crowsnest - A webcam Service for multiple Cams and Stream Services.
[02/08/26 02:10:10] Version: v5.0.10-1-g47e5b34
[02/08/26 02:10:10] Prepare Startup ...
[02/08/26 02:10:10] INFO: Host Information:
[02/08/26 02:10:10]       Distribution: Armbian 26.5.1 trixie
[02/08/26 02:10:10]       Release: MainsailOS release 3.0.0 (trixie)
[02/08/26 02:10:10]       Kernel: Linux 6.18.33-current-sunxi64 aarch64
[02/08/26 02:10:10]       Model: Unknown
[02/08/26 02:10:10]       Available CPU Cores: 4
[02/08/26 02:10:10]       Available Memory: 4013648 kB
[02/08/26 02:10:10]       Diskspace (avail. / total): 22G / 28G
[02/08/26 02:10:10] INFO: Found Streamer:
[02/08/26 02:10:10]       ustreamer: /usr/bin/ustreamer
[02/08/26 02:10:10]       camera-streamer: /usr/bin/camera-streamer
[02/08/26 02:10:10] INFO: Print Configfile: '/home/pi/printer_data/config/crowsnest.conf'
[02/08/26 02:10:10]       [crowsnest]
[02/08/26 02:10:10]       log_level: verbose
[02/08/26 02:10:10]       rollover_on_start: false
[02/08/26 02:10:10]       no_proxy: false
[02/08/26 02:10:10]
[02/08/26 02:10:10]       [cam 1]
[02/08/26 02:10:10]       mode: ustreamer
[02/08/26 02:10:10]       port: 8080
[02/08/26 02:10:10]       device: /dev/v4l/by-id/usb-Creality_3D_Technology_CREALITY_CAM_00000000-video-index2
[02/08/26 02:10:10]       resolution: 640x480
[02/08/26 02:10:10]       max_fps: 25
[02/08/26 02:10:10]       custom_flags: --format=MJPEG
[02/08/26 02:10:10] INFO: Detect available Devices
[02/08/26 02:10:10] INFO: Found 2 total available Device(s)
[02/08/26 02:10:10] INFO: Found 2 available v4l2 (UVC) camera(s)
[02/08/26 02:10:10] /dev/v4l/by-id/usb-Creality_3D_Technology_CREALITY_CAM_00000000-video-index2 -> /dev/video3
[02/08/26 02:10:10] Supported Formats:
[02/08/26 02:10:10]       [0]: 'MJPG' (Motion-JPEG, compressed):
[02/08/26 02:10:10]               Size: Discrete 320x240
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.040s (25.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.040s (25.000 fps)
[02/08/26 02:10:10]               Size: Discrete 640x360
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.040s (25.000 fps)
[02/08/26 02:10:10]               Size: Discrete 720x576
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.040s (25.000 fps)
[02/08/26 02:10:10]               Size: Discrete 640x480
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.040s (25.000 fps)
[02/08/26 02:10:10] Supported Controls:
[02/08/26 02:10:10]       User Controls:
[02/08/26 02:10:10]             brightness (int)                   : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]             contrast (int)                     : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]             saturation (int)                   : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]             white_balance_automatic (bool)     : default=1 value=1
[02/08/26 02:10:10]             gain (int)                         : min=0 max=100 step=1 default=5 value=5 flags=has-min-max
[02/08/26 02:10:10]             power_line_frequency (menu)        : default=2 value=1
[02/08/26 02:10:10]                   0: Disabled
[02/08/26 02:10:10]                   1: 50 Hz
[02/08/26 02:10:10]                   2: 60 Hz
[02/08/26 02:10:10]             white_balance_temperature (int)    : min=0 max=255 step=1 default=128 value=128 flags=inactive, has-min-max
[02/08/26 02:10:10]             sharpness (int)                    : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]
[02/08/26 02:10:10]       Camera Controls:
[02/08/26 02:10:10]             auto_exposure (menu)               : default=0 value=0
[02/08/26 02:10:10]                   0: Auto Mode
[02/08/26 02:10:10]                   1: Manual Mode
[02/08/26 02:10:10]                   2: Shutter Priority Mode
[02/08/26 02:10:10]                   3: Aperture Priority Mode
[02/08/26 02:10:10]             exposure_time_absolute (int)       : min=0 max=6500 step=1 default=100 value=100 flags=inactive, has-min-max
[02/08/26 02:10:10]
[02/08/26 02:10:10] /dev/v4l/by-id/usb-Creality_3D_Technology_CREALITY_CAM_00000000-video-index0 -> /dev/video1
[02/08/26 02:10:10] Supported Formats:
[02/08/26 02:10:10]       [0]: 'H264' (H.264, compressed):
[02/08/26 02:10:10]               Size: Discrete 1920x1080
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]               Size: Discrete 1280x720
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]               Size: Discrete 640x480
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]       [1]: 'HEVC' (HEVC, compressed):
[02/08/26 02:10:10]               Size: Discrete 1920x1080
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]               Size: Discrete 1280x720
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10]               Size: Discrete 640x480
[02/08/26 02:10:10]                       Interval: Discrete 0.033s (30.000 fps)
[02/08/26 02:10:10] Supported Controls:
[02/08/26 02:10:
10]       User Controls:
[02/08/26 02:10:10]             brightness (int)                   : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]             contrast (int)                     : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]             saturation (int)                   : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]             white_balance_automatic (bool)     : default=1 value=1
[02/08/26 02:10:10]             gain (int)                         : min=0 max=100 step=1 default=5 value=5 flags=has-min-max
[02/08/26 02:10:10]             power_line_frequency (menu)        : default=2 value=1
[02/08/26 02:10:10]                   0: Disabled
[02/08/26 02:10:10]                   1: 50 Hz
[02/08/26 02:10:10]                   2: 60 Hz
[02/08/26 02:10:10]             white_balance_temperature (int)    : min=0 max=255 step=1 default=128 value=128 flags=inactive, has-min-max
[02/08/26 02:10:10]             sharpness (int)                    : min=0 max=255 step=1 default=128 value=128 flags=has-min-max
[02/08/26 02:10:10]
[02/08/26 02:10:10]       Camera Controls:
[02/08/26 02:10:10]             auto_exposure (menu)               : default=0 value=0
[02/08/26 02:10:10]                   0: Auto Mode
[02/08/26 02:10:10]                   1: Manual Mode
[02/08/26 02:10:10]                   2: Shutter Priority Mode
[02/08/26 02:10:10]                   3: Aperture Priority Mode
[02/08/26 02:10:10]             exposure_time_absolute (int)       : min=0 max=6500 step=1 default=100 value=100 flags=inactive, has-min-max
[02/08/26 02:10:10]
[02/08/26 02:10:10] Try to parse configured Cams / Services...
[02/08/26 02:10:10] [cam 1]: Parse configuration ...
[02/08/26 02:10:10] [cam 1]: Configuration looks good. Continue ...
[02/08/26 02:10:10] Try to start configured Cams / Services ...
[02/08/26 02:10:10] [cam 1]: Start ustreamer with device /dev/v4l/by-id/usb-Creality_3D_Technology_CREALITY_CAM_00000000-video-index2 ...
[02/08/26 02:10:10] ... Done!

Thanks for the thoughtful review and for giving an AI-assisted contribution a fair chance.

@mryel00 mryel00 linked an issue Aug 2, 2026 that may be closed by this pull request
@mryel00

mryel00 commented Aug 2, 2026

Copy link
Copy Markdown
Member

Thanks for [...] giving an AI-assisted contribution a fair chance

As long as I won't get too much AI PRs and the PR text is not a wall of text, I will consider them if they make sense.
Crowsnest is luckily a very small project and the contributions are quite few, so it's manageable ^^

As a note to your config: --format=MJPEG shouldn't be needed. If the cam only works if you add it, there is a bug.

@mryel00
mryel00 merged commit 7b4059c into mainsail-crew:develop Aug 2, 2026
4 checks passed
@mryel00 mryel00 changed the title fix(camera): detect capture interfaces beyond index0 fix(uvc): detect capture interfaces beyond index0 Aug 2, 2026
@grigory51

Copy link
Copy Markdown
Contributor Author

As a note to your config: --format=MJPEG shouldn't be needed. If the cam only works if you add it, there is a bug.

Leftovers from the investigation of the problem.

@grigory51
grigory51 deleted the fix/uvc-multi-interface branch August 24, 2026 18:11
@mryel00

mryel00 commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Sry that it took so long to release. I completely forgot this PR 🙈

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UVC capture interfaces other than video-index0 are not detected

2 participants