Skip to content

Commit 47bc1b6

Browse files
ddd999ddd999
authored andcommitted
Video: Improvements to photo/video mode
- Enables local still image and video recording by re-organizing the camera subsystem into three distinct modes: streaming video (default), still photo capture, and video capture - Adds Python helper scripts to detect and control cameras for local still/video capture - Now sends a VideoStreamInformation packet whenever video streaming is started - Captures a photo or starts/stops video recording either when the button is pressed on the web UI, or when a MavLink MAV_CMD_DO_DIGICAM_CONTROL message is received - Geotagging: Writes GPS position information (if available) to EXIF metadata of photos
1 parent b851c72 commit 47bc1b6

14 files changed

+4294
-2907
lines changed

deploy/RasPi3-4-5-deploy.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ fi
3434
source /etc/os-release
3535
if [[ "$ID" == "debian" || "$ID" == "raspbian" ]] && [ "$VERSION_CODENAME" == "bookworm" ]; then
3636
sudo apt install -y gstreamer1.0-libcamera
37+
fi
3738

3839
## Only install picamera2 on RaspiOS
3940
sudo apt -y install python3-picamera2 python3-libcamera python3-kms++

deploy/install_common_libraries.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ sudo apt upgrade -y
99
sudo apt install -y gstreamer1.0-plugins-good libgstrtspserver-1.0-0 gir1.2-gst-rtsp-server-1.0 gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad
1010
sudo apt install -y network-manager python3 python3-gst-1.0 python3-pip dnsmasq git jq wireless-tools iw python3-dev gstreamer1.0-x ppp python3-venv
1111

12+
## Install OpenCV for camera management
13+
sudo apt install -y python3-opencv
14+
1215
## Pymavlink
1316
sudo apt install -y python3-lxml python3-numpy
1417

package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/get_camera_caps.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python3
2+
# -*- coding:utf-8 vi:ts=4:noexpandtab -*-
3+
4+
import subprocess
5+
import re
6+
import sys
7+
import json
8+
import os
9+
10+
def check_if_v4l2_ctl_avail():
11+
try:
12+
subprocess.run(['v4l2-ctl', '--help'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
13+
except subprocess.CalledProcessError:
14+
print("v4l2-ctl is not installed. Exiting.")
15+
sys.exit(1)
16+
17+
def get_subdev_paths():
18+
base_path = '/dev'
19+
return sorted([os.path.join(base_path, dev) for dev in os.listdir(base_path) if dev.startswith('v4l-subdev')])
20+
21+
def get_video_device_paths():
22+
base_path = '/dev'
23+
return sorted([os.path.join(base_path, dev) for dev in os.listdir(base_path) if dev.startswith('video')])
24+
25+
def get_mbus_codes(dev_path):
26+
command = f"v4l2-ctl -d {dev_path} --list-subdev-mbus-codes 0"
27+
result = subprocess.run(command, shell=True, text=True, capture_output=True)
28+
if result.returncode != 0:
29+
return []
30+
pattern = r"0x([0-9a-fA-F]+):\s+([A-Za-z0-9_]+)"
31+
return re.findall(pattern, result.stdout)
32+
33+
def get_resolutions(dev_path, mbus_code):
34+
command = f"v4l2-ctl -d {dev_path} --list-subdev-framesizes pad=0,code=0x{mbus_code}"
35+
result = subprocess.run(command, shell=True, text=True, capture_output=True)
36+
if result.returncode != 0:
37+
return []
38+
pattern = r"Size Range: (\d+)x(\d+)"
39+
matches = re.findall(pattern, result.stdout)
40+
return [{'width': int(w), 'height': int(h)} for w, h in matches]
41+
42+
def get_formats_and_resolutions(dev_path):
43+
command = f"v4l2-ctl -d {dev_path} --list-formats-ext"
44+
result = subprocess.run(command, shell=True, text=True, capture_output=True)
45+
if result.returncode != 0:
46+
return []
47+
48+
devices = []
49+
fmt_pattern = r"^\s*\[\d+\]: '(\w+)' \(.*?\)"
50+
size_pattern = r"\s+Size: Discrete (\d+)x(\d+)"
51+
current_fmt = None
52+
53+
for line in result.stdout.splitlines():
54+
fmt_match = re.match(fmt_pattern, line)
55+
if fmt_match:
56+
current_fmt = fmt_match.group(1)
57+
continue
58+
59+
size_match = re.match(size_pattern, line)
60+
if size_match and current_fmt:
61+
width, height = map(int, size_match.groups())
62+
devices.append({
63+
'format': current_fmt,
64+
'width': width,
65+
'height': height,
66+
'label': f"{width}x{height}_{current_fmt}",
67+
'value': f"{current_fmt}_{width}x{height}"
68+
})
69+
70+
return devices
71+
72+
def get_card_name(dev_path):
73+
command = f"v4l2-ctl -d {dev_path} --all"
74+
result = subprocess.run(command, shell=True, text=True, capture_output=True)
75+
if result.returncode != 0:
76+
return None
77+
78+
match = re.search(r"Card type\s+:\s+(.+)", result.stdout)
79+
if match:
80+
return match.group(1).strip()
81+
82+
return None
83+
84+
check_if_v4l2_ctl_avail()
85+
86+
devices = []
87+
88+
# Process CSI cameras
89+
for dev_path in get_subdev_paths():
90+
mbus_codes = get_mbus_codes(dev_path)
91+
if not mbus_codes:
92+
continue
93+
94+
card_name = get_card_name(dev_path) or "Unnamed CSI Camera"
95+
96+
device_caps = {
97+
# Don't specify a device path for CSI cameras,
98+
# but generate a unique ID for them.
99+
'id': f"CSI-{re.sub(r'[^a-zA-Z0-9]', '_', card_name)}",
100+
'device': None,
101+
'type': 'CSI',
102+
'card_name': card_name,
103+
'caps': []
104+
}
105+
106+
for mbus_code, pixel_format in mbus_codes:
107+
resolutions = get_resolutions(dev_path, mbus_code)
108+
109+
for res in resolutions:
110+
fmt = pixel_format.split("MEDIA_BUS_FMT_")[1] if "MEDIA_BUS_FMT_" in pixel_format else pixel_format
111+
cap_info = {
112+
'format': fmt,
113+
'width': res['width'],
114+
'height': res['height'],
115+
'label': f"{res['width']}x{res['height']}_{fmt}",
116+
'value': f"{mbus_code}_{fmt}_{res['width']}x{res['height']}"
117+
}
118+
device_caps['caps'].append(cap_info)
119+
120+
if device_caps['caps']:
121+
devices.append(device_caps)
122+
123+
# Process UVC cameras
124+
for dev_path in get_video_device_paths():
125+
caps = get_formats_and_resolutions(dev_path)
126+
if caps:
127+
devices.append({
128+
'id': dev_path, # use the device path as the unique ID for UVC cameras
129+
'device': dev_path,
130+
'type': 'UVC',
131+
'card_name': get_card_name(dev_path) or "Unnamed UVC Camera",
132+
'caps': caps
133+
})
134+
135+
print(json.dumps(devices, indent=4))

0 commit comments

Comments
 (0)