17"""High-level tools to build GStreamer pipelines.
22from collections
import namedtuple
23from collections.abc
import Mapping
24from typing
import Callable, Iterable, Optional, Tuple, Union
25from typing
import Mapping
as MappingType
29gi.require_version(
'Gst',
'1.0')
30gi.require_version(
'GstAudio',
'1.0')
31gi.require_version(
'GLib',
'2.0')
32from gi.repository
import Gst, GLib
34from lal
import LIGOTimeGPS
36from gstlal
import datasource
37from gstlal
import pipeparts
38from gstlal
import pipeio
39from gstlal
import simplehandler
40from gstlal.utilities.element_registry
import ElementRegistry
43SourceElem = namedtuple(
"SourceElem",
"datasource is_live gps_range state_vector dq_vector idq_series")
44Buffer = namedtuple(
"Buffer",
"name t0 duration data caps is_gap")
46MessageType = Gst.MessageType
50 """Class for building a GStreamer-based pipeline.
54 _caps_buffer_map =
None
58 name: Optional[str] =
None,
59 mainloop: Optional[GLib.MainLoop] =
None,
60 pipeline: Optional[Gst.Pipeline] =
None,
61 handler: Optional[
"StreamHandler"] =
None,
62 source: Optional[
"SourceElem"] =
None,
63 head: Union[MappingType[str, Gst.Element], Gst.Element,
None] =
None,
65 """Create a Stream that can be used to build a GStreamer-based pipeline.
69 str, a name for the GStreamer pipeline (optional).
70 If not set, generates a unique name.
72 GLib.MainLoop, the GLib event loop to drive the GStreamer pipeline.
73 If not set, one will be created.
75 Gst.Pipeline, the GStreamer pipeline object that contains the pipeline graph.
76 If not set, one will be created.
78 StreamHandler, a handler which registers callbacks upon new bus messages and
79 stops the event loop upon EOS. If not set, one will be created.
81 SourceElem, an object that stores source information as well as state/DQ vector
82 elements. If not set, one will be created.
84 Union[MappingType[str, Gst.Element], Gst.Element], a pointer to the current
85 element in the pipeline. If not set, the Stream will not have any elements
86 attached to the pipeline upon instantiation.
99 self.
name = name
if name
else str(uuid.uuid1())
100 self.
mainloop = mainloop
if mainloop
else GLib.MainLoop()
106 self.
source = source
if source
else None
109 """Start the main event loop for this stream.
115 if not self.
source.is_live:
120 if os.environ.get(
"GST_DEBUG_DUMP_DOT_DIR",
False):
122 pipeparts.write_dump_dot(self.
pipeline, f
"{name}_PLAYING", verbose=
True)
127 def do_on_call(self, signum, frame):
128 pipeparts.write_dump_dot(self.
pipeline, f
"{name}_SIGINT", verbose=
True)
130 sighandler = SigHandler(self.
pipeline)
137 data_source_info: datasource.DataSourceInfo,
138 ifos: Union[str, Iterable[str]],
139 name: Optional[str] =
None,
140 verbose: bool =
False,
141 state_vector: bool =
False,
142 dq_vector: bool =
False,
143 idq_series: bool =
False
145 """Construct a Stream from a datasource.DataSourceInfo object.
149 DataSourceInfo, the object to construct this stream with.
151 Union[str, Iterable[str]], the detectors read timeseries data for.
153 str, a name for the GStreamer pipeline (optional).
154 If not set, generates a unique name.
156 bool, default False, whether to display logging/progress information.
158 bool, default False, whether to attach state vector information to this Stream
160 bool, default False, whether to attach data quality vector information to this Stream
162 bool, default False, whether to fetch idq data information with this Stream
165 Stream, the newly created stream.
168 is_live = data_source_info.data_source
in datasource.KNOWN_LIVE_DATASOURCES
169 if isinstance(ifos, str):
175 stream = cls(name=name, head={})
180 src, state_vectors[ifo], dq_vectors[ifo], idq_series_data[ifo] = datasource.mkbasicsrc(
188 mainloop=stream.mainloop,
189 pipeline=stream.pipeline,
190 handler=stream.handler,
194 stream.source = SourceElem(
195 datasource=data_source_info.data_source,
197 gps_range=data_source_info.seg,
198 state_vector=state_vectors
if state_vector
else None,
199 dq_vector=dq_vectors
if dq_vector
else None,
200 idq_series = idq_series_data
if idq_series
else None,
206 return stream[ifos[0]]
209 """Attach a callback to one of this element's signals.
215 func: Callable[[Buffer],
None],
216 caps: Optional[Gst.Caps] =
None
218 """Terminate this stream with an appsink element and process new buffers with a callback.
222 Callable[[Buffer], None], a callback that gets invoked when a new buffer is available
224 Gst.Caps, how to interpret the contents of the raw buffers.
225 If not set, defaults to raw audio buffers (audio/x-raw).
229 def sample_handler(elem: Gst.Element):
232 return Gst.FlowReturn.OK
234 if isinstance(self.
head, Mapping):
236 for key
in self.
keys():
239 sink = pipeparts.mkappsink(self.
pipeline, self.
head, max_buffers=1, sync=
False)
240 sink.connect(
"new-sample", sample_handler)
244 """Attach a callback which get invoked when new bus messages are available.
248 Gst.MessageType, the type of message to invoke a callback for.
256 """Set pipeline state, checking for errors.
260 Gst.State: The state to set this stream's pipeline to.
264 If the pipeline failed to transition to the state specified.
268 raise RuntimeError(f
"pipeline failed to enter {state.value_name}")
271 """Retrieve an element from the stream's pipeline by name.
275 str, the name of the element to retrieve
278 Gst.Element, the element associated with the name given.
281 return self.
pipeline.get_by_name(name)
283 def post_message(self, msg_name: None, timestamp: Optional[int] =
None) ->
None:
284 """Post a new application message to this stream's bus.
288 str, the name of the application message to send.
290 (int, optional), the timestamp to attach to this message.
293 s = Gst.Structure.new_empty(msg_name)
294 message = Gst.Message.new_application(self.
pipeline, s)
296 message.timestamp = timestamp
297 self.
pipeline.get_bus().post(message)
300 """Retrieves a new Stream with specified key.
303 return self.__class__(
309 head=self.
head.setdefault(key, {}),
313 """Attach a new Stream with specified key/value pair.
317 assert self.
name == value.name
318 assert self.
mainloop is value.mainloop
319 assert self.
pipeline is value.pipeline
320 assert self.
handler is value.handler
321 assert self.
source is value.source
323 self.
name = value.name
327 self.
source = value.source
329 self.
head[key] = value.head
331 def keys(self) -> Iterable[str]:
332 yield from self.
head.keys()
334 def values(self) -> Iterable["Stream"]:
335 for key
in self.
keys():
338 def items(self) -> Iterable[Tuple[str, "Stream"]]:
339 for key
in self.
keys():
343 """Return a new stream with all pointers to elements cleared out.
346 return self.__class__(
356 """Seek pipeline to the given gps start/end times.
359 start, end = self.
source.gps_range
360 datasource.pipeline_seek_for_gps(self.
pipeline, start, end)
363 def _pull_buffer(cls, elem: Gst.Element, caps: Optional[Gst.Caps] =
None):
365 sample = elem.emit(
"pull-sample")
366 buf = sample.get_buffer()
367 buftime = LIGOTimeGPS(0, buf.pts)
368 is_gap = bool(buf.mini_object.flags & Gst.BufferFlags.GAP)
376 for i
in range(buf.n_memory()):
377 memory = buf.peek_memory(i)
378 success, mapinfo = memory.map(Gst.MapFlags.READ)
389 memory.unmap(mapinfo)
392 data = pipeio.array_from_audio_sample(sample)
397 duration=buf.duration,
399 caps=sample.get_caps(),
404 def _load_caps_buffer_map(cls) -> None:
409 from gstlal.snglinspiraltable
import GSTLALSnglInspiral
413 bufmap[
"application/x-lal-snglinspiral"] = GSTLALSnglInspiral.from_buffer
416 from gstlal.snglbursttable
import GSTLALSnglBurst
420 bufmap[
"application/x-lal-snglburst"] = GSTLALSnglBurst.from_buffer
423 from gstlal.sngltriggertable
import GSTLALSnglTrigger
427 bufmap[
"application/gstlal-sngltrigger"] = GSTLALSnglTrigger.from_buffer
432 def _preroll_handler(elem: Gst.Element) -> Gst.FlowReturn:
433 buf = elem.emit(
"pull-preroll")
435 return Gst.FlowReturn.OK
439 def __init__(self, *args, **kwargs):
444 Gst.MessageType.ELEMENT: {},
445 Gst.MessageType.APPLICATION: {},
446 Gst.MessageType.EOS: {},
450 """Attach a callback which get invoked when new bus messages are available.
454 Gst.MessageType, the type of message to invoke a callback for.
465 msg_name, callback = args
467 raise ValueError(
"callback already registered for message type/name")
468 self.
callbacks[msg_type][msg_name] = callback
471 """Invoke registered callbacks when new bus messages are received.
475 Gst.Bus, the GStreamer bus.
477 Gst.Message, the message received.
480 bool, whether further message handling is performed by the parent class
481 with default cases for EOS, INFO, WARNING and ERROR messages.
485 if message.type == Gst.MessageType.EOS:
489 elif message.get_structure():
490 message_name = message.get_structure().get_name()
493 if message_name
in self.
callbacks[message.type]:
494 self.
callbacks[message.type][message_name](message)
do_on_message(self, Gst.Bus bus, Gst.Message message)
None add_callback(self, Gst.MessageType msg_type, *args)
None __setitem__(self, str key, "Stream" value)
"Stream" __getitem__(self, str key)
None bufsink(self, Callable[[Buffer], None] func, Optional[Gst.Caps] caps=None)
Gst.Element get_element_by_name(self, str name)
None _load_caps_buffer_map(cls)
"Stream" from_datasource(cls, datasource.DataSourceInfo data_source_info, Union[str, Iterable[str]] ifos, Optional[str] name=None, bool verbose=False, bool state_vector=False, bool dq_vector=False, bool idq_series=False)
None connect(self, *args, **kwargs)
None __init__(self, Optional[str] name=None, Optional[GLib.MainLoop] mainloop=None, Optional[Gst.Pipeline] pipeline=None, Optional["StreamHandler"] handler=None, Optional["SourceElem"] source=None, Union[MappingType[str, Gst.Element], Gst.Element, None] head=None)
_pull_buffer(cls, Gst.Element elem, Optional[Gst.Caps] caps=None)
None post_message(self, None msg_name, Optional[int] timestamp=None)
pipeline
Setup a signal handler to intercept SIGINT in order to write the pipeline graph at ctrl+C before clea...
None set_state(self, Gst.State state)
None add_callback(self, Gst.MessageType msg_type, *args)