2222import time
2323
2424logger = logging .getLogger (__name__ )
25- logging .getLogger ('aiortc.rtcrtpsender' ).setLevel (logging .WARNING )
26- logging .getLogger ('aiortc.rtcrtpreceiver' ).setLevel (logging .WARNING )
25+ logging .getLogger ("aiortc" ).setLevel (logging .DEBUG )
26+ logging .getLogger ("aiortc.rtcrtpsender" ).setLevel (logging .WARNING )
27+ logging .getLogger ("aiortc.rtcrtpreceiver" ).setLevel (logging .WARNING )
2728
2829
2930MAX_BITRATE = 2000000
@@ -51,36 +52,60 @@ def __init__(self, track: MediaStreamTrack, pipeline: Pipeline):
5152 super ().__init__ ()
5253 self .track = track
5354 self .pipeline = pipeline
54- self ._frame_count = 0
55- self ._start_time = time .monotonic ()
5655 self ._lock = threading .Lock ()
5756 self ._fps = 0.0
57+ self ._frame_delay = 0.0
5858 self ._running = True
59+ self ._fps_interval_frame_count = 0
60+ self ._last_fps_calculation_time = time .monotonic ()
61+ self ._stream_start_time = None
62+ self ._last_frame_presentation_time = None
63+ self ._last_frame_processed_time = None
5964 self ._start_fps_thread ()
65+ self ._start_frame_delay_thread ()
6066
6167 def _start_fps_thread (self ):
6268 """Start a separate thread to calculate FPS periodically."""
63- self .fps_thread = threading .Thread (target = self ._calculate_fps_loop , daemon = True )
64- self .fps_thread .start ()
69+ self ._fps_thread = threading .Thread (
70+ target = self ._calculate_fps_loop , daemon = True
71+ )
72+ self ._fps_thread .start ()
6573
6674 def _calculate_fps_loop (self ):
6775 """Loop to calculate FPS periodically."""
6876 while self ._running :
6977 time .sleep (1 ) # Calculate FPS every second.
7078 with self ._lock :
7179 current_time = time .monotonic ()
72- time_diff = current_time - self ._start_time
80+ time_diff = current_time - self ._last_fps_calculation_time
7381 if time_diff > 0 :
74- self ._fps = self ._frame_count / time_diff
82+ self ._fps = self ._fps_interval_frame_count / time_diff
7583
7684 # Reset start_time and frame_count for the next interval.
77- self ._start_time = current_time
78- self ._frame_count = 0
85+ self ._last_fps_calculation_time = current_time
86+ self ._fps_interval_frame_count = 0
87+
88+ def _start_frame_delay_thread (self ):
89+ """Start a separate thread to calculate frame delay periodically."""
90+ self ._frame_delay_thread = threading .Thread (
91+ target = self ._calculate_frame_delay_loop , daemon = True
92+ )
93+ self ._frame_delay_thread .start ()
94+
95+ def _calculate_frame_delay_loop (self ):
96+ """Loop to calculate frame delay periodically."""
97+ while self ._running :
98+ time .sleep (1 ) # Calculate frame delay every second.
99+ with self ._lock :
100+ if self ._last_frame_presentation_time is not None :
101+ current_time = time .monotonic ()
102+ self ._frame_delay = (current_time - self ._stream_start_time ) - float (self ._last_frame_presentation_time )
79103
80104 def stop (self ):
81105 """Stop the FPS calculation thread."""
82106 self ._running = False
83- self .fps_thread .join ()
107+ self ._fps_thread .join ()
108+ self ._frame_delay_thread .join ()
84109
85110 @property
86111 def fps (self ) -> float :
@@ -92,19 +117,34 @@ def fps(self) -> float:
92117 with self ._lock :
93118 return self ._fps
94119
120+ @property
121+ def frame_delay (self ) -> float :
122+ """Get the current frame delay.
123+
124+ Returns:
125+ The current frame delay.
126+ """
127+ with self ._lock :
128+ return self ._frame_delay
129+
95130 async def recv (self ) -> av .VideoFrame :
96131 """Receive and process a video frame. Called by the WebRTC library when a frame
97132 is received.
98133
99134 Returns:
100135 The processed video frame.
101136 """
137+ if self ._stream_start_time is None :
138+ self ._stream_start_time = time .monotonic ()
139+
102140 input_frame = await self .track .recv ()
103141 processed_frame = await self .pipeline (input_frame )
104142
105- # Increment frame count for FPS calculation .
143+ # Store frame info for stats .
106144 with self ._lock :
107- self ._frame_count += 1
145+ self ._fps_interval_frame_count += 1
146+ self ._last_frame_presentation_time = input_frame .time
147+ self ._last_frame_processed_time = time .monotonic ()
108148
109149 return processed_frame
110150
@@ -187,30 +227,29 @@ async def offer(request):
187227 @pc .on ("datachannel" )
188228 def on_datachannel (channel ):
189229 if channel .label == "control" :
230+
190231 @channel .on ("message" )
191232 async def on_message (message ):
192233 try :
193234 params = json .loads (message )
194-
235+
195236 if params .get ("type" ) == "get_nodes" :
196237 nodes_info = await pipeline .get_nodes_info ()
197- response = {
198- "type" : "nodes_info" ,
199- "nodes" : nodes_info
200- }
238+ response = {"type" : "nodes_info" , "nodes" : nodes_info }
201239 channel .send (json .dumps (response ))
202240 elif params .get ("type" ) == "update_prompt" :
203241 if "prompt" not in params :
204- logger .warning ("[Control] Missing prompt in update_prompt message" )
242+ logger .warning (
243+ "[Control] Missing prompt in update_prompt message"
244+ )
205245 return
206246 pipeline .set_prompt (params ["prompt" ])
207- response = {
208- "type" : "prompt_updated" ,
209- "success" : True
210- }
247+ response = {"type" : "prompt_updated" , "success" : True }
211248 channel .send (json .dumps (response ))
212249 else :
213- logger .warning ("[Server] Invalid message format - missing required fields" )
250+ logger .warning (
251+ "[Server] Invalid message format - missing required fields"
252+ )
214253 except json .JSONDecodeError :
215254 logger .error ("[Server] Invalid JSON received" )
216255 except Exception as e :
@@ -309,8 +348,8 @@ async def on_shutdown(app: web.Application):
309348
310349 logging .basicConfig (
311350 level = args .log_level .upper (),
312- format = ' %(asctime)s [%(levelname)s] %(message)s' ,
313- datefmt = ' %H:%M:%S'
351+ format = " %(asctime)s [%(levelname)s] %(message)s" ,
352+ datefmt = " %H:%M:%S" ,
314353 )
315354
316355 app = web .Application ()
0 commit comments