Add FPS throttling to capture, processing, and send loops
All three frame pipeline loops were running unthrottled, consuming excessive CPU. Now each sleeps for the remaining frame budget after completing work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -141,8 +141,11 @@ class ScreenCaptureLiveStream(LiveStream):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Capture error (display={self._capture_stream.display_index}): {e}")
|
logger.error(f"Capture error (display={self._capture_stream.display_index}): {e}")
|
||||||
|
|
||||||
# No FPS throttling - capture as fast as frames are available
|
# Throttle to target FPS
|
||||||
# But sleep briefly during idle periods to avoid burning CPU
|
elapsed = time.time() - loop_start
|
||||||
|
remaining = frame_time - elapsed
|
||||||
|
if remaining > 0:
|
||||||
|
time.sleep(remaining)
|
||||||
|
|
||||||
|
|
||||||
class ProcessedLiveStream(LiveStream):
|
class ProcessedLiveStream(LiveStream):
|
||||||
@@ -209,18 +212,17 @@ class ProcessedLiveStream(LiveStream):
|
|||||||
# processed by a consumer), so the 3rd slot is always safe to reuse.
|
# processed by a consumer), so the 3rd slot is always safe to reuse.
|
||||||
_ring: List[Optional[np.ndarray]] = [None, None, None]
|
_ring: List[Optional[np.ndarray]] = [None, None, None]
|
||||||
_ring_idx = 0
|
_ring_idx = 0
|
||||||
|
frame_time = 1.0 / self._source.target_fps if self._source.target_fps > 0 else 1.0
|
||||||
|
|
||||||
while self._running:
|
while self._running:
|
||||||
source_frame = self._source.get_latest_frame()
|
loop_start = time.time()
|
||||||
if source_frame is None:
|
|
||||||
# Small sleep when waiting for frames to avoid CPU spinning
|
|
||||||
time.sleep(0.001)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Identity cache: Skip processing duplicate frames to save CPU
|
source_frame = self._source.get_latest_frame()
|
||||||
# (Compare object identity to detect when capture engine returns same frame)
|
if source_frame is None or source_frame is cached_source_frame:
|
||||||
if source_frame is cached_source_frame:
|
# Sleep until next frame is expected
|
||||||
time.sleep(0.001)
|
elapsed = time.time() - loop_start
|
||||||
|
remaining = frame_time - elapsed
|
||||||
|
time.sleep(max(remaining, 0.001))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cached_source_frame = source_frame
|
cached_source_frame = source_frame
|
||||||
|
|||||||
@@ -859,8 +859,11 @@ class ProcessorManager:
|
|||||||
state.metrics.last_error = str(e)
|
state.metrics.last_error = str(e)
|
||||||
logger.error(f"Processing error for target {target_id}: {e}", exc_info=True)
|
logger.error(f"Processing error for target {target_id}: {e}", exc_info=True)
|
||||||
|
|
||||||
# No FPS control - process frames as fast as they arrive (match test behavior)
|
# Throttle to target FPS
|
||||||
pass
|
elapsed = time.time() - loop_start
|
||||||
|
remaining = frame_time - elapsed
|
||||||
|
if remaining > 0:
|
||||||
|
await asyncio.sleep(remaining)
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.info(f"Processing loop cancelled for target {target_id}")
|
logger.info(f"Processing loop cancelled for target {target_id}")
|
||||||
@@ -1503,8 +1506,11 @@ class ProcessorManager:
|
|||||||
state.metrics.last_error = str(e)
|
state.metrics.last_error = str(e)
|
||||||
logger.error(f"KC processing error for {target_id}: {e}", exc_info=True)
|
logger.error(f"KC processing error for {target_id}: {e}", exc_info=True)
|
||||||
|
|
||||||
# No FPS control - process frames as fast as they arrive (match test behavior)
|
# Throttle to target FPS
|
||||||
pass
|
elapsed = time.time() - loop_start
|
||||||
|
remaining = frame_time - elapsed
|
||||||
|
if remaining > 0:
|
||||||
|
await asyncio.sleep(remaining)
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.info(f"KC processing loop cancelled for target {target_id}")
|
logger.info(f"KC processing loop cancelled for target {target_id}")
|
||||||
|
|||||||
Reference in New Issue
Block a user