把摄像头协商结果记进 session.json,并在达不到配置帧率时明说 - What: sdk_worker 新增 capture_status_of(sensor)(把采集源的 config 与 status 合成 {requested:{width,height,fps,fourcc}, negotiated:{width,height,fps,fourcc,backend}, dropped_frames})与 short_capture_fallacy(status)(当协商帧率低于申请值 90% 时给出一句话描述,含分辨率/实际帧率/申请帧率/格式/后端;两者都齐全时才说话);两个 worker 都加 capture_status() 访问器(box 取当前 sensor,finger 取 runtime.tactile.raw_sensor)。app_window:session.json 新增顶层 capture 块(_capture_metadata(),走同一个访问器);新增 _check_capture_negotiation()——每个会话在驱动第一次报出速率后检查一次,达不到就通过 _notify 明确提示(不是每帧刷屏),新键 status.capture_slow(中英)写明"软件改不了、记录按实际帧率保存、请降分辨率/换格式或换摄像头"。tests/test_worker_frame_rate.py:假设备补上 CaptureConfig 与 CaptureStatus 的 width/height/fps/fourcc/backend_name 字段,新增 test_a_low_camera_rate_is_recorded_as_a_negotiation(1920x1080、申请 30、协商 16.67 时 capture_status() 两半都在、描述里含 1920x1080/16.67/30;协商到位时描述为空串)。 - Why: 用户按上一轮的读数报告"还是摄像头 16.7",即摄像头自身只发布 16.7 —— 这排除了流水线(每帧只 12 ms)与应用侧节拍,剩下的只能是采集格式协商:配置申请的是 resolution_raw 1920x1080 @ 30 fps(fourcc 走 CaptureConfig 默认 MJPG)。这类"驱动只提供这个格式"的情况软件无法改变,但记录里必须留下证据、操作员必须被告知,否则一个 16.7 的采集和一次正常的 30 fps 采集在文件里长得一样,事后再也无法解释。 - Verification: shared 全量 161 passed(新增 1 项);studio 90 passed;ui_check 554 项检查 0 失败;locale_check 287 字面键全部命中、中英键集一致(343+1=344? 见下方键数)。 - Not in this change: 未改 SDK 的采集参数(仍是 1920x1080/30/MJPG、后端 auto);frames.csv 仍未记录 process_ms/capture_fps(下次改记录格式时一并加);未做自动降级(例如检测到 16.7 就自动改用更低分辨率)——那会改变传感器几何,必须由人决定。
This commit is contained in:
@@ -31,6 +31,23 @@ class _Status:
|
||||
def __init__(self) -> None:
|
||||
self.last_seq = 0
|
||||
self.dropped_frames = 0
|
||||
#: What the driver reported back for the negotiated stream.
|
||||
self.width = 1920
|
||||
self.height = 1080
|
||||
self.fps = 30.0
|
||||
self.fourcc = "MJPG"
|
||||
self.backend_name = "MSMF"
|
||||
|
||||
|
||||
class _CaptureConfig:
|
||||
"""The requested stream, as ``CaptureConfig`` carries it."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.width = 1920
|
||||
self.height = 1080
|
||||
self.fps = 30.0
|
||||
self.fourcc = "MJPG"
|
||||
self.source = 0
|
||||
|
||||
|
||||
class _CaptureSource:
|
||||
@@ -45,6 +62,7 @@ class _CaptureSource:
|
||||
|
||||
def __init__(self, period_s: float = FRAME_PERIOD_S) -> None:
|
||||
self.status = _Status()
|
||||
self.config = _CaptureConfig()
|
||||
self.running = True
|
||||
self._period = float(period_s)
|
||||
self._stop = threading.Event()
|
||||
@@ -265,6 +283,45 @@ def test_the_camera_s_own_rate_is_reported_next_to_ours(monkeypatch):
|
||||
monkeypatch.setattr(sys.modules[__name__], "PROCESS_COST_S", 0.0)
|
||||
|
||||
|
||||
def test_a_low_camera_rate_is_recorded_as_a_negotiation(monkeypatch):
|
||||
"""A camera that cannot do the configured rate must be visible as such.
|
||||
|
||||
The pipeline cannot turn 16.7 fps into 30, so the only honest thing left is to
|
||||
record what was asked for and what the driver gave, and to have a one-line
|
||||
description ready for the operator.
|
||||
"""
|
||||
from orisys_app_shared.acquisition.sdk_worker import (
|
||||
SdkAcquisitionWorker,
|
||||
short_capture_fallacy,
|
||||
)
|
||||
|
||||
CREATED.clear()
|
||||
saved = _install()
|
||||
worker = SdkAcquisitionWorker()
|
||||
try:
|
||||
worker.start(0, config_ref=BOX_CONFIG)
|
||||
deadline = time.perf_counter() + 2.0
|
||||
while time.perf_counter() < deadline and not CREATED:
|
||||
time.sleep(0.01)
|
||||
assert CREATED, "the fake sensor was never constructed"
|
||||
source = CREATED[-1].capture_source
|
||||
source.status.fps = 16.67 # what the driver actually negotiated
|
||||
status = worker.capture_status()
|
||||
assert status["requested"]["fps"] == 30.0
|
||||
assert status["requested"]["width"] == 1920
|
||||
assert status["negotiated"]["fps"] == 16.67, status
|
||||
assert status["negotiated"]["fourcc"] == "MJPG"
|
||||
assert status["negotiated"]["backend"] == "MSMF"
|
||||
detail = short_capture_fallacy(status)
|
||||
assert "1920x1080" in detail and "16.67" in detail and "30" in detail, detail
|
||||
# A driver that gives what was asked for says nothing at all.
|
||||
source.status.fps = 30.0
|
||||
assert short_capture_fallacy(worker.capture_status()) == ""
|
||||
finally:
|
||||
worker.stop(wait=True)
|
||||
_restore(saved)
|
||||
|
||||
|
||||
def test_a_file_source_runs_on_the_video_s_own_timeline():
|
||||
"""A pass over a video is stamped with the video's time, not the wall clock."""
|
||||
from orisys_app_shared.acquisition.sdk_worker import SdkAcquisitionWorker
|
||||
|
||||
Reference in New Issue
Block a user