按"硬件问题、配置不变"收尾:记录里写下实测帧率,探针加逐颗验收模式 - What: worker 把本会话实测到的采集速率记进 self._measured_capture_fps(box 与 finger 两条循环都算:finger 的帧率也从"1 秒窗口内计数"改成 (n-1)/span 的速率,两条视图对 fps 的含义必须一致),capture_status() 因此多出 measured_fps —— session.json 的 capture 块现在同时有 requested / negotiated / measured_fps / dropped_frames,一颗摄像头到底交付了多少 fps 由记录自证。status.capture_slow 的中英文案改写:删掉已被实测否证的"降低分辨率/换格式"建议,改成"这是采集端(摄像头/驱动)的问题、软件改不了,换一颗真能交付 30 fps 的(并先确认线材/端口没把它降速)再测"。tools/camera_probe.py 新增 --check 验收模式:只测 bundle 用的那一个格式(MJPG 1920x1080@30,120 帧),按**实测**速率打印一行 PASS/FAIL 并给出退出码,用于逐颗过检;无摄像头时报 "No camera answered on index N (is the app closed?)" 并返回 1。tests:假设备用上 capture_status() 的 measured_fps(0.05 s 粒度等它出现,断言 20–35 fps)、探针 --check 的无摄像头路径。 - Why: 用户判定这是驱动/硬件问题(同型号产品此前能跑 30 fps),决定**配置不变**、以后用能到 30 fps 的产品,并在之后逐颗检测。配置不变意味着应用继续按 30 fps 申请(这是对的:申请 15 只会把问题上移),但由此产生两件必须收尾的事:(1) 提示语里"降低分辨率/换格式"是错的建议——探针已经证明 720p MJPG 一样是 15 fps、YUYV 只有 1 fps;(2) 既然要逐颗检测,就必须有一个能回答"这一颗交付了多少"的权威数字,而驱动自报值不可信(DSHOW 回读),所以把实测值写进记录并给探针一个单行验收模式。 - Verification: shared 全量 161 passed;studio 91 passed;ui_check 554 项检查 0 失败;locale_check 中英键集一致;探针 --check 在无摄像头时打印指定文案并返回 1,--help 列出该选项。 - Not in this change: 未改任何采集配置(resolution_raw/fps/fourcc 都不动);未做自动降级;frames.csv 仍未记录 process_ms/capture_fps(留到加未滤波 17 列、记录格式升版时一起)。
This commit is contained in:
@@ -175,13 +175,56 @@ def size_sweep(index: int, backend_name: str, backend: int, fps: float = 30.0,
|
||||
return rows
|
||||
|
||||
|
||||
def check_format(
|
||||
index: int,
|
||||
*,
|
||||
request: tuple[str, int, int, float] = REQUESTS[0],
|
||||
samples: int = 120,
|
||||
) -> int:
|
||||
"""Acceptance mode: does this camera deliver the bundle's format?
|
||||
|
||||
One line, one exit code, for going through units in turn. The request is the
|
||||
one the sensor bundles make (MJPG 1920x1080@30); the verdict is the *measured*
|
||||
rate, because the reported one is an echo.
|
||||
"""
|
||||
fourcc, width, height, fps = request
|
||||
for backend_name, backend in BACKENDS:
|
||||
result = probe(index, backend_name, backend, request, samples=samples)
|
||||
if result is None:
|
||||
continue
|
||||
measured = result["measured_fps"]
|
||||
asked = f"{fourcc} {width}x{height}@{fps:g}"
|
||||
if measured >= fps * TOLERANCE:
|
||||
print(f"PASS camera {index}: {asked} -> {measured:.1f} fps "
|
||||
f"[{backend_name}, {result['fourcc']}]")
|
||||
return 0
|
||||
print(
|
||||
f"FAIL camera {index}: {asked} -> {measured:.1f} fps "
|
||||
f"(reported {result['reported_fps']:.1f}, {result['fourcc']}, "
|
||||
f"{backend_name})"
|
||||
)
|
||||
return 1
|
||||
print(f"No camera answered on index {index} (is the app closed?).")
|
||||
return 1
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("--index", type=int, default=None, help="camera index to probe")
|
||||
parser.add_argument("--max-index", type=int, default=4, help="highest index to try")
|
||||
parser.add_argument("--samples", type=int, default=SAMPLES, help="frames per row")
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="acceptance mode: measure the bundle's format once, exit 0 when it "
|
||||
"delivers it (use this to go through units)",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.check:
|
||||
return check_format(args.index if args.index is not None else 0,
|
||||
samples=args.samples)
|
||||
|
||||
indices = [args.index] if args.index is not None else list(range(args.max_index + 1))
|
||||
found = 0
|
||||
for index in indices:
|
||||
|
||||
Reference in New Issue
Block a user