Skip to content

Commit 0583419

Browse files
author
Steve
committed
fix: Implement blocking pad probes to fix HLS segment event race condition
Based on analysis from O3 Pro and Gemini 2.5 Pro: - Add blocking probes to video/audio queues to prevent data flow until ready - Ensure HLS sink is in PLAYING state before unblocking data - Store queue references for probe management - Remove segment injection which caused event misordering - Properly synchronize both streams before starting muxer This robust solution prevents the 'Got data flow before segment event' warnings by ensuring proper pipeline initialization order on all platforms.
1 parent 011fbe0 commit 0583419

1 file changed

Lines changed: 86 additions & 59 deletions

File tree

webrtc_subprocess_glib.py

Lines changed: 86 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,49 @@ def try_pending_renegotiation(self):
774774
self.log("Still not in stable state, will retry")
775775
return True # Continue the timer
776776

777+
def check_hls_streams_ready(self):
778+
"""Check if both audio and video are connected and unblock if ready"""
779+
if not self.use_hls:
780+
return
781+
782+
# Check if both streams are connected
783+
if hasattr(self, 'hls_audio_connected') and self.hls_audio_connected and \
784+
hasattr(self, 'hls_video_connected') and self.hls_video_connected:
785+
786+
self.log(" ✅ Both audio and video connected - starting HLS recording")
787+
788+
# First sync the HLS sink state
789+
if hasattr(self, 'hlssink') and self.hlssink:
790+
ret = self.hlssink.sync_state_with_parent()
791+
self.log(f" HLS sink sync result: {ret}")
792+
793+
# Ensure it's in PLAYING state
794+
state_ret, state, pending = self.hlssink.get_state(0)
795+
if state != Gst.State.PLAYING:
796+
self.log(f" HLS sink not in PLAYING state ({state}), forcing to PLAYING")
797+
ret = self.hlssink.set_state(Gst.State.PLAYING)
798+
self.log(f" HLS sink set_state result: {ret}")
799+
800+
# Wait for state change
801+
timeout = 2 * Gst.SECOND
802+
state_ret, state, pending = self.hlssink.get_state(timeout)
803+
self.log(f" HLS sink final state: {state}")
804+
805+
# Now remove the blocking probes to allow data flow
806+
if hasattr(self, 'video_probe_id') and hasattr(self, 'video_queue'):
807+
video_src = self.video_queue.get_static_pad('src')
808+
if video_src:
809+
video_src.remove_probe(self.video_probe_id)
810+
self.log(" ▶️ Unblocked video flow")
811+
812+
if hasattr(self, 'audio_probe_id') and hasattr(self, 'audio_queue'):
813+
audio_src = self.audio_queue.get_static_pad('src')
814+
if audio_src:
815+
audio_src.remove_probe(self.audio_probe_id)
816+
self.log(" ▶️ Unblocked audio flow")
817+
818+
self.log(" 🎬 HLS recording started!")
819+
777820
def setup_hls_muxer(self):
778821
"""Set up shared mpegtsmux and HLS sink for audio/video"""
779822
if hasattr(self, 'hls_mux'):
@@ -834,8 +877,8 @@ def setup_hls_muxer(self):
834877
# Add sink to pipeline
835878
self.pipe.add(self.hlssink)
836879

837-
# Don't sync state yet - wait until pads are connected
838-
# This prevents the muxer from starting before it has inputs
880+
# Keep sink in NULL state until pads are connected
881+
self.hlssink.set_state(Gst.State.NULL)
839882

840883
self.log(" ✅ HLS muxer ready for audio/video streams")
841884

@@ -1538,6 +1581,8 @@ def ndi_video_probe_cb(pad, info):
15381581
if not video_queue:
15391582
self.log("Failed to create video queue for HLS", "error")
15401583
return
1584+
# Store reference for later access
1585+
self.video_queue = video_queue
15411586

15421587
# Add identity element to help with segment handling
15431588
video_identity = Gst.ElementFactory.make('identity', 'video_identity')
@@ -1628,21 +1673,9 @@ def depay_probe_cb(pad, info):
16281673
h264_pad = h264parse.get_static_pad('src')
16291674
if h264_pad:
16301675
def h264_probe_cb(pad, info):
1631-
# Check if this is a buffer (not an event)
1632-
if info.type & Gst.PadProbeType.BUFFER:
1633-
# Inject a segment event before the first buffer
1634-
if not hasattr(self, '_h264_segment_sent'):
1635-
self._h264_segment_sent = True
1636-
# Create segment event
1637-
segment = Gst.Segment()
1638-
segment.init(Gst.Format.TIME)
1639-
event = Gst.Event.new_segment(segment)
1640-
pad.push_event(event)
1641-
self.log(" ✅ Injected segment event for HLS video")
1642-
1643-
if not hasattr(self, '_h264_probe_logged'):
1644-
self._h264_probe_logged = True
1645-
self.log(" ✅ Video data confirmed after h264parse!")
1676+
if not hasattr(self, '_h264_probe_logged'):
1677+
self._h264_probe_logged = True
1678+
self.log(" ✅ Video data confirmed after h264parse!")
16461679
return Gst.PadProbeReturn.OK
16471680
h264_pad.add_probe(Gst.PadProbeType.BUFFER, h264_probe_cb)
16481681

@@ -1663,13 +1696,6 @@ def queue_probe_cb(pad, info):
16631696
if video_pad:
16641697
src_pad = video_queue.get_static_pad('src')
16651698

1666-
# Send initial segment event before linking
1667-
segment = Gst.Segment()
1668-
segment.init(Gst.Format.TIME)
1669-
event = Gst.Event.new_segment(segment)
1670-
video_pad.send_event(event)
1671-
self.log(" ✅ Sent initial segment event to HLS video pad")
1672-
16731699
if src_pad.link(video_pad) == Gst.PadLinkReturn.OK:
16741700
self.log(" ✅ Video connected to HLS sink")
16751701
# Check hlssink state
@@ -1703,22 +1729,23 @@ def queue_probe_cb(pad, info):
17031729
for element in elements:
17041730
element.sync_state_with_parent()
17051731

1706-
# Also ensure HLS sink is in correct state
1707-
if hasattr(self, 'hlssink') and self.hlssink:
1708-
# First try to sync with parent
1709-
ret = self.hlssink.sync_state_with_parent()
1710-
self.log(f" HLS sink sync result: {ret}")
1711-
1712-
# Check the actual state
1713-
state_ret, state, pending = self.hlssink.get_state(0)
1714-
if state != Gst.State.PLAYING:
1715-
self.log(f" HLS sink not in PLAYING state ({state}), forcing to PLAYING")
1716-
ret = self.hlssink.set_state(Gst.State.PLAYING)
1717-
self.log(f" HLS sink set_state result: {ret}")
1718-
# Wait for state change to complete
1719-
timeout = 5 * Gst.SECOND # 5 second timeout
1720-
state_ret, state, pending = self.hlssink.get_state(timeout)
1721-
self.log(f" HLS sink final state: {state} (result: {state_ret})")
1732+
# Add blocking probe to video queue to prevent data flow until ready
1733+
if self.use_hls:
1734+
video_queue_src = video_queue.get_static_pad('src')
1735+
if video_queue_src:
1736+
def video_block_probe_cb(pad, info):
1737+
return Gst.PadProbeReturn.OK # Just block
1738+
self.video_probe_id = video_queue_src.add_probe(
1739+
Gst.PadProbeType.BLOCK_DOWNSTREAM,
1740+
video_block_probe_cb
1741+
)
1742+
self.log(" 🛑 Blocking video flow until both streams ready")
1743+
1744+
# Track that video is connected
1745+
self.hls_video_connected = True
1746+
1747+
# Check if both streams are ready to start
1748+
self.check_hls_streams_ready()
17221749

17231750
# Link incoming pad to queue
17241751
sink_pad = queue.get_static_pad('sink')
@@ -1957,6 +1984,8 @@ def handle_audio_pad(self, pad):
19571984
aacparse = Gst.ElementFactory.make('aacparse', None)
19581985
# Create a queue before muxer
19591986
audio_queue = Gst.ElementFactory.make('queue', 'audio_queue_hls')
1987+
# Store reference for later access
1988+
self.audio_queue = audio_queue
19601989
# Add identity element for audio segment handling
19611990
audio_identity = Gst.ElementFactory.make('identity', 'audio_identity')
19621991
if audio_identity:
@@ -2065,26 +2094,24 @@ def handle_audio_pad(self, pad):
20652094
for element in elements:
20662095
element.sync_state_with_parent()
20672096

2068-
# Add segment event injection for audio too
2069-
audio_src_pad = audio_queue.get_static_pad('src')
2070-
if audio_src_pad:
2071-
def audio_segment_probe_cb(pad, info):
2072-
if info.type & Gst.PadProbeType.BUFFER:
2073-
if not hasattr(self, '_audio_segment_sent'):
2074-
self._audio_segment_sent = True
2075-
# Inject segment event before first audio buffer
2076-
segment = Gst.Segment()
2077-
segment.init(Gst.Format.TIME)
2078-
event = Gst.Event.new_segment(segment)
2079-
pad.push_event(event)
2080-
self.log(" ✅ Injected segment event for HLS audio")
2081-
return Gst.PadProbeReturn.OK
2082-
audio_src_pad.add_probe(Gst.PadProbeType.BUFFER, audio_segment_probe_cb)
20832097

2084-
# Also ensure HLS sink is in correct state for audio
2085-
if hasattr(self, 'hlssink') and self.hlssink:
2086-
# Don't sync again if already done for video
2087-
pass
2098+
# Add blocking probe to audio queue to prevent data flow until ready
2099+
if self.use_hls:
2100+
audio_queue_src = audio_queue.get_static_pad('src')
2101+
if audio_queue_src:
2102+
def audio_block_probe_cb(pad, info):
2103+
return Gst.PadProbeReturn.OK # Just block
2104+
self.audio_probe_id = audio_queue_src.add_probe(
2105+
Gst.PadProbeType.BLOCK_DOWNSTREAM,
2106+
audio_block_probe_cb
2107+
)
2108+
self.log(" 🛑 Blocking audio flow until both streams ready")
2109+
2110+
# Track that audio is connected
2111+
self.hls_audio_connected = True
2112+
2113+
# Check if both streams are ready to start
2114+
self.check_hls_streams_ready()
20882115

20892116
# Link incoming pad to queue
20902117
sink_pad = queue.get_static_pad('sink')

0 commit comments

Comments
 (0)