@@ -129,12 +129,12 @@ def static_capabilities(cls) -> dict[str, SupportLevel]:
129129 caps = super ().static_capabilities ()
130130 caps .update (
131131 {
132- "set_resolution" : SupportLevel .SUPPORTED ,
133- "set_fps" : SupportLevel .BEST_EFFORT ,
132+ "set_resolution" : SupportLevel .BEST_EFFORT , # see tolerance values in OpenCVOptions
133+ "set_fps" : SupportLevel .BEST_EFFORT , # ditto
134134 "set_exposure" : SupportLevel .UNSUPPORTED ,
135135 "set_gain" : SupportLevel .UNSUPPORTED ,
136- "device_discovery" : SupportLevel .SUPPORTED ,
137- "stable_identity" : SupportLevel .SUPPORTED ,
136+ "device_discovery" : SupportLevel .SUPPORTED , # uses opencv2-enumerate-cameras
137+ "stable_identity" : SupportLevel .SUPPORTED , # to get VID/PID/path
138138 }
139139 )
140140 return caps
@@ -245,6 +245,16 @@ def actual_resolution(self) -> tuple[int, int] | None:
245245 return (self ._actual_width , self ._actual_height )
246246 return None
247247
248+ @property
249+ def actual_exposure (self ) -> None :
250+ """Not supported by OpenCV backend."""
251+ return None
252+
253+ @property
254+ def actual_gain (self ) -> None :
255+ """Not supported by OpenCV backend."""
256+ return None
257+
248258 # ----------------------------
249259 # Internal helpers
250260 # ----------------------------
@@ -280,8 +290,8 @@ def _get_requested_resolution(self) -> tuple[int, int]:
280290 except Exception :
281291 pass
282292
283- # 3) default
284- return (720 , 540 )
293+ # 3) default -> auto (0,0)
294+ return (0 , 0 )
285295
286296 def _apply_resolution_policy (
287297 self ,
@@ -367,76 +377,115 @@ def _configure_capture(self) -> None:
367377 self ._codec_str = self ._read_codec_string ()
368378 logger .info (f"Camera using codec: { self ._codec_str } " )
369379
370- # --- Resolution (explicit request) ---
380+ # Requested values
371381 req_w , req_h = self ._requested_resolution
372382 enforce_aspect = opt .enforce_aspect
383+ requested_fps = float (self .settings .fps or 0.0 )
384+
385+ # -------------------------
386+ # Resolution
387+ # -------------------------
388+ # If Auto (0,0), do NOT set resolution. Just read device defaults.
389+ if req_w <= 0 or req_h <= 0 :
390+ # Some backends only populate width/height after a few grabs.
391+ try :
392+ # for _ in range(3):
393+ self ._capture .grab ()
394+ except Exception :
395+ pass
373396
374- if not self ._fast_start :
375- # verified, robust path
397+ self ._actual_width = int (self ._capture .get (cv2 .CAP_PROP_FRAME_WIDTH ) or 0 )
398+ self ._actual_height = int (self ._capture .get (cv2 .CAP_PROP_FRAME_HEIGHT ) or 0 )
399+ self ._actual_fps = float (self ._capture .get (cv2 .CAP_PROP_FPS ) or 0.0 )
400+
401+ # For clarity in logs
402+ logger .info ("Resolution requested=Auto, actual=%sx%s" , self ._actual_width , self ._actual_height )
403+
404+ elif not self ._fast_start :
405+ # Verified, robust path (tries candidates + verifies)
376406 result = apply_mode_with_verification (
377407 self ._capture ,
378408 ModeRequest (
379409 width = req_w ,
380410 height = req_h ,
381- fps = float ( self . settings . fps or 0.0 ) ,
411+ fps = requested_fps ,
382412 enforce_aspect = enforce_aspect ,
383413 aspect_tol = float (opt .aspect_tol ),
384414 area_tol = float (opt .area_tol ),
385415 ),
386416 )
387417 self ._actual_width , self ._actual_height , self ._actual_fps = result .width , result .height , result .fps
418+
388419 else :
389420 # fast-start: best-effort set (no heavy negotiation)
390- if req_w > 0 and req_h > 0 :
391- try :
392- self ._capture .set (cv2 .CAP_PROP_FRAME_WIDTH , float (req_w ))
393- self ._capture .set (cv2 .CAP_PROP_FRAME_HEIGHT , float (req_h ))
394- except Exception as exc :
395- logger .debug (f"Fast-start resolution set failed: { exc } " )
421+ try :
422+ self ._capture .set (cv2 .CAP_PROP_FRAME_WIDTH , float (req_w ))
423+ self ._capture .set (cv2 .CAP_PROP_FRAME_HEIGHT , float (req_h ))
424+ except Exception as exc :
425+ logger .debug (f"Fast-start resolution set failed: { exc } " )
396426
397427 self ._actual_width = int (self ._capture .get (cv2 .CAP_PROP_FRAME_WIDTH ) or 0 )
398428 self ._actual_height = int (self ._capture .get (cv2 .CAP_PROP_FRAME_HEIGHT ) or 0 )
399429
430+ # Compute actual_res tuple if known
400431 actual_res = None
401432 if (self ._actual_width or 0 ) > 0 and (self ._actual_height or 0 ) > 0 :
402433 actual_res = (int (self ._actual_width ), int (self ._actual_height ))
403434
404435 logger .info (
405- "Resolution requested=%sx%s, actual=%s" ,
406- req_w ,
407- req_h ,
436+ "Resolution requested=%s, actual=%s" ,
437+ f"{ req_w } x{ req_h } " if (req_w > 0 and req_h > 0 ) else "Auto" ,
408438 f"{ actual_res [0 ]} x{ actual_res [1 ]} " if actual_res else "unknown" ,
409439 )
410440
411- # enforce mismatch policy (warn/strict/accept)
412- self ._apply_resolution_policy (
413- requested = (req_w , req_h ),
414- actual = actual_res ,
415- policy = opt .resolution_policy ,
416- )
441+ # Enforce mismatch policy only if a real request was made
442+ if req_w > 0 and req_h > 0 :
443+ self ._apply_resolution_policy (
444+ requested = (req_w , req_h ),
445+ actual = actual_res ,
446+ policy = opt .resolution_policy ,
447+ )
417448
418- # optional persistence of "what worked"
449+ # Optional persistence of "what worked"
419450 if opt .persist_last_applied_resolution and actual_res :
420451 ns = self .settings .properties .setdefault (self .OPTIONS_KEY , {})
421452 ns ["last_applied_resolution" ] = [actual_res [0 ], actual_res [1 ]]
422453
423- # --- FPS (keep your current logic) ---
424- requested_fps = float (self .settings .fps or 0.0 )
425- if not self ._fast_start and requested_fps > 0.0 :
426- current_fps = float (self ._capture .get (cv2 .CAP_PROP_FPS ) or 0.0 )
454+ # -------------------------
455+ # FPS (best-effort always)
456+ # -------------------------
457+ # IMPORTANT CHANGE:
458+ # Try to set FPS even in fast_start (best-effort). Many drivers ignore it,
459+ # and CAP_PROP_FPS often reads back 0, but at least we attempt consistently.
460+ if requested_fps > 0.0 :
461+ try :
462+ current_fps = float (self ._capture .get (cv2 .CAP_PROP_FPS ) or 0.0 )
463+ except Exception :
464+ current_fps = 0.0
465+
466+ # Only attempt if clearly different or unknown
427467 if current_fps <= 0.0 or abs (current_fps - requested_fps ) > 0.1 :
428- if not self ._capture .set (cv2 .CAP_PROP_FPS , requested_fps ):
429- logger .debug (f"Device ignored FPS set to { requested_fps :.2f} " )
430- self ._actual_fps = float (self ._capture .get (cv2 .CAP_PROP_FPS ) or 0.0 )
431- else :
468+ try :
469+ ok = self ._capture .set (cv2 .CAP_PROP_FPS , float (requested_fps ))
470+ if not ok :
471+ logger .debug (f"Device ignored FPS set to { requested_fps :.2f} " )
472+ except Exception as exc :
473+ logger .debug (f"FPS set raised: { exc } " )
474+
475+ # Read back (may be 0.0 on many backends)
476+ try :
432477 self ._actual_fps = float (self ._capture .get (cv2 .CAP_PROP_FPS ) or 0.0 )
478+ except Exception :
479+ self ._actual_fps = 0.0
433480
434481 if self ._actual_fps and requested_fps and abs (self ._actual_fps - requested_fps ) > 0.1 :
435482 logger .warning (f"FPS mismatch: requested { requested_fps :.2f} , got { self ._actual_fps :.2f} " )
436483
437484 logger .info (f"Camera configured with FPS: { self ._actual_fps :.2f} " )
438485
439- # --- Extra properties (safe whitelist) ---
486+ # -------------------------
487+ # Extra properties (safe whitelist)
488+ # -------------------------
440489 for prop , value in self .settings .properties .items ():
441490 if prop in ("api" , "resolution" , "fast_start" , "alt_index_probe" ):
442491 continue
0 commit comments