gstlal 1.13.0
Loading...
Searching...
No Matches
mux.py
1"""Module for multiplexing (mux) and demultiplexing (demux) elements
2
3"""
4
5import gi
6
7gi.require_version('Gst', '1.0')
8from gi.repository import GObject
9from gi.repository import Gst
10
11GObject.threads_init()
12Gst.init(None)
13
14from ligo import segments
15from lal import iterutils
16from lal import LIGOTimeGPS
17from gstlal.pipeparts import pipetools
18
19
21 def __init__(self, elem, units_dict):
22 """
23 Connect a handler for the pad-added signal of the
24 framecpp_channeldemux element elem, and when a pad is added
25 to the element if the pad's name appears as a key in the
26 units_dict dictionary that pad's units property will be set
27 to the string value associated with that key in the
28 dictionary.
29
30 Example:
31
32 >>> FrameCPPChannelDemuxSetUnitsHandler(elem, {"H1:LSC-STRAIN": "strain"}) # doctest: +SKIP
33
34 NOTE: this is a work-around to address the problem that
35 most (all?) frame files do not have units set on their
36 channel data, whereas downstream consumers of the data
37 might require information about the units. The demuxer
38 provides the units as part of a tag event, and
39 framecpp_channeldemux_set_units() can be used to override
40 the values, thereby correcting absent or incorrect units
41 information.
42 """
43 self.elem = elem
44 self.pad_added_handler_id = elem.connect("pad-added", self.pad_added, units_dict)
45 assert self.pad_added_handler_id > 0
46
47 @staticmethod
48 def pad_added(element, pad, units_dict):
49 name = pad.get_name()
50 if name in units_dict:
51 pad.set_property("units", units_dict[name])
52
53
55 """
56 Utility to watch for missing data. Pad probes are used to collect
57 the times spanned by buffers, these are compared to a segment list
58 defining the intervals of data the stream is required to have. If
59 any intervals of data are found to have been skipped or if EOS is
60 seen before the end of the segment list then a ValueError exception
61 is raised.
62
63 There are two ways to use this tool. To directly install a segment
64 list monitor on a single pad use the .set_probe() class method.
65 For elements with dynamic pads, the class can be allowed to
66 automatically add monitors to pads as they become available by
67 using the element's pad-added signal. In this case initialize an
68 instance of the class with the element and a dictionary of segment
69 lists mapping source pad name to the segment list to check that
70 pad's output against.
71
72 In both cases a jitter parameter sets the maximum size of a skipped
73 segment that will be ignored (for example, to accomodate round-off
74 error in element timestamp computations). The default is 1 ns.
75 """
76
77 # FIXME: this code now has two conflicting mechanisms for removing
78 # probes from pads: one code path removes probes when pads get to
79 # EOS, while the othe removes a probe each time the pad for the
80 # probe appears a second or subsequent time on an element (and then
81 # re-installs the probe on the new pad). it's possible that these
82 # two could attempt to remove the same probe twice, which will
83 # cause a crash, although it should not happen in current use
84 # cases. the fix is to rework the probe tracking mechanism so that
85 # both code paths agree on what probes are installed
86 def __init__(self, elem, seglists, jitter=LIGOTimeGPS(0, 1)):
87 self.jitter = jitter
88 self.probe_handler_ids = {}
89 # make a copy of the segmentlistdict in case the calling
90 # code modifies it
91 self.pad_added_handler_id = elem.connect("pad-added", self.pad_added, seglists.copy())
92 assert self.pad_added_handler_id > 0
93
94 def pad_added(self, element, pad, seglists):
95 name = pad.get_name()
96 if name in self.probe_handler_ids:
97 pad.remove_probe(self.probe_handler_ids.pop(name))
98 if name in seglists:
99 self.probe_handler_ids[name] = self.set_probe(pad, seglists[name], self.jitter)
100 assert self.probe_handler_ids[name] > 0
101
102 @classmethod
103 def set_probe(cls, pad, seglist, jitter=LIGOTimeGPS(0, 1)):
104 # use a copy of the segmentlist so the probe can modify it
105 seglist = segments.segmentlist(seglist)
106 # mutable object to carry data to probe
107 data = [seglist, jitter, None]
108 # install probe, save ID in data
109 probe_id = data[2] = pad.add_probe(Gst.PadProbeType.DATA_DOWNSTREAM, cls.probe, data)
110 return probe_id
111
112 @staticmethod
113 def probe(pad, probeinfo, seg_jitter_id):
114 seglist, jitter, probe_id = seg_jitter_id
115 if probeinfo.type & Gst.PadProbeType.BUFFER:
116 obj = probeinfo.get_buffer()
117 if not obj.mini_object.flags & Gst.BufferFlags.GAP:
118 # remove the current buffer from the data
119 # we're expecting to see
120 seglist -= segments.segmentlist([segments.segment((LIGOTimeGPS(0, obj.pts), LIGOTimeGPS(0, obj.pts + obj.duration)))])
121 # ignore missing data intervals unless
122 # they're bigger than the jitter
123 iterutils.inplace_filter(lambda seg: abs(seg) > jitter, seglist)
124 # are we still expecting to see something that
125 # precedes the current buffer?
126 preceding = segments.segment((segments.NegInfinity, LIGOTimeGPS(0, obj.pts)))
127 if seglist.intersects_segment(preceding):
128 raise ValueError("%s: detected missing data: %s" % (pad.get_name(), seglist & segments.segmentlist([preceding])))
129 elif probeinfo.type & Gst.PadProbeType.EVENT_DOWNSTREAM and probeinfo.get_event().type == Gst.EventType.EOS:
130 # detach probe at EOS
131 pad.remove_probe(probe_id)
132 # ignore missing data intervals unless they're
133 # bigger than the jitter
134 iterutils.inplace_filter(lambda seg: abs(seg) > jitter, seglist)
135 if seglist:
136 raise ValueError("%s: at EOS detected missing data: %s" % (pad.get_name(), seglist))
137 return True
138
139
140def framecpp_channel_demux(pipeline, src, **properties):
141 """Demux src using framecpp
142
143 Args:
144 pipeline:
145 Gst.Pipeline, the pipeline to which the new element will be added
146 src:
147 Gst.Element, the source element
148 **properties:
149 dict, keyword arguments to be set as element properties
150
151 References:
152 [1] framecppdemux implementation: gstlal/gstlal-ugly/gst/framecpp/framecpp_channeldemux.cc
153
154 Returns:
155 Element, src demuxed using framecpp
156 """
157 return pipetools.make_element_with_src(pipeline, src, "framecpp_channeldemux", **properties)
158
159
160def framecpp_channel_mux(pipeline, channel_src_map, units=None, seglists=None, **properties):
161 """Mux a source using framecpp
162
163 Args:
164 pipeline:
165 Gst.Pipeline, the pipeline to which the new element will be added
166 channel_src_map:
167 dict, mapping a channel -> src element
168 units:
169 str, default None, if given set these units on source
170 seglists:
171 default None, if given create a segments handler for these segments
172 **properties:
173
174 Returns:
175 Element, the muxed sources
176 """
177 elem = pipetools.make_element_with_src(pipeline, None, "framecpp_channelmux", **properties)
178 if channel_src_map is not None:
179 for channel, src in channel_src_map.items():
180 for srcpad in src.srcpads:
181 # FIXME FIXME FIXME. This should use the pad template from the element.
182 # FIXME once a newer version of some library is available, then we should be able to switch to this
183 # if srcpad.link(elem.get_request_pad(channel)) == Gst.PadLinkReturn.OK
184 # Instead. Right now it fails due to the
185 # underscore in channel names. When it fails
186 # it fails silently and returns None, which
187 # gives a cryptic error message
188 if srcpad.link(elem.request_pad(Gst.PadTemplate.new(channel, Gst.PadDirection.SINK, Gst.PadPresence.REQUEST, Gst.Caps("ANY")), channel)) == Gst.PadLinkReturn.OK:
189 break
190 if units is not None:
192 if seglists is not None:
194 return elem
195
196
197def framecpp_channel_mux_from_list(pipeline, *srcs, channels = None, **properties):
198 """Mux a source using framecpp
199
200 NOTE: This acts similarly to framecpp_channel_mux with a different function
201 signature to map channels to sources.
202
203 Args:
204 pipeline:
205 Gst.Pipeline, the pipeline to which the new element will be added
206 *srcs:
207 Gst.Element, the source elements
208 channels:
209 Union[str, Iterable], default None, the channels mapping to sources
210 seglists:
211 default None, if given create a segments handler for these segments
212 **properties:
213
214 Returns:
215 Element, the muxed sources
216 """
217 if isinstance(channels, str):
218 channels = [channels]
219 channel_src_map = {channel: src for channel, src in zip(channels, srcs)}
220 return framecpp_channel_mux(pipeline, channel_src_map, **properties)
221
222
223def ogg_mux(pipeline, src):
224 """This element merges streams (audio and video) into ogg files.
225
226 Args:
227 pipeline:
228 Gst.Pipeline, the pipeline to which the new element will be added
229 src:
230 Gst.Element, the source element
231
232 References:
233 [1] oggmux docs: https://gstreamer.freedesktop.org/documentation/ogg/oggmux.html?gi-language=python
234
235 Returns:
236 Element, the source merged as ogg format
237 """
238 return pipetools.make_element_with_src(pipeline, src, "oggmux")
239
240
241def avi_mux(pipeline, src):
242 """Muxes raw or compressed audio and/or video streams into an AVI file.
243
244 Args:
245 pipeline:
246 Gst.Pipeline, the pipeline to which the new element will be added
247 src:
248 Gst.Element, the source element
249
250 References:
251 [1] avimux docs: https://gstreamer.freedesktop.org/documentation/avi/avimux.html?gi-language=python
252
253 Returns:
254 Element, the source merged as avi
255 """
256 return pipetools.make_element_with_src(pipeline, src, "avimux")
set_probe(cls, pad, seglist, jitter=LIGOTimeGPS(0, 1))
Definition mux.py:103
ogg_mux(pipeline, src)
Definition mux.py:223
framecpp_channel_mux_from_list(pipeline, *srcs, channels=None, **properties)
Definition mux.py:197
avi_mux(pipeline, src)
Definition mux.py:241
framecpp_channel_demux(pipeline, src, **properties)
Definition mux.py:140
framecpp_channel_mux(pipeline, channel_src_map, units=None, seglists=None, **properties)
Definition mux.py:160