55import multiprocessing as mp
66import os
77import subprocess
8+ import threading
89import time
910import traceback
1011from dataclasses import dataclass
3536 LeaveAck ,
3637 MediaChunk ,
3738 MediaComplete ,
39+ MediaError ,
3840 MediaInit ,
3941 ReloadAck ,
4042 ReloadModelPayload ,
@@ -163,11 +165,95 @@ def gpu_worker_process(
163165 from dreamverse .video_generation import VideoGenerationWorker
164166
165167 worker = VideoGenerationWorker (gpu_id )
168+ # One active fMP4 stream per worker keeps binary media chunks ordered by segment.
169+ active_stream_thread : threading .Thread | None = None
170+ active_stream_segment_idx : int | None = None
166171
167172 def event_loop (first_cmd : Command = None ):
168173 """Blocking event loop for LTX2; dispatches user commands."""
169174 print (f"[GPU { gpu_id } ] Entering event loop" )
170175
176+ def wait_for_active_stream () -> None :
177+ """Join the active fMP4 segment stream before leave, shutdown, or the next stream."""
178+ nonlocal active_stream_thread
179+ nonlocal active_stream_segment_idx
180+ if active_stream_thread is None :
181+ return
182+ if active_stream_thread .is_alive ():
183+ print (f"[GPU { gpu_id } ] Waiting for AV stream "
184+ f"segment { active_stream_segment_idx } before continuing" )
185+ active_stream_thread .join ()
186+ active_stream_thread = None
187+ active_stream_segment_idx = None
188+
189+ def start_stream_thread (
190+ * ,
191+ step_result ,
192+ user_id : str ,
193+ segment_idx : int ,
194+ stream_id : str ,
195+ head_trim_frames : int ,
196+ head_trim_audio_frames : int ,
197+ ) -> None :
198+ """Start fMP4 encoding for one segment and publish media events to the controller."""
199+ nonlocal active_stream_thread
200+ nonlocal active_stream_segment_idx
201+ wait_for_active_stream ()
202+ stream_timings = dict (step_result .timings )
203+
204+ def _publish (event : StreamEvent ) -> None :
205+ response_queue .put (_stream_event_to_worker_event (event , user_id , segment_idx ))
206+
207+ def _run_stream () -> None :
208+ try :
209+ av_ok , av_error = stream_fmp4 (
210+ frames = step_result .frames ,
211+ audio = step_result .audio ,
212+ audio_sample_rate = step_result .audio_sample_rate ,
213+ stream_id = stream_id ,
214+ timings = stream_timings ,
215+ head_trim_frames = head_trim_frames ,
216+ head_trim_audio_frames = head_trim_audio_frames ,
217+ shared_buffer = None ,
218+ shared_buffer_bytes = 0 ,
219+ publish = _publish ,
220+ log_prefix = f"[GPU { gpu_id } ]" ,
221+ )
222+ if not av_ok :
223+ response_queue .put (
224+ MediaError (
225+ user_id = user_id ,
226+ segment_idx = segment_idx ,
227+ stream_id = stream_id ,
228+ message = av_error or "worker av_fmp4 stream failed" ,
229+ ))
230+ return
231+ print (f"[GPU { gpu_id } ] AV streamed segment { segment_idx } : "
232+ f"encode_total={ stream_timings .get ('av_encode_stream_ms' , 0 ):.0f} ms "
233+ f"wav_write={ stream_timings .get ('av_wav_write_ms' , 0 ):.1f} ms "
234+ f"spawn={ stream_timings .get ('av_ffmpeg_spawn_ms' , 0 ):.1f} ms "
235+ f"first_chunk={ stream_timings .get ('av_first_chunk_ms' , 0 ):.0f} ms "
236+ f"chunk_interval_med={ stream_timings .get ('av_chunk_interval_ms_median' , 0 ):.1f} ms "
237+ f"chunk_interval_p95={ stream_timings .get ('av_chunk_interval_ms_p95' , 0 ):.1f} ms "
238+ f"publish_med={ stream_timings .get ('av_chunk_publish_ms_median' , 0 ):.2f} ms "
239+ f"read_med={ stream_timings .get ('av_chunk_read_ms_median' , 0 ):.1f} ms" )
240+ except Exception as exc :
241+ response_queue .put (
242+ MediaError (
243+ user_id = user_id ,
244+ segment_idx = segment_idx ,
245+ stream_id = stream_id ,
246+ message = str (exc ),
247+ ))
248+
249+ active_stream_thread = threading .Thread (
250+ target = _run_stream ,
251+ name = f"dreamverse-av-stream-gpu{ gpu_id } -seg{ segment_idx } " ,
252+ daemon = False ,
253+ )
254+ active_stream_segment_idx = segment_idx
255+ active_stream_thread .start ()
256+
171257 def handle_command (cmd : Command ):
172258 if cmd .type == CommandType .USER_JOIN :
173259 print (f"[GPU { gpu_id } ] User { cmd .user_id [:8 ]} joined" )
@@ -200,40 +286,21 @@ def handle_command(cmd: Command):
200286 f"audio_shape={ audio_shape } , "
201287 f"audio_sample_rate={ step_result .audio_sample_rate } " )
202288 stream_id = generate_stream_id (segment_idx )
203-
204- def _publish (event : StreamEvent ) -> None :
205- response_queue .put (_stream_event_to_worker_event (event , cmd .user_id , segment_idx ))
206-
207- av_ok , av_error = stream_fmp4 (
208- frames = step_result .frames ,
209- audio = step_result .audio ,
210- audio_sample_rate = step_result .audio_sample_rate ,
289+ start_stream_thread (
290+ step_result = step_result ,
291+ user_id = cmd .user_id ,
292+ segment_idx = segment_idx ,
211293 stream_id = stream_id ,
212- timings = step_result .timings ,
213294 head_trim_frames = head_trim_frames ,
214295 head_trim_audio_frames = head_trim_audio_frames ,
215- shared_buffer = shared_stream_buffer ,
216- shared_buffer_bytes = shared_stream_buffer_bytes ,
217- publish = _publish ,
218- log_prefix = f"[GPU { gpu_id } ]" ,
219296 )
220- if not av_ok :
221- raise RuntimeError (av_error or "worker av_fmp4 stream failed" )
222- print (f"[GPU { gpu_id } ] AV streamed segment { segment_idx } : "
223- f"encode_total={ step_result .timings .get ('av_encode_stream_ms' , 0 ):.0f} ms "
224- f"wav_write={ step_result .timings .get ('av_wav_write_ms' , 0 ):.1f} ms "
225- f"spawn={ step_result .timings .get ('av_ffmpeg_spawn_ms' , 0 ):.1f} ms "
226- f"first_chunk={ step_result .timings .get ('av_first_chunk_ms' , 0 ):.0f} ms "
227- f"chunk_interval_med={ step_result .timings .get ('av_chunk_interval_ms_median' , 0 ):.1f} ms "
228- f"chunk_interval_p95={ step_result .timings .get ('av_chunk_interval_ms_p95' , 0 ):.1f} ms "
229- f"publish_med={ step_result .timings .get ('av_chunk_publish_ms_median' , 0 ):.2f} ms "
230- f"read_med={ step_result .timings .get ('av_chunk_read_ms_median' , 0 ):.1f} ms" )
231- step_result .timings ["ipc_put_start_ns" ] = time .time_ns ()
297+ step_timings = dict (step_result .timings )
298+ step_timings ["ipc_put_start_ns" ] = time .time_ns ()
232299 response_queue .put (
233300 StepComplete (
234301 user_id = cmd .user_id ,
235302 segment_idx = segment_idx ,
236- timings = step_result . timings ,
303+ timings = step_timings ,
237304 ))
238305 except Exception as e :
239306 print (f"[GPU { gpu_id } ] Step error: { e } " )
@@ -245,6 +312,7 @@ def _publish(event: StreamEvent) -> None:
245312
246313 elif cmd .type == CommandType .USER_LEAVE :
247314 print (f"[GPU { gpu_id } ] User { cmd .user_id [:8 ]} left" )
315+ wait_for_active_stream ()
248316 worker .clear_conditioning ()
249317 response_queue .put (LeaveAck (user_id = cmd .user_id ))
250318
@@ -286,6 +354,7 @@ def _publish(event: StreamEvent) -> None:
286354
287355 if first_cmd is not None :
288356 if first_cmd .type == CommandType .SHUTDOWN :
357+ wait_for_active_stream ()
289358 worker .shutdown ()
290359 response_queue .put (ShutdownAck ())
291360 return
@@ -300,6 +369,7 @@ def _publish(event: StreamEvent) -> None:
300369
301370 if cmd .type == CommandType .SHUTDOWN :
302371 print (f"[GPU { gpu_id } ] Event loop shutting down" )
372+ wait_for_active_stream ()
303373 worker .shutdown ()
304374 response_queue .put (ShutdownAck ())
305375 return
@@ -600,7 +670,7 @@ def get_response_nonblocking():
600670 continue
601671
602672 # AV streaming events → route to the user's stream queue.
603- if isinstance (event , (MediaInit , MediaChunk , MediaComplete )):
673+ if isinstance (event , (MediaInit , MediaChunk , MediaComplete , MediaError )):
604674 stream_queue = self ._stream_queues .get (event .user_id )
605675 if stream_queue is not None :
606676 await stream_queue .put (event )
0 commit comments