Source code for QVideo.lib.QFPSMeter
'''Sliding-window frame-rate meter emitted as a Qt signal.'''
from qtpy import QtCore
from collections import deque
import time
import logging
logger = logging.getLogger(__name__)
__all__ = ['QFPSMeter']
[docs]
class QFPSMeter(QtCore.QObject):
'''Measures frame rate over a sliding window of frame timestamps.
On every :meth:`tick` a timestamp is appended to a circular buffer
of length *window*. Once the buffer is full, FPS is computed from
the span of the buffered timestamps and :attr:`fpsReady` is emitted.
Because the buffer slides forward one frame at a time, the reading
updates on every tick rather than in discrete batches.
Parameters
----------
window : int
Number of timestamps retained. Larger values give smoother
estimates at the cost of slower response to rate changes.
Values less than 2 are clamped to 2.
Signals
-------
fpsReady(float)
Emitted on every :meth:`tick` once the buffer is full.
Properties
----------
value : float
Most recently measured frame rate [frames per second].
Zero until the buffer fills for the first time.
Slots
-----
tick() -> None
Record one frame arrival and update the FPS estimate.
reset() -> None
Clear the timestamp buffer and cached value.
'''
#: Emitted on every :meth:`tick` once the buffer is full.
fpsReady = QtCore.Signal(float)
def __init__(self, window: int = 10) -> None:
super().__init__()
self.window = max(2, int(window))
self._value = 0.
self._timestamps = deque(maxlen=self.window)
[docs]
@QtCore.Slot()
def tick(self) -> None:
'''Record one frame arrival and update the FPS estimate.
Appends the current time to the circular buffer. Once the
buffer holds *window* timestamps, computes::
fps = (window - 1) / (newest_timestamp - oldest_timestamp)
and emits :attr:`fpsReady`.
'''
self._timestamps.append(time.perf_counter())
if len(self._timestamps) == self.window:
elapsed = self._timestamps[-1] - self._timestamps[0]
if elapsed > 0:
self._value = (self.window - 1) / elapsed
self.fpsReady.emit(self._value)
else:
logger.warning('elapsed time is zero; skipping FPS update')
[docs]
@QtCore.Slot()
def reset(self) -> None:
'''Reset the meter to its initial state.
Clears the timestamp buffer and cached value so that the next
:meth:`tick` begins a fresh measurement. Useful when the video
source stops and restarts.
'''
self._value = 0.
self._timestamps.clear()
@property
def value(self) -> float:
'''Most recently measured frame rate [frames per second].'''
return self._value