mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:27:03 +01:00
fix(webcam): keep Logitech C270 at 30 fps in low light via udev rule
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3ea0359748
commit
cf55091e95
@ -0,0 +1,43 @@
|
||||
{
|
||||
"intent": "Logitech C270 stays at 30 fps in Google Meet (Helium) in low light instead of dropping to ~15 fps and looking laggy.",
|
||||
"scope": [
|
||||
"linux_configuration/fixes/fix_webcam_c270.sh",
|
||||
"linux_configuration/fixes/99-c270-fixed-framerate.rules",
|
||||
"linux_configuration/tests/test_fix_webcam_c270.bats",
|
||||
"Non-goal: manual exposure lock; auto exposure stays on"
|
||||
],
|
||||
"changes": [
|
||||
"udev rule sets exposure_dynamic_framerate=0 on the C270 capture node at every add event",
|
||||
"Installer script with install/--remove/--status; installs v4l-utils if missing"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "v4l2-ctl -d /dev/video0 -c exposure_dynamic_framerate=0 (live, Meet open)",
|
||||
"result": "pass",
|
||||
"evidence": "User confirmed Meet video no longer laggy"
|
||||
},
|
||||
{
|
||||
"command": "vm run c270 'fix_webcam_c270.sh; --status; --remove; --status'",
|
||||
"result": "pass",
|
||||
"evidence": "Rule installed then removed in vmbox, rc=0"
|
||||
},
|
||||
{
|
||||
"command": "v4l2-ctl -c exposure_dynamic_framerate=1 && sudo udevadm trigger --action=add --settle /sys/class/video4linux/video0",
|
||||
"result": "pass",
|
||||
"evidence": "Host control read back exposure_dynamic_framerate: 0 after simulated plug-in"
|
||||
},
|
||||
{
|
||||
"command": "bats linux_configuration/tests/test_fix_webcam_c270.bats",
|
||||
"result": "pass",
|
||||
"evidence": "5/5 ok"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Darker, noisier picture in dim rooms (fixed 33 ms max exposure)",
|
||||
"Chromium could start resetting this control in a future version, undoing the rule per stream"
|
||||
],
|
||||
"rollback": [
|
||||
"fix_webcam_c270.sh --remove, then replug the camera",
|
||||
"v4l2-ctl -d /dev/video0 -C exposure_dynamic_framerate reads the firmware value again"
|
||||
]
|
||||
}
|
||||
7
linux_configuration/fixes/99-c270-fixed-framerate.rules
Normal file
7
linux_configuration/fixes/99-c270-fixed-framerate.rules
Normal file
@ -0,0 +1,7 @@
|
||||
# Logitech C270: stop the camera halving its frame rate in dim light.
|
||||
# With exposure_dynamic_framerate=1 (what the firmware comes up with) auto
|
||||
# exposure stretches frames to ~60 ms, i.e. ~15 fps, and Meet looks laggy.
|
||||
# Chromium/Helium never resets this control (its reset batch includes
|
||||
# FOCUS_AUTO, which the C270 lacks, so the whole batch fails), so setting it
|
||||
# once per plug-in is enough. Installed by fixes/fix_webcam_c270.sh.
|
||||
ACTION=="add", SUBSYSTEM=="video4linux", ATTR{index}=="0", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0825", RUN+="/usr/bin/v4l2-ctl -d $devnode -c exposure_dynamic_framerate=0"
|
||||
88
linux_configuration/fixes/fix_webcam_c270.sh
Executable file
88
linux_configuration/fixes/fix_webcam_c270.sh
Executable file
@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Logitech C270: keep a steady 30 fps in low light (udev rule)
|
||||
#
|
||||
# Disables exposure_dynamic_framerate on every plug-in/boot, so auto exposure
|
||||
# can no longer drop the camera to ~15 fps. Trade-off: a darker image in dim
|
||||
# rooms. Rationale lives in 99-c270-fixed-framerate.rules.
|
||||
#
|
||||
# fix_webcam_c270.sh install the rule and apply it now
|
||||
# fix_webcam_c270.sh --remove remove the rule (camera default returns on replug)
|
||||
# fix_webcam_c270.sh --status show rule state and the live control value
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly SCRIPT_NAME SCRIPT_DIR
|
||||
readonly RULE_NAME="99-c270-fixed-framerate.rules"
|
||||
readonly RULE_SRC="$SCRIPT_DIR/$RULE_NAME"
|
||||
readonly RULE_DST="/etc/udev/rules.d/$RULE_NAME"
|
||||
readonly CONTROL="exposure_dynamic_framerate"
|
||||
# Overridable so the tests can point it at a fake sysfs tree.
|
||||
readonly V4L_SYSFS="${C270_V4L_SYSFS:-/sys/class/video4linux}"
|
||||
|
||||
log() { echo "[$SCRIPT_NAME] $*"; }
|
||||
|
||||
# The C270's capture node (index 0); empty when the camera is unplugged.
|
||||
find_device() {
|
||||
local node
|
||||
for node in "$V4L_SYSFS"/video*; do
|
||||
[[ -e "$node" ]] || continue
|
||||
if [[ "$(cat "$node/index")" == 0 ]] &&
|
||||
grep -qx 0825 "$node/device/../idProduct" 2>/dev/null &&
|
||||
grep -qx 046d "$node/device/../idVendor" 2>/dev/null; then
|
||||
echo "/dev/$(basename "$node")"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
install_rule() {
|
||||
pacman -Q v4l-utils &>/dev/null || sudo pacman -S --needed --noconfirm v4l-utils
|
||||
sudo install -m 644 "$RULE_SRC" "$RULE_DST"
|
||||
sudo udevadm control --reload
|
||||
log "installed $RULE_DST"
|
||||
local dev
|
||||
dev="$(find_device)"
|
||||
if [[ -n "$dev" ]]; then
|
||||
v4l2-ctl -d "$dev" -c "$CONTROL=0"
|
||||
log "applied to $dev now"
|
||||
else
|
||||
log "C270 not plugged in; rule applies on next plug-in"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_rule() {
|
||||
sudo rm -f "$RULE_DST"
|
||||
sudo udevadm control --reload
|
||||
log "removed $RULE_DST"
|
||||
}
|
||||
|
||||
show_status() {
|
||||
if [[ -f "$RULE_DST" ]]; then
|
||||
log "rule: installed"
|
||||
else
|
||||
log "rule: not installed"
|
||||
fi
|
||||
local dev
|
||||
dev="$(find_device)"
|
||||
if [[ -n "$dev" ]]; then
|
||||
log "$dev: $(v4l2-ctl -d "$dev" -C "$CONTROL")"
|
||||
else
|
||||
log "C270 not plugged in"
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
"") install_rule ;;
|
||||
--remove) remove_rule ;;
|
||||
--status) show_status ;;
|
||||
-h | --help) sed -n '4,13p' "$0" | sed 's/^# \{0,3\}//' ;;
|
||||
*)
|
||||
log "unknown option: $1"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
74
linux_configuration/tests/test_fix_webcam_c270.bats
Executable file
74
linux_configuration/tests/test_fix_webcam_c270.bats
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bats
|
||||
# Tests for fixes/fix_webcam_c270.sh against a fake sysfs tree and stubbed
|
||||
# sudo/v4l2-ctl/pacman, so nothing touches /etc or a real camera.
|
||||
#
|
||||
# Requires: bats (extra/bats). Run with:
|
||||
# bats linux_configuration/tests/test_fix_webcam_c270.bats
|
||||
|
||||
bats_require_minimum_version 1.5.0
|
||||
|
||||
setup() {
|
||||
REPO_DIR="$(cd -- "$(dirname -- "$BATS_TEST_FILENAME")/.." && pwd)"
|
||||
SCRIPT="$REPO_DIR/fixes/fix_webcam_c270.sh"
|
||||
export C270_V4L_SYSFS="$BATS_TEST_TMPDIR/v4l"
|
||||
mkdir -p "$C270_V4L_SYSFS" "$BATS_TEST_TMPDIR/bin"
|
||||
export CALLS="$BATS_TEST_TMPDIR/calls"
|
||||
# One recording stub, linked under each tool name it stands in for.
|
||||
cat >"$BATS_TEST_TMPDIR/bin/stub" <<'STUB'
|
||||
#!/bin/bash
|
||||
echo "$(basename "$0") $*" >>"$CALLS"
|
||||
STUB
|
||||
chmod +x "$BATS_TEST_TMPDIR/bin/stub"
|
||||
local tool
|
||||
for tool in sudo v4l2-ctl pacman; do
|
||||
ln -s stub "$BATS_TEST_TMPDIR/bin/$tool"
|
||||
done
|
||||
export PATH="$BATS_TEST_TMPDIR/bin:$PATH"
|
||||
}
|
||||
|
||||
# $1 = node name, $2 = index, $3 = vendor, $4 = product
|
||||
fake_node() {
|
||||
local usb="$BATS_TEST_TMPDIR/usb-$1"
|
||||
mkdir -p "$usb/intf" "$C270_V4L_SYSFS/$1"
|
||||
echo "$3" >"$usb/idVendor"
|
||||
echo "$4" >"$usb/idProduct"
|
||||
echo "$2" >"$C270_V4L_SYSFS/$1/index"
|
||||
ln -s "$usb/intf" "$C270_V4L_SYSFS/$1/device"
|
||||
}
|
||||
|
||||
@test "install without a camera installs the rule and skips v4l2-ctl" {
|
||||
run "$SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"not plugged in"* ]]
|
||||
grep -q "sudo install -m 644 .*99-c270-fixed-framerate.rules /etc/udev/rules.d/" "$CALLS"
|
||||
run ! grep -q "^v4l2-ctl" "$CALLS"
|
||||
}
|
||||
|
||||
@test "install applies to the C270 capture node, not its metadata node or other cams" {
|
||||
fake_node video0 0 046d 0999
|
||||
fake_node video2 1 046d 0825
|
||||
fake_node video3 0 046d 0825
|
||||
run "$SCRIPT"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -qx "v4l2-ctl -d /dev/video3 -c exposure_dynamic_framerate=0" "$CALLS"
|
||||
[ "$(grep -c '^v4l2-ctl' "$CALLS")" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "--remove deletes the rule and reloads udev" {
|
||||
run "$SCRIPT" --remove
|
||||
[ "$status" -eq 0 ]
|
||||
grep -qx "sudo rm -f /etc/udev/rules.d/99-c270-fixed-framerate.rules" "$CALLS"
|
||||
grep -qx "sudo udevadm control --reload" "$CALLS"
|
||||
}
|
||||
|
||||
@test "--status reports the live control" {
|
||||
fake_node video0 0 046d 0825
|
||||
run "$SCRIPT" --status
|
||||
[ "$status" -eq 0 ]
|
||||
grep -qx "v4l2-ctl -d /dev/video0 -C exposure_dynamic_framerate" "$CALLS"
|
||||
}
|
||||
|
||||
@test "unknown option exits 2" {
|
||||
run "$SCRIPT" --bogus
|
||||
[ "$status" -eq 2 ]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user