gstlal  1.4.1
lal_checktimestamps.py
Go to the documentation of this file.
1 # Copyright (C) 2009--2013 Kipp Cannon
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by the
5 # Free Software Foundation; either version 2 of the License, or (at your
6 # option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 # Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 
17 
18 #
19 # =============================================================================
20 #
21 # Preamble
22 #
23 # =============================================================================
24 #
25 
26 
27 import sys
28 
29 
30 import gi
31 gi.require_version('Gst', '1.0')
32 gi.require_version('GstAudio', '1.0')
33 gi.require_version('GstBase', '1.0')
34 from gi.repository import GObject
35 from gi.repository import Gst
36 from gi.repository import GstAudio
37 from gi.repository import GstBase
38 
39 
40 GObject.threads_init()
41 Gst.init(None)
42 
43 
44 __author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
45 __version__ = "FIXME"
46 __date__ = "FIXME"
47 
48 
49 #
50 # =============================================================================
51 #
52 # Element
53 #
54 # =============================================================================
55 #
56 
57 
58 
60 
61 
62 
71 
72 
73 def printable_timestamp(timestamp):
74  """!
75  A function to nicely format a timestamp for printing
76  """
77  if timestamp is None or timestamp == Gst.CLOCK_TIME_NONE:
78  return "(none)"
79  return "%d.%09d s" % (timestamp // Gst.SECOND, timestamp % Gst.SECOND)
80 
81 
82 def gst_buffer_flag_is_set(buf, flags):
83  # FIXME: this should not be needed. figure out how
84  # GST_BUFFER_FLAG_IS_SET() is exported via gir
85  return buf.mini_object.flags & flags == flags
86 
87 
88 class lal_checktimestamps(GstBase.BaseTransform):
89  """!
90  Gstreamer element that will verify that the timestamps agree with
91  incoming buffers based on tracking the buffer offsets.
92  """
93  __gstmetadata__ = (
94  "Timestamp Checker Pass-Through Element",
95  "Generic",
96  "Checks that timestamps and offsets of audio streams advance as expected and remain synchronized to each other",
97  __author__
98  )
99 
100  #silent = GObject.Property(type = GObject.TYPE_BOOLEAN, default = False, nick = "silent", blurb = "Only report errors.", flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
101  #timestamp_fuzz = GObject.Property(type = GObject.TYPE_INT64, minimum = 0, maximum = GObject.G_MAXINT64, default = 1, nick = "timestamp fuzz", blurb = "Number of nanoseconds of timestamp<-->offset discrepancy to accept before reporting it. Timestamp<-->offset discrepancies of 1/2 a sample or more are always reported.", flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
102 
103  __gproperties__ = {
104  "silent": (
105  GObject.TYPE_BOOLEAN,
106  "silent",
107  "Only report errors.",
108  True,
109  GObject.ParamFlags.READWRITE
110  ),
111  "timestamp-fuzz": (
112  GObject.TYPE_INT64,
113  "timestamp fuzz",
114  "Number of nanoseconds of timestamp<-->offset discrepancy to accept before reporting it. Timestamp<-->offset discrepancies of 1/2 a sample or more are always reported.",
115  0, GObject.G_MAXINT64, 1,
116  GObject.ParamFlags.READWRITE
117  )
118  }
119 
120  __gsttemplates__ = (
121  Gst.PadTemplate.new("sink",
122  Gst.PadDirection.SINK,
123  Gst.PadPresence.ALWAYS,
124  Gst.caps_from_string(
125  "audio/x-raw, " +
126  "rate = " + GstAudio.AUDIO_RATE_RANGE + ", " +
127  "channels = " + GstAudio.AUDIO_CHANNELS_RANGE + ", " +
128  "format = (string) { Z64LE, Z64BE, Z128LE, Z128BE }, " +
129  "layout = (string) interleaved, " +
130  "channel-mask = (bitmask) 0; " +
131  "audio/x-raw, " +
132  "rate = " + GstAudio.AUDIO_RATE_RANGE + ", " +
133  "channels = " + GstAudio.AUDIO_CHANNELS_RANGE + ", " +
134  "format = " + GstAudio.AUDIO_FORMATS_ALL + ", " +
135  "layout = (string) interleaved, " +
136  "channel-mask = (bitmask) 0"
137  )
138  ),
139  Gst.PadTemplate.new("src",
140  Gst.PadDirection.SRC,
141  Gst.PadPresence.ALWAYS,
142  Gst.caps_from_string(
143  "audio/x-raw, " +
144  "rate = " + GstAudio.AUDIO_RATE_RANGE + ", " +
145  "channels = " + GstAudio.AUDIO_CHANNELS_RANGE + ", " +
146  "format = (string) { Z64LE, Z64BE, Z128LE, Z128BE }, " +
147  "layout = (string) interleaved, " +
148  "channel-mask = (bitmask) 0; " +
149  "audio/x-raw, " +
150  "rate = " + GstAudio.AUDIO_RATE_RANGE + ", " +
151  "channels = " + GstAudio.AUDIO_CHANNELS_RANGE + ", " +
152  "format = " + GstAudio.AUDIO_FORMATS_ALL + ", " +
153  "layout = (string) interleaved, " +
154  "channel-mask = (bitmask) 0"
155  )
156  )
157  )
158 
159 
160  def __init__(self):
161  super(lal_checktimestamps, self).__init__()
162  self.set_passthrough(True)
163  self.silent = True
164  self.timestamp_fuzz = 1
165 
166 
167  def do_set_property(self, prop, val):
168  if prop.name == "silent":
169  self.silent = val
170  elif prop.name == "timestamp-fuzz":
171  self.timestamp_fuzz = val
172  else:
173  raise AttributeError(prop.name)
174 
175 
176  def do_get_property(self, prop):
177  if prop.name == "silent":
178  return self.silent
179  if prop.name == "timestamp-fuzz":
180  return self.timestamp_fuzz
181  raise AttributeError(prop.name)
182 
183 
184  def do_set_caps(self, incaps, outcaps):
185  info = GstAudio.AudioInfo()
186  if info.from_caps(incaps):
187  self.unit_size = info.bpf
188  self.units_per_second = info.rate
189  return True
190  s = incaps.get_structure(0)
191  if not s or s.get_name() != "audio/x-raw":
192  return False
193  success, chnls = s.get_int("channels")
194  if success:
195  success, rate = s.get_int("rate")
196  if not success:
197  return False
198  fmt = s.get_string("format")
199  if fmt == "Z64LE":
200  self.unit_size = 8 * chnls
201  self.units_per_second = rate
202  return True
203  elif fmt == "Z128LE":
204  self.unit_size = 16 * chnls
205  self.units_per_second = rate
206  return True
207  return False
208 
209 
210  def do_start(self):
211  self.t0 = None
212  self.offset0 = None
213  self.next_offset = None
214  self.next_timestamp = None
215  return True
216 
217 
218  def check_time_offset_mismatch(self, buf):
219  expected_offset = self.offset0 + int(round((buf.pts - self.t0) * float(self.units_per_second) / Gst.SECOND))
220  expected_timestamp = self.t0 + int(round((buf.offset - self.offset0) * Gst.SECOND / float(self.units_per_second)))
221  if buf.offset != expected_offset:
222  print >>sys.stderr, "%s: timestamp/offset mismatch%s: got offset %d, buffer timestamp %s corresponds to offset %d (error = %d samples)" % (self.get_property("name"), (" at discontinuity" if gst_buffer_flag_is_set(buf, Gst.BufferFlags.DISCONT) else ""), buf.offset, printable_timestamp(buf.pts), expected_offset, buf.offset - expected_offset)
223  elif abs(buf.pts - expected_timestamp) > self.timestamp_fuzz:
224  print >>sys.stderr, "%s: timestamp/offset mismatch%s: got timestamp %s, buffer offset %d corresponds to timestamp %s (error = %d ns)" % (self.get_property("name"), (" at discontinuity" if gst_buffer_flag_is_set(buf, Gst.BufferFlags.DISCONT) else ""), printable_timestamp(buf.pts), buf.offset, printable_timestamp(expected_timestamp), buf.pts - expected_timestamp)
225 
226 
227  def do_transform_ip(self, buf):
228  if self.t0 is None or gst_buffer_flag_is_set(buf, Gst.BufferFlags.DISCONT):
229  if self.t0 is None:
230  if not self.silent:
231  print >>sys.stderr, "%s: initial timestamp = %s, offset = %d" % (self.get_property("name"), printable_timestamp(buf.pts), buf.offset)
232  elif gst_buffer_flag_is_set(buf, Gst.BufferFlags.DISCONT):
233  print >>sys.stderr, "%s: DISCONT buffer: timestamp = %s, offset = %d; would have been %s, offset = %d" % (self.get_property("name"), printable_timestamp(buf.pts), buf.offset, printable_timestamp(self.next_timestamp), self.next_offset)
234 
235  #
236  # check for timestamp/offset mismatch
237  #
238 
240 
241  #
242  # reset/initialize timestamp book-keeping
243  #
244 
245  self.next_timestamp = self.t0 = buf.pts
246  self.next_offset = self.offset0 = buf.offset
247  else:
248  #
249  # check for timestamp/offset discontinuities
250  #
251 
252  if buf.pts != self.next_timestamp:
253  print >>sys.stderr, "%s: got timestamp %s expected %s (discont flag is %s)" % (self.get_property("name"), printable_timestamp(buf.pts), printable_timestamp(self.next_timestamp), "set" if gst_buffer_flag_is_set(buf, Gst.BufferFlags.DISCONT) else "not set")
254  if buf.offset != self.next_offset:
255  print >>sys.stderr, "%s: got offset %d expected %d (discont flag is %s)" % (self.get_property("name"), buf.offset, self.next_offset, "set" if gst_buffer_flag_is_set(buf, Gst.BufferFlags.DISCONT) else "not set")
256 
257  #
258  # check for timestamp/offset mismatch
259  #
260 
262 
263  #
264  # check for buffer size / sample count mismatch
265  #
266 
267  length = buf.offset_end - buf.offset
268  allowed_sizes = [length * self.unit_size]
269  if gst_buffer_flag_is_set(buf, Gst.BufferFlags.GAP):
270  allowed_sizes.append(0)
271  if buf.get_size() not in allowed_sizes:
272  print >>sys.stderr, "%s: got buffer size %d, buffer length %d corresponds to size %d" % (self.get_property("name"), buf.get_size(), length, length * self.unit_size)
273 
274  #
275  # reset for next buffer
276  #
277 
278  self.next_offset = buf.offset_end
279  self.next_timestamp = buf.pts + buf.duration
280 
281  #
282  # done
283  #
284 
285  return Gst.FlowReturn.OK
286 
287 
288 #
289 # register element class
290 #
291 
292 
293 GObject.type_register(lal_checktimestamps)
294 
295 __gstelementfactory__ = (
296  lal_checktimestamps.__name__,
297  Gst.Rank.NONE,
298  lal_checktimestamps
299 )
def printable_timestamp(timestamp)
A function to nicely format a timestamp for printing.
Gstreamer element that will verify that the timestamps agree with incoming buffers based on tracking ...