use crate::usb::error::{UsbError, UsbResult};
use crate::usb::host::dwc2::ep0 as dwc2_ep0;
use crate::usb::host::dwc2::ep0::{DMA_OFF_UVC_BULK, UVC_BULK_DMA_CAP};
use crate::usb::setup;
const VS_PROBE_CONTROL: u8 = 0x01;
const VS_COMMIT_CONTROL: u8 = 0x02;
const USB_DT_CONFIGURATION: u8 = 2;
const USB_DT_INTERFACE: u8 = 4;
const USB_DT_ENDPOINT: u8 = 5;
const CS_INTERFACE: u8 = 0x24;
const VS_FORMAT_MJPEG: u8 = 0x06;
const VS_FRAME_MJPEG: u8 = 0x07;
const VS_FORMAT_UNCOMPRESSED: u8 = 0x04;
const VS_FRAME_UNCOMPRESSED: u8 = 0x05;
const USB_CLASS_VIDEO: u8 = 0x0e;
const USB_SUBCLASS_VIDEO_STREAMING: u8 = 0x02;
const USB_SUBCLASS_VIDEO_CONTROL: u8 = 0x01;
const VC_HEADER: u8 = 0x01;
const VC_INPUT_TERMINAL: u8 = 0x02;
const VC_PROCESSING_UNIT: u8 = 0x05;
const ITT_CAMERA: u16 = 0x0201;
#[allow(dead_code)] const PU_BACKLIGHT_COMPENSATION: u8 = 0x01;
#[allow(dead_code)] const PU_BRIGHTNESS_CONTROL: u8 = 0x02;
#[allow(dead_code)] const PU_CONTRAST_CONTROL: u8 = 0x03;
#[allow(dead_code)] const PU_GAIN_CONTROL: u8 = 0x04;
#[allow(dead_code)] const PU_HUE_CONTROL: u8 = 0x06;
#[allow(dead_code)] const PU_SATURATION_CONTROL: u8 = 0x07;
#[allow(dead_code)] const PU_SHARPNESS_CONTROL: u8 = 0x08;
const PU_WHITE_BALANCE_TEMPERATURE_CONTROL: u8 = 0x0A;
const PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL: u8 = 0x0B;
const PU_HUE_AUTO_CONTROL: u8 = 0x10;
const PU_POWER_LINE_FREQUENCY_CONTROL: u8 = 0x05;
const CT_AE_MODE_CONTROL: u8 = 0x02;
const CT_AE_PRIORITY_CONTROL: u8 = 0x03;
#[allow(dead_code)] const CT_EXPOSURE_TIME_ABSOLUTE_CONTROL: u8 = 0x04;
const CT_FOCUS_AUTO_CONTROL: u8 = 0x08;
const ENDPOINT_ATTR_ISOCH: u8 = 1;
const ENDPOINT_ATTR_BULK: u8 = 2;
const UVC_PROBE_COMMIT_LEN: usize = 34;
#[inline]
fn uvc_set_cur_vs(interface: u8, selector: u8, w_length: u16) -> [u8; 8] {
let wvalue = u16::from(selector) << 8;
[
0x21,
0x01,
wvalue as u8,
(wvalue >> 8) as u8,
interface,
0,
w_length as u8,
(w_length >> 8) as u8,
]
}
#[inline]
fn uvc_get_cur_vs(interface: u8, selector: u8, w_length: u16) -> [u8; 8] {
let wvalue = u16::from(selector) << 8;
[
0xA1,
0x81,
wvalue as u8,
(wvalue >> 8) as u8,
interface,
0,
w_length as u8,
(w_length >> 8) as u8,
]
}
#[inline]
fn uvc_get_max_vs(interface: u8, selector: u8, w_length: u16) -> [u8; 8] {
let wvalue = u16::from(selector) << 8;
[
0xA1,
0x83,
wvalue as u8,
(wvalue >> 8) as u8,
interface,
0,
w_length as u8,
(w_length >> 8) as u8,
]
}
#[inline]
fn uvc_set_cur_vc(interface: u8, entity_id: u8, selector: u8, w_length: u16) -> [u8; 8] {
let wvalue = u16::from(selector) << 8;
let windex = (u16::from(entity_id) << 8) | u16::from(interface);
[
0x21,
0x01,
wvalue as u8,
(wvalue >> 8) as u8,
windex as u8,
(windex >> 8) as u8,
w_length as u8,
(w_length >> 8) as u8,
]
}
#[inline]
fn uvc_get_cur_vc(interface: u8, entity_id: u8, selector: u8, w_length: u16) -> [u8; 8] {
let wvalue = u16::from(selector) << 8;
let windex = (u16::from(entity_id) << 8) | u16::from(interface);
[
0xA1,
0x81,
wvalue as u8,
(wvalue >> 8) as u8,
windex as u8,
(windex >> 8) as u8,
w_length as u8,
(w_length >> 8) as u8,
]
}
#[inline]
fn uvc_get_def_vc(interface: u8, entity_id: u8, selector: u8, w_length: u16) -> [u8; 8] {
let wvalue = u16::from(selector) << 8;
let windex = (u16::from(entity_id) << 8) | u16::from(interface);
[
0xA1,
0x87,
wvalue as u8,
(wvalue >> 8) as u8,
windex as u8,
(windex >> 8) as u8,
w_length as u8,
(w_length >> 8) as u8,
]
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UvcXferKind {
Bulk,
Isoch,
}
#[derive(Clone, Debug)]
pub struct UvcStreamSelection {
pub vs_interface: u8,
pub alt_setting: u8,
pub ep_num: u8,
pub mps_raw: u16,
pub xfer: UvcXferKind,
pub format_index: u8,
pub frame_index: u8,
pub frame_interval: u32,
pub is_mjpeg: bool,
pub frame_w: u16,
pub frame_h: u16,
pub negotiated_payload_size: u32,
pub negotiated_frame_size: u32,
pub isoch_alts_count: u8,
pub isoch_alts: [(u8, u16); 8],
}
pub fn read_configuration_descriptor(dev: u32, ep0_mps: u32, cfg_index: u8) -> UsbResult<[u8; 4096]> {
let mut hdr = [0u8; 9];
dwc2_ep0::ep0_control_read(
dev,
setup::get_descriptor_configuration(cfg_index, 9),
ep0_mps,
&mut hdr,
)?;
if hdr[1] != USB_DT_CONFIGURATION {
return Err(UsbError::Protocol("not a configuration descriptor"));
}
let total = u16::from_le_bytes([hdr[2], hdr[3]]) as usize;
if total > 4096 {
return Err(UsbError::Protocol("configuration descriptor too large (>4096)"));
}
let mut buf = [0u8; 4096];
dwc2_ep0::ep0_control_read(
dev,
setup::get_descriptor_configuration(cfg_index, total as u16),
ep0_mps,
&mut buf[..total],
)?;
Ok(buf)
}
#[inline]
fn max_packet_11(mps_raw: u16) -> u32 {
u32::from(mps_raw & 0x7FF)
}
#[inline]
#[allow(dead_code)]
fn isoch_total_bytes_per_uframe(mps_raw: u16) -> usize {
let mb = (mps_raw & 0x7FF) as usize;
let mult = ((mps_raw >> 11) & 3) as usize + 1;
mb.saturating_mul(mult)
}
pub fn parse_uvc_video_stream(cfg: &[u8], cfg_total: usize) -> UsbResult<UvcStreamSelection> {
let len = cfg_total.min(cfg.len());
if len < 12 {
return Err(UsbError::Protocol("cfg too short"));
}
let mut i = usize::from(cfg[0]);
if i >= len {
return Err(UsbError::Protocol("bad cfg bLength"));
}
let mut cur_ifc_class = 0u8;
let mut cur_ifc_sub = 0u8;
let mut cur_ifc_num = 0u8;
let mut cur_alt = 0u8;
let mut last_fmt_subtype = 0u8;
let mut last_fmt_ix = 0u8;
let mut best_bulk: Option<(u8, u8, u16, u8)> = None;
let mut best_isoch: Option<(u8, u8, u16, u8)> = None;
let mut isoch_alts: [(u8, u16); 8] = [(0u8, 0u16); 8];
let mut isoch_alts_count: usize = 0;
let mut mjpeg_pick: Option<(u8, u8, u16, u16, u32)> = None;
let mut uncomp_pick: Option<(u8, u8, u16, u16, u32)> = None;
let mut cur_fmt_ix_for_frame = 0u8;
let mut cur_fmt_subtype_for_frame = 0u8;
while i + 2 <= len {
let bl = cfg[i] as usize;
if bl < 2 || i + bl > len {
break;
}
let ty = cfg[i + 1];
if ty == USB_DT_INTERFACE && bl >= 9 {
cur_ifc_num = cfg[i + 2];
cur_alt = cfg[i + 3];
cur_ifc_class = cfg[i + 5];
cur_ifc_sub = cfg[i + 6];
} else if ty == CS_INTERFACE
&& cur_ifc_class == USB_CLASS_VIDEO
&& cur_ifc_sub == USB_SUBCLASS_VIDEO_STREAMING
{
let st = cfg.get(i + 2).copied().unwrap_or(0);
if (st == VS_FORMAT_MJPEG || st == VS_FORMAT_UNCOMPRESSED) && bl >= 4 {
last_fmt_subtype = st;
last_fmt_ix = cfg[i + 3];
cur_fmt_subtype_for_frame = st;
cur_fmt_ix_for_frame = cfg[i + 3];
log::info!("UVC: VS-fmt if={cur_ifc_num} alt={cur_alt} ix={} subtype={:#04x} ({})",
last_fmt_ix, st,
if st == VS_FORMAT_MJPEG { "MJPEG" } else { "Uncompressed" });
}
if (st == VS_FRAME_MJPEG || st == VS_FRAME_UNCOMPRESSED) && bl >= 26 {
let frame_ix = cfg[i + 3];
let w = u16::from_le_bytes([cfg[i + 5], cfg[i + 6]]);
let h = u16::from_le_bytes([cfg[i + 7], cfg[i + 8]]);
let dflt_ival = u32::from_le_bytes([cfg[i + 21], cfg[i + 22], cfg[i + 23], cfg[i + 24]]);
let ival_type = cfg[i + 25];
let mut min_ival = dflt_ival;
if ival_type == 0 && bl >= 38 {
let dw_min = u32::from_le_bytes([cfg[i + 26], cfg[i + 27], cfg[i + 28], cfg[i + 29]]);
if dw_min > 0 { min_ival = dw_min; }
} else if ival_type > 0 {
let n = ival_type as usize;
let mut p = i + 26;
for _ in 0..n {
if p + 4 > i + bl { break; }
let v = u32::from_le_bytes([cfg[p], cfg[p + 1], cfg[p + 2], cfg[p + 3]]);
if v > 0 && v < min_ival { min_ival = v; }
p += 4;
}
}
let fps_x100 = if dflt_ival > 0 { 100_000_000_u32 / dflt_ival.max(1) } else { 0 };
let fps_min_x100 = if min_ival > 0 { 100_000_000_u32 / min_ival.max(1) } else { 0 };
log::info!("UVC: VS-frame fmt_ix={cur_fmt_ix_for_frame} frame_ix={frame_ix} {}x{} iv_dflt={dflt_ival} ({}.{:02} fps) iv_min={min_ival} ({}.{:02} fps) ival_type={ival_type}",
w, h,
fps_x100 / 100, fps_x100 % 100,
fps_min_x100 / 100, fps_min_x100 % 100);
let dflt_ival = if min_ival > 0 { min_ival } else { dflt_ival };
let pick = (cur_fmt_ix_for_frame, frame_ix, w, h, dflt_ival);
let is_mjpeg = cur_fmt_subtype_for_frame == VS_FORMAT_MJPEG
|| st == VS_FRAME_MJPEG;
fn rank((_, _, pw, ph, _): (u8, u8, u16, u16, u32)) -> i32 {
let w = pw as i32;
let h = ph as i32;
let area = w * h;
let pref_max =
PREFERRED_MAX_PIXELS.load(core::sync::atomic::Ordering::Relaxed) as i32;
if pref_max > 0 {
return if area <= pref_max {
area.saturating_add(1_000_000)
} else {
(-(area - pref_max)).saturating_sub(1_000)
};
}
if w == 1280 && h == 720 { return 1_000_000; }
if w == 640 && h == 480 { return 900_000; }
if w == 800 && h == 600 { return 800_000; }
if w == 1024 && h == 768 { return 750_000; }
if w == 320 && h == 240 { return 700_000; }
if area <= 1280 * 720 { 600_000 - (1280 * 720 - area) } else { 100_000 - (area - 1280 * 720) }
}
if is_mjpeg {
let pick_better = match mjpeg_pick {
None => true,
Some(prev) => rank(pick) > rank(prev),
};
if pick_better { mjpeg_pick = Some(pick); }
} else {
let pick_better = match uncomp_pick {
None => true,
Some(prev) => rank(pick) > rank(prev),
};
if pick_better { uncomp_pick = Some(pick); }
}
let _ = last_fmt_subtype;
let _ = last_fmt_ix;
}
} else if ty == USB_DT_ENDPOINT
&& cur_ifc_class == USB_CLASS_VIDEO
&& cur_ifc_sub == USB_SUBCLASS_VIDEO_STREAMING
{
let ep_addr = cfg[i + 2];
let attr = cfg[i + 3];
let mps_raw = u16::from_le_bytes([cfg[i + 4], cfg[i + 5]]);
let mps = mps_raw & 0x7FF;
let mult = ((mps_raw >> 11) & 0x3) + 1;
let xfer = attr & 0x03;
if (ep_addr & 0x80) == 0 {
i += bl;
continue;
}
let ep_num = ep_addr & 0x0F;
let total = u32::from(mps) * u32::from(mult);
log::info!("UVC: VS-cand if={cur_ifc_num} alt={cur_alt} ep={ep_num} kind={} mps={mps} mult={mult} total={total}/uframe mps_raw={mps_raw:#06x}",
if xfer == ENDPOINT_ATTR_BULK { "Bulk" } else if xfer == ENDPOINT_ATTR_ISOCH { "Isoch" } else { "Other" });
if xfer == ENDPOINT_ATTR_BULK {
let tak = (cur_alt, ep_num, mps_raw, cur_ifc_num);
best_bulk = Some(match best_bulk {
None => tak,
Some(b) => if mps > (b.2 & 0x7FF) { tak } else { b },
});
} else if xfer == ENDPOINT_ATTR_ISOCH {
let tak = (cur_alt, ep_num, mps_raw, cur_ifc_num);
let new_mult = mult;
best_isoch = Some(match best_isoch {
None => tak,
Some(b) => {
let old_mps = b.2 & 0x7FF;
let old_mult = ((b.2 >> 11) & 0x3) + 1;
let new_score = if new_mult == 1 {
10_000_000u32 + u32::from(mps)
} else {
u32::from(mps) * u32::from(new_mult)
};
let old_score = if old_mult == 1 {
10_000_000u32 + u32::from(old_mps)
} else {
u32::from(old_mps) * u32::from(old_mult)
};
if new_score > old_score { tak } else { b }
}
});
if isoch_alts_count < isoch_alts.len() {
isoch_alts[isoch_alts_count] = (cur_alt, mps_raw);
isoch_alts_count += 1;
}
}
}
i += bl;
}
let (alt, epn, mps_raw, vs_if, kind) = if let Some((a, e, m, v)) = best_bulk {
(a, e, m, v, UvcXferKind::Bulk)
} else if let Some((a, e, m, v)) = best_isoch {
(a, e, m, v, UvcXferKind::Isoch)
} else {
return Err(UsbError::NotImplemented);
};
let (fmt_ix, frame_ix, frame_w, frame_h, interval, is_mjpeg) = match mjpeg_pick {
Some((fi, frix, w, h, iv)) => (fi, frix, w, h, iv, true),
None => match uncomp_pick {
Some((fi, frix, w, h, iv)) => (fi, frix, w, h, iv, false),
None => (1, 1, 0, 0, 333_333, false),
},
};
log::info!("UVC: SEL if={vs_if} alt={alt} ep={epn} {:?} mps_raw={:#06x} fmt_ix={fmt_ix} frame_ix={frame_ix} {}x{} iv={interval} mjpeg={is_mjpeg}",
kind, mps_raw, frame_w, frame_h);
Ok(UvcStreamSelection {
vs_interface: vs_if,
alt_setting: alt,
ep_num: epn,
mps_raw,
xfer: kind,
format_index: fmt_ix,
frame_index: frame_ix,
frame_interval: interval,
is_mjpeg,
frame_w,
frame_h,
negotiated_payload_size: 0,
negotiated_frame_size: 0,
isoch_alts_count: isoch_alts_count as u8,
isoch_alts,
})
}
fn reselect_isoch_alt_for_payload(sel: &mut UvcStreamSelection) {
if sel.xfer != UvcXferKind::Isoch || sel.isoch_alts_count == 0 {
return;
}
let need = sel.negotiated_payload_size;
if need == 0 {
return;
}
let alts = &sel.isoch_alts[..sel.isoch_alts_count as usize];
let mut best_fit: Option<(u8, u16, u32)> = None;
let mut best_max: Option<(u8, u16, u32)> = None;
for &(alt, mps_raw) in alts {
let mps = u32::from(mps_raw & 0x7FF);
let mult = u32::from((mps_raw >> 11) & 0x3) + 1;
if mult > 1 {
continue;
}
let total = mps;
if total >= need {
let pick = (alt, mps_raw, total);
best_fit = Some(match best_fit {
None => pick,
Some(p) if p.2 > total => pick,
Some(p) => p,
});
}
let pick = (alt, mps_raw, total);
best_max = Some(match best_max {
None => pick,
Some(p) if p.2 < total => pick,
Some(p) => p,
});
}
let (new_alt, new_mps_raw, new_total) = best_fit
.or(best_max)
.unwrap_or((sel.alt_setting, sel.mps_raw, 0));
if new_alt != sel.alt_setting || new_mps_raw != sel.mps_raw {
log::info!("UVC: re-select Isoch alt {} (mps_raw={:#06x}, {} B/uframe) -> alt {} (mps_raw={:#06x}, {} B/uframe) for payload={} (mult>1 skipped)",
sel.alt_setting, sel.mps_raw,
u32::from(sel.mps_raw & 0x7FF) * (u32::from((sel.mps_raw >> 11) & 0x3) + 1),
new_alt, new_mps_raw, new_total, need);
sel.alt_setting = new_alt;
sel.mps_raw = new_mps_raw;
}
}
#[derive(Clone, Debug, Default)]
pub struct UvcControlEntities {
pub vc_interface: u8,
pub camera_terminal_id: Option<u8>,
pub ct_controls: u32,
pub processing_unit_id: Option<u8>,
pub pu_controls: u32,
}
pub fn parse_uvc_control_entities(cfg: &[u8], cfg_total: usize) -> Option<UvcControlEntities> {
let len = cfg_total.min(cfg.len());
if len < 12 {
return None;
}
let mut i = usize::from(cfg[0]);
if i >= len {
return None;
}
let mut cur_ifc_class = 0u8;
let mut cur_ifc_sub = 0u8;
let mut cur_ifc_num;
let mut out = UvcControlEntities::default();
let mut found_vc = false;
while i + 2 <= len {
let bl = cfg[i] as usize;
if bl < 2 || i + bl > len {
break;
}
let ty = cfg[i + 1];
if ty == USB_DT_INTERFACE && bl >= 9 {
cur_ifc_num = cfg[i + 2];
cur_ifc_class = cfg[i + 5];
cur_ifc_sub = cfg[i + 6];
if cur_ifc_class == USB_CLASS_VIDEO && cur_ifc_sub == USB_SUBCLASS_VIDEO_CONTROL {
out.vc_interface = cur_ifc_num;
found_vc = true;
}
} else if ty == CS_INTERFACE
&& cur_ifc_class == USB_CLASS_VIDEO
&& cur_ifc_sub == USB_SUBCLASS_VIDEO_CONTROL
&& bl >= 3
{
let st = cfg[i + 2];
match st {
VC_HEADER => {}
VC_INPUT_TERMINAL
if bl >= 15 => {
let id = cfg[i + 3];
let tt = u16::from_le_bytes([cfg[i + 4], cfg[i + 5]]);
if tt == ITT_CAMERA {
out.camera_terminal_id = Some(id);
let csize = cfg[i + 14] as usize;
let cmax = csize.min(bl.saturating_sub(15)).min(4);
let mut bm = 0u32;
for k in 0..cmax {
bm |= u32::from(cfg[i + 15 + k]) << (8 * k);
}
out.ct_controls = bm;
}
}
VC_PROCESSING_UNIT
if bl >= 9 => {
let id = cfg[i + 3];
let csize = cfg[i + 7] as usize;
let cmax = csize.min(bl.saturating_sub(8)).min(4);
let mut bm = 0u32;
for k in 0..cmax {
bm |= u32::from(cfg[i + 8 + k]) << (8 * k);
}
out.processing_unit_id = Some(id);
out.pu_controls = bm;
}
_ => {}
}
}
i += bl;
}
if found_vc { Some(out) } else { None }
}
fn try_set_cur_u8(
dev: u32,
ep0_mps: u32,
vc_if: u8,
entity: u8,
selector: u8,
value: u8,
) -> bool {
let setup = uvc_set_cur_vc(vc_if, entity, selector, 1);
let buf = [value];
dwc2_ep0::ep0_control_write(dev, setup, ep0_mps, &buf).is_ok()
}
#[allow(dead_code)]
fn try_get_cur_u8(
dev: u32,
ep0_mps: u32,
vc_if: u8,
entity: u8,
selector: u8,
) -> Option<u8> {
let setup = uvc_get_cur_vc(vc_if, entity, selector, 1);
let mut buf = [0u8; 1];
if dwc2_ep0::ep0_control_read(dev, setup, ep0_mps, &mut buf).is_ok() {
Some(buf[0])
} else {
None
}
}
#[allow(dead_code)]
fn try_get_cur_u16(
dev: u32,
ep0_mps: u32,
vc_if: u8,
entity: u8,
selector: u8,
) -> Option<u16> {
let setup = uvc_get_cur_vc(vc_if, entity, selector, 2);
let mut buf = [0u8; 2];
if dwc2_ep0::ep0_control_read(dev, setup, ep0_mps, &mut buf).is_ok() {
Some(u16::from_le_bytes(buf))
} else {
None
}
}
fn try_get_def_u8(
dev: u32,
ep0_mps: u32,
vc_if: u8,
entity: u8,
selector: u8,
) -> Option<u8> {
let setup = uvc_get_def_vc(vc_if, entity, selector, 1);
let mut buf = [0u8; 1];
if dwc2_ep0::ep0_control_read(dev, setup, ep0_mps, &mut buf).is_ok() {
Some(buf[0])
} else {
None
}
}
fn try_get_def_u16(
dev: u32,
ep0_mps: u32,
vc_if: u8,
entity: u8,
selector: u8,
) -> Option<u16> {
let setup = uvc_get_def_vc(vc_if, entity, selector, 2);
let mut buf = [0u8; 2];
if dwc2_ep0::ep0_control_read(dev, setup, ep0_mps, &mut buf).is_ok() {
Some(u16::from_le_bytes(buf))
} else {
None
}
}
fn try_set_cur_u16(
dev: u32,
ep0_mps: u32,
vc_if: u8,
entity: u8,
selector: u8,
value: u16,
) -> bool {
let setup = uvc_set_cur_vc(vc_if, entity, selector, 2);
let buf = value.to_le_bytes();
dwc2_ep0::ep0_control_write(dev, setup, ep0_mps, &buf).is_ok()
}
struct PuCtrl {
bit: u32,
selector: u8,
width: u8,
name: &'static str,
override_val: Option<u16>,
}
fn pu_apply_one(dev: u32, ep0_mps: u32, vc_if: u8, pu: u8, bm: u32, ctrl: PuCtrl) {
if (bm & (1u32 << ctrl.bit)) == 0 {
return;
}
let want_src = ctrl.override_val.map(|_| "override").unwrap_or("def");
match ctrl.width {
1 => {
let cur = try_get_cur_u8(dev, ep0_mps, vc_if, pu, ctrl.selector);
let want = match ctrl.override_val {
Some(v) => Some(v as u8),
None => try_get_def_u8(dev, ep0_mps, vc_if, pu, ctrl.selector),
};
match (cur, want) {
(Some(c), Some(d)) if c != d => {
let ok = try_set_cur_u8(dev, ep0_mps, vc_if, pu, ctrl.selector, d);
log::info!(
"UVC: PU.{} {c} -> {d} ({want_src}, {})",
ctrl.name,
if ok { "ok" } else { "set 失败" }
);
}
(None, _) => log::warn!("UVC: PU.{} GET_CUR 失败", ctrl.name),
_ => {}
}
}
2 => {
let cur = try_get_cur_u16(dev, ep0_mps, vc_if, pu, ctrl.selector);
let want = match ctrl.override_val {
Some(v) => Some(v),
None => try_get_def_u16(dev, ep0_mps, vc_if, pu, ctrl.selector),
};
match (cur, want) {
(Some(c), Some(d)) if c != d => {
let ok = try_set_cur_u16(dev, ep0_mps, vc_if, pu, ctrl.selector, d);
log::info!(
"UVC: PU.{} {c} -> {d} ({want_src}, {})",
ctrl.name,
if ok { "ok" } else { "set 失败" }
);
}
(None, _) => log::warn!("UVC: PU.{} GET_CUR 失败", ctrl.name),
_ => {}
}
}
_ => {}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct UvcImageTuning {
pub brightness: Option<u16>,
pub contrast: Option<u16>,
pub hue: Option<u16>,
pub saturation: Option<u16>,
pub sharpness: Option<u16>,
pub gamma: Option<u16>,
pub backlight: Option<u16>,
pub gain: Option<u16>,
pub white_balance_temp_k: Option<u16>,
pub power_line_freq: Option<u8>,
}
pub fn uvc_init_camera_controls(
dev: u32,
ep0_mps: u32,
ent: &UvcControlEntities,
tune: &UvcImageTuning,
) -> UsbResult<()> {
log::info!(
"UVC: VC if={} CT={:?} (bm={:#010x}) PU={:?} (bm={:#010x})",
ent.vc_interface,
ent.camera_terminal_id,
ent.ct_controls,
ent.processing_unit_id,
ent.pu_controls
);
if let Some(pu) = ent.processing_unit_id {
let vc_if = ent.vc_interface;
let bm = ent.pu_controls;
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 0, selector: PU_BRIGHTNESS_CONTROL, width: 2, name: "Brightness", override_val: tune.brightness });
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 1, selector: PU_CONTRAST_CONTROL, width: 2, name: "Contrast", override_val: tune.contrast });
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 2, selector: PU_HUE_CONTROL, width: 2, name: "Hue", override_val: tune.hue });
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 3, selector: PU_SATURATION_CONTROL, width: 2, name: "Saturation", override_val: tune.saturation });
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 4, selector: PU_SHARPNESS_CONTROL, width: 2, name: "Sharpness", override_val: tune.sharpness });
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 5, selector: 0x09, width: 2, name: "Gamma", override_val: tune.gamma });
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 8, selector: PU_BACKLIGHT_COMPENSATION, width: 2, name: "Backlight", override_val: tune.backlight });
pu_apply_one(dev, ep0_mps, vc_if, pu, bm, PuCtrl { bit: 9, selector: PU_GAIN_CONTROL, width: 2, name: "Gain", override_val: tune.gain });
match tune.white_balance_temp_k {
Some(k) if (bm & (1 << 6)) != 0 => {
if (bm & (1 << 12)) != 0 {
let _ = try_set_cur_u8(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL, 0);
}
let _ = try_set_cur_u16(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_CONTROL, k);
log::info!("UVC: PU.WB = {k}K (manual)");
}
_ => {
if (bm & (1 << 12)) != 0 {
let _ = try_set_cur_u8(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL, 0);
if (bm & (1 << 6)) != 0
&& let Some(d) = try_get_def_u16(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_CONTROL) {
let _ = try_set_cur_u16(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_CONTROL, d);
}
let _ = try_set_cur_u8(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL, 1);
let cur_t = try_get_cur_u16(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_CONTROL).unwrap_or(0);
log::info!("UVC: PU.WB = Auto (cur {cur_t}K)");
} else if (bm & (1 << 6)) != 0 {
let val = try_get_def_u16(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_CONTROL).unwrap_or(4500);
let _ = try_set_cur_u16(dev, ep0_mps, vc_if, pu, PU_WHITE_BALANCE_TEMPERATURE_CONTROL, val);
log::info!("UVC: PU.WB = {val}K (no-auto)");
}
}
}
if (bm & (1 << 11)) != 0 {
let _ = try_set_cur_u8(dev, ep0_mps, vc_if, pu, PU_HUE_AUTO_CONTROL, 1);
}
if (bm & (1 << 10)) != 0 {
let plf = tune.power_line_freq.unwrap_or(1);
let _ = try_set_cur_u8(dev, ep0_mps, vc_if, pu, PU_POWER_LINE_FREQUENCY_CONTROL, plf);
}
}
if let Some(ct) = ent.camera_terminal_id {
if (ent.ct_controls & (1 << 1)) != 0 {
let mut applied = 0u8;
for &mode in &[0x02u8, 0x08, 0x04] {
if try_set_cur_u8(dev, ep0_mps, ent.vc_interface, ct, CT_AE_MODE_CONTROL, mode) {
applied = mode;
break;
}
}
log::info!("UVC: CT.AeMode = {applied:#04x}");
if (ent.ct_controls & (1 << 2)) != 0 {
let _ = try_set_cur_u8(
dev,
ep0_mps,
ent.vc_interface,
ct,
CT_AE_PRIORITY_CONTROL,
1,
);
}
}
if (ent.ct_controls & (1 << 17)) != 0 {
let _ = try_set_cur_u8(
dev,
ep0_mps,
ent.vc_interface,
ct,
CT_FOCUS_AUTO_CONTROL,
1,
);
}
}
Ok(())
}
fn build_probe_commit_payload(sel: &UvcStreamSelection) -> [u8; UVC_PROBE_COMMIT_LEN] {
let mut b = [0u8; UVC_PROBE_COMMIT_LEN];
b[0] = 0x01;
b[1] = 0x00;
b[2] = sel.format_index;
b[3] = sel.frame_index;
b[4..8].copy_from_slice(&sel.frame_interval.to_le_bytes());
let w = u32::from(sel.frame_w.max(640));
let h = u32::from(sel.frame_h.max(480));
let est = if sel.is_mjpeg { w.saturating_mul(h) } else { w.saturating_mul(h).saturating_mul(2) };
b[18..22].copy_from_slice(&est.to_le_bytes());
let pkt_total = u32::from(sel.mps_raw & 0x7FF) * (u32::from((sel.mps_raw >> 11) & 0x3) + 1);
b[22..26].copy_from_slice(&pkt_total.to_le_bytes());
b
}
fn dump_probe(prefix: &str, p: &[u8]) {
if p.len() < 26 { return; }
let bm_hint = u16::from_le_bytes([p[0], p[1]]);
let fmt_ix = p[2];
let frame_ix = p[3];
let interval = u32::from_le_bytes([p[4], p[5], p[6], p[7]]);
let key_frm = u16::from_le_bytes([p[8], p[9]]);
let pframe = u16::from_le_bytes([p[10], p[11]]);
let comp_q = u16::from_le_bytes([p[12], p[13]]);
let comp_w = u16::from_le_bytes([p[14], p[15]]);
let delay = u16::from_le_bytes([p[16], p[17]]);
let max_video = u32::from_le_bytes([p[18], p[19], p[20], p[21]]);
let max_pkt = u32::from_le_bytes([p[22], p[23], p[24], p[25]]);
log::info!("UVC: {prefix} bmHint={bm_hint:#06x} fmt={fmt_ix} frame={frame_ix} iv={interval} keyFrm={key_frm} pFrm={pframe} compQ={comp_q} compW={comp_w} delay={delay} dwMaxVideoFrameSize={max_video} dwMaxPayloadTransferSize={max_pkt}");
}
pub fn uvc_start_video_stream(dev: u32, ep0_mps: u32, sel: &mut UvcStreamSelection) -> UsbResult<()> {
reset_frame_continuity();
let _ = dwc2_ep0::ep0_control_write_no_data(
dev,
setup::set_interface(0, sel.vs_interface),
ep0_mps,
);
let probe_init = build_probe_commit_payload(sel);
dump_probe("PROBE.SET", &probe_init);
dwc2_ep0::ep0_control_write(
dev,
uvc_set_cur_vs(sel.vs_interface, VS_PROBE_CONTROL, UVC_PROBE_COMMIT_LEN as u16),
ep0_mps,
&probe_init,
)?;
let mut probe_max = [0u8; UVC_PROBE_COMMIT_LEN];
if dwc2_ep0::ep0_control_read(
dev,
uvc_get_max_vs(sel.vs_interface, VS_PROBE_CONTROL, UVC_PROBE_COMMIT_LEN as u16),
ep0_mps,
&mut probe_max,
).is_ok() {
dump_probe("PROBE.MAX", &probe_max);
}
let mut probe = [0u8; UVC_PROBE_COMMIT_LEN];
dwc2_ep0::ep0_control_read(
dev,
uvc_get_cur_vs(sel.vs_interface, VS_PROBE_CONTROL, UVC_PROBE_COMMIT_LEN as u16),
ep0_mps,
&mut probe,
)?;
dump_probe("PROBE.CUR", &probe);
sel.negotiated_payload_size = u32::from_le_bytes([probe[22], probe[23], probe[24], probe[25]]);
sel.negotiated_frame_size = u32::from_le_bytes([probe[18], probe[19], probe[20], probe[21]]);
reselect_isoch_alt_for_payload(sel);
let alt_mps = u32::from(sel.mps_raw & 0x7FF)
* (u32::from((sel.mps_raw >> 11) & 0x3) + 1);
if alt_mps > 0 && alt_mps < sel.negotiated_payload_size {
log::info!("UVC: clamping COMMIT dwMaxPayloadTransferSize {} -> {} to match alt bandwidth",
sel.negotiated_payload_size, alt_mps);
sel.negotiated_payload_size = alt_mps;
probe[22..26].copy_from_slice(&alt_mps.to_le_bytes());
}
dwc2_ep0::ep0_control_write(
dev,
uvc_set_cur_vs(sel.vs_interface, VS_COMMIT_CONTROL, UVC_PROBE_COMMIT_LEN as u16),
ep0_mps,
&probe,
)?;
dwc2_ep0::ep0_control_write_no_data(
dev,
setup::set_interface(sel.alt_setting, sel.vs_interface),
ep0_mps,
)?;
log::info!("UVC: streaming armed if={} alt={} negotiated_payload={} frame_size={}",
sel.vs_interface, sel.alt_setting, sel.negotiated_payload_size, sel.negotiated_frame_size);
Ok(())
}
pub fn uvc_stop_streaming(dev: u32, ep0_mps: u32, vs_if: u8) -> UsbResult<()> {
reset_frame_continuity();
dwc2_ep0::ep0_control_write_no_data(dev, setup::set_interface(0, vs_if), ep0_mps)
}
#[inline]
fn uvc_payload_header_len(data: &[u8]) -> Option<usize> {
if data.is_empty() {
return None;
}
let hlen = data[0] as usize;
if hlen == 0 || hlen > data.len() {
return None;
}
Some(hlen)
}
pub const UVC_WORK_AREA_BYTES: usize = 65536;
pub const UVC_ASSEMBLED_JPEG_DMA_OFF: usize = DMA_OFF_UVC_BULK + UVC_WORK_AREA_BYTES;
fn parse_uvc_packet(pkt: &[u8]) -> (bool, usize, Option<u8>, u8) {
if pkt.len() < 2 {
return (false, 0, None, 0);
}
let hlen = pkt[0] as usize;
if hlen < 2 || hlen > pkt.len() {
return (false, 0, None, 0);
}
let info = pkt[1];
let cur_fid = info & 0x01;
let payload_len = pkt.len() - hlen;
let eof = (info & 0x02) != 0;
(eof, payload_len, Some(cur_fid), info)
}
enum FrameState {
WaitFirstSwitch { last_fid: Option<u8> },
Capturing { frame_fid: u8, saw_data: bool },
}
pub static LAST_EOF_FID: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0xFF);
#[inline]
pub fn reset_frame_continuity() {
LAST_EOF_FID.store(0xFF, core::sync::atomic::Ordering::Relaxed);
}
struct CapturingPacket<'a> {
pkt: &'a [u8],
payload_len: usize,
eof: bool,
cur_fid: u8,
info: u8,
jpeg_len: &'a mut usize,
jpeg_cap: usize,
}
fn process_packet(
pkt: &[u8],
state: &mut FrameState,
jpeg_len: &mut usize,
jpeg_cap: usize,
debug_remaining: &mut u32,
) -> UsbResult<bool> {
let (eof, payload_len, fid_opt, info) = parse_uvc_packet(pkt);
if *debug_remaining > 0 {
log::info!("UVC-pkt uf={} len={} hlen={} info={:#04x} fid={:?} eof={} payload={}",
dwc2_ep0::current_uframe(),
pkt.len(), if pkt.is_empty() { 0 } else { pkt[0] as usize },
info, fid_opt, eof, payload_len);
*debug_remaining -= 1;
}
let Some(cur_fid) = fid_opt else {
return Ok(false);
};
match state {
FrameState::WaitFirstSwitch { last_fid } => {
match *last_fid {
None => *last_fid = Some(cur_fid),
Some(prev) if prev != cur_fid => {
*state = FrameState::Capturing { frame_fid: cur_fid, saw_data: false };
let p = CapturingPacket { pkt, payload_len, eof, cur_fid, info, jpeg_len, jpeg_cap };
return process_packet_capturing(state, p);
}
_ => {}
}
Ok(false)
}
FrameState::Capturing { .. } => {
let p = CapturingPacket { pkt, payload_len, eof, cur_fid, info, jpeg_len, jpeg_cap };
process_packet_capturing(state, p)
}
}
}
fn process_packet_capturing(state: &mut FrameState, p: CapturingPacket<'_>) -> UsbResult<bool> {
let FrameState::Capturing { frame_fid, saw_data } = state else {
return Ok(false);
};
if p.cur_fid != *frame_fid {
let has_eoi = *p.jpeg_len >= 2
&& dwc2_ep0::dma_rx_slice(UVC_ASSEMBLED_JPEG_DMA_OFF + *p.jpeg_len - 2, 2)
.map(|t| t[0] == 0xff && t[1] == 0xd9)
.unwrap_or(false);
if FRAME_DEBUG.load(core::sync::atomic::Ordering::Relaxed) {
log::info!("UVC-trace FID-flip uf={} frame_fid={} new_fid={} saw_data={} jpeg_len={} has_eoi={}",
dwc2_ep0::current_uframe(), *frame_fid, p.cur_fid, *saw_data, *p.jpeg_len, has_eoi);
}
if *saw_data && has_eoi {
return Ok(true);
}
*p.jpeg_len = 0;
*frame_fid = p.cur_fid;
*saw_data = false;
}
if p.payload_len > 0 {
let hlen = p.pkt[0] as usize;
let payload = &p.pkt[hlen..];
if !*saw_data && (payload.len() < 2 || payload[0] != 0xff || payload[1] != 0xd8) {
return Ok(false);
}
if p.jpeg_len.checked_add(payload.len()).unwrap_or(usize::MAX) > p.jpeg_cap {
return Err(UsbError::Hardware("video assemble overflow"));
}
dwc2_ep0::dma_write_at(UVC_ASSEMBLED_JPEG_DMA_OFF + *p.jpeg_len, payload)?;
*p.jpeg_len += payload.len();
*saw_data = true;
}
let has_eoi_now = *p.jpeg_len >= 2
&& dwc2_ep0::dma_rx_slice(UVC_ASSEMBLED_JPEG_DMA_OFF + *p.jpeg_len - 2, 2)
.map(|t| t[0] == 0xff && t[1] == 0xd9)
.unwrap_or(false);
let frame_done = p.eof && *saw_data && has_eoi_now;
if p.eof && FRAME_DEBUG.load(core::sync::atomic::Ordering::Relaxed) {
log::info!("UVC-trace EOF uf={} fid={} info={:#04x} payload={} saw_data={} jpeg_len={} has_eoi={} done={}",
dwc2_ep0::current_uframe(), p.cur_fid, p.info, p.payload_len, *saw_data, *p.jpeg_len, has_eoi_now, frame_done);
}
if p.eof && *saw_data && !has_eoi_now {
*p.jpeg_len = 0;
*saw_data = false;
*state = FrameState::WaitFirstSwitch { last_fid: Some(p.cur_fid) };
return Ok(false);
}
Ok(frame_done)
}
pub static FRAME_DEBUG: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
pub static PREFERRED_MAX_PIXELS: core::sync::atomic::AtomicU32 =
core::sync::atomic::AtomicU32::new(0);
pub fn set_preferred_max_pixels(p: u32) {
PREFERRED_MAX_PIXELS.store(p, core::sync::atomic::Ordering::Relaxed);
}
pub fn uvc_capture_one_frame(dev: u32, ep0_mps: u32, sel: &UvcStreamSelection) -> UsbResult<usize> {
let _ = ep0_mps;
let _ = uvc_payload_header_len; let ep = u32::from(sel.ep_num);
let maxp = max_packet_11(sel.mps_raw).max(1);
let mps_low = maxp as usize;
let mult = (((sel.mps_raw >> 11) & 0x3) as u32 + 1).clamp(1, 3) as usize;
let jpeg_cap = UVC_BULK_DMA_CAP.saturating_sub(UVC_WORK_AREA_BYTES);
let mut jpeg_len = 0usize;
let mut transfers = 0u32;
let mut data_transfers = 0u32;
let work_off = DMA_OFF_UVC_BULK;
let prev_eof_fid = LAST_EOF_FID.load(core::sync::atomic::Ordering::Relaxed);
let mut state = FrameState::WaitFirstSwitch {
last_fid: if prev_eof_fid <= 1 { Some(prev_eof_fid) } else { None },
};
let mut debug_remaining: u32 = 0;
let frame_dbg = FRAME_DEBUG.load(core::sync::atomic::Ordering::Relaxed);
let uf_start = if frame_dbg { dwc2_ep0::current_uframe() } else { 0 };
let mut uf_first_switch: u32 = uf_start;
let mut uframes_at_switch: u32 = 0;
match sel.xfer {
UvcXferKind::Bulk => {
let chunk = (mps_low.saturating_mul(16)).min(UVC_WORK_AREA_BYTES);
if chunk == 0 {
return Err(UsbError::Protocol("bad chunk"));
}
let mut pid = dwc2_ep0::PID_DATA0;
loop {
let actual = dwc2_ep0::bulk_in(dev, ep, maxp, pid, chunk, work_off)?;
if actual == 0 {
return Err(UsbError::Protocol("bulk IN 0 bytes"));
}
transfers = transfers.wrapping_add(1);
pid = if pid == dwc2_ep0::PID_DATA0 {
dwc2_ep0::PID_DATA1
} else {
dwc2_ep0::PID_DATA0
};
let slice = dwc2_ep0::dma_rx_slice(work_off, actual).ok_or(UsbError::Hardware("dma view"))?;
let eof = process_packet(slice, &mut state, &mut jpeg_len, jpeg_cap, &mut debug_remaining)?;
if eof {
if frame_dbg {
log::info!("UVC: frame {} bytes ({} bulk transfers)", jpeg_len, transfers);
}
return Ok(jpeg_len);
}
if transfers > 60_000 {
return Err(UsbError::Timeout);
}
}
}
UvcXferKind::Isoch => {
const MAX_UFRAMES: u32 = 80_000;
for _ in 0..MAX_UFRAMES {
transfers = transfers.wrapping_add(1);
let actual = dwc2_ep0::isoch_in_uframe(dev, ep, sel.mps_raw, work_off)?;
if actual == 0 {
continue;
}
data_transfers = data_transfers.wrapping_add(1);
let slice = dwc2_ep0::dma_rx_slice(work_off, actual).ok_or(UsbError::Hardware("dma view"))?;
let was_waiting = matches!(state, FrameState::WaitFirstSwitch { .. });
let eof = if mult == 1 {
process_packet(slice, &mut state, &mut jpeg_len, jpeg_cap, &mut debug_remaining)?
} else {
let mut hit_eof = false;
let mut off = 0usize;
while off < slice.len() {
let end = if slice.len() - off >= mps_low { off + mps_low } else { slice.len() };
let pkt = &slice[off..end];
off = end;
if process_packet(pkt, &mut state, &mut jpeg_len, jpeg_cap, &mut debug_remaining)? {
hit_eof = true;
break;
}
}
hit_eof
};
if frame_dbg && was_waiting && matches!(state, FrameState::Capturing { .. }) {
uf_first_switch = dwc2_ep0::current_uframe();
uframes_at_switch = transfers;
}
if eof {
if let FrameState::Capturing { frame_fid, .. } = state {
LAST_EOF_FID.store(frame_fid, core::sync::atomic::Ordering::Relaxed);
}
if frame_dbg {
let uf_end = dwc2_ep0::current_uframe();
let dwait = uf_first_switch.wrapping_sub(uf_start) & 0xffff;
let dcap = uf_end.wrapping_sub(uf_first_switch) & 0xffff;
log::info!("UVC: frame {} bytes ({} loops, {} data; HFNUM dwait={} uf ({}.{} ms / {} loops), dcap={} uf ({}.{} ms / {} loops), mult={})",
jpeg_len, transfers, data_transfers,
dwait, dwait / 8, (dwait % 8) * 125 / 10, uframes_at_switch,
dcap, dcap / 8, (dcap % 8) * 125 / 10, transfers - uframes_at_switch,
mult);
}
return Ok(jpeg_len);
}
}
log::info!("UVC: capture timeout after {} uframes ({} data; {} bytes assembled, mult={})",
transfers, data_transfers, jpeg_len, mult);
Err(UsbError::Timeout)
}
}
}