gstlal  0.8.1
 All Classes Namespaces Files Functions Variables Pages
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 pygtk
31 pygtk.require("2.0")
32 import gobject
33 import pygst
34 pygst.require('0.10')
35 import gst
36 
37 
38 __author__ = "Kipp Cannon <kipp.cannon@ligo.org>"
39 __version__ = "FIXME"
40 __date__ = "FIXME"
41 
42 
43 #
44 # =============================================================================
45 #
46 # Element
47 #
48 # =============================================================================
49 #
50 
51 
52 ## @file lal_checktimestamps.py
53 # This gstreamer element checks timestamps; see lal_checktimestamps for more information
54 
55 
56 ## @package lal_checktimestamps
57 # A gstreamer element to check time stamps
58 #
59 # ### Review status
60 # - Code walkthrough 2014/02/12
61 # - Review git hash: ecaf8840ada2877b9b2a8a144a62874c004cd3d2
62 # - Folks involved J. Creighton, B.S. Sathyaprakash, K. Cannon, C. Hanna, F. Robinet
63 #
64 #
65 
66 def printable_timestamp(timestamp):
67  """!
68  A function to nicely format a timestamp for printing
69  """
70  if timestamp is None or timestamp == gst.CLOCK_TIME_NONE:
71  return "(none)"
72  return "%d.%09d s" % (timestamp // gst.SECOND, timestamp % gst.SECOND)
73 
74 
75 class lal_checktimestamps(gst.BaseTransform):
76  """!
77  A class representing a gstreamer element that will verify that the
78  timestamps agree with incoming buffers based on tracking the buffer offsets.
79  """
80  __gstdetails__ = (
81  "Timestamp Checker Pass-Through Element",
82  "Generic",
83  "Checks that timestamps and offsets of audio streams advance as expected and remain synchronized to each other",
84  __author__
85  )
86 
87  __gproperties__ = {
88  "timestamp-fuzz": (
89  gobject.TYPE_UINT64,
90  "timestamp fuzz",
91  "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.",
92  # FIXME: why isn't G_MAXUINT64 defined in 2.18?
93  #0, gobject.G_MAXUINT64, 1,
94  0, 18446744073709551615L, 1,
95  gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT
96  ),
97  "silent": (
98  gobject.TYPE_BOOLEAN,
99  "silent",
100  "Only report errors.",
101  False,
102  gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT
103  )
104  }
105 
106  __gsttemplates__ = (
107  gst.PadTemplate("sink",
108  gst.PAD_SINK,
109  gst.PAD_ALWAYS,
110  gst.caps_from_string(
111  "audio/x-raw-float, " +
112  "rate = (int) [1, MAX], " +
113  "channels = (int) [1, MAX], " +
114  "endianness = (int) BYTE_ORDER, " +
115  "width = (int) {32, 64};" +
116  "audio/x-raw-complex, " +
117  "rate = (int) [1, MAX], " +
118  "channels = (int) [1, MAX], " +
119  "endianness = (int) BYTE_ORDER, " +
120  "width = (int) {64, 128};" +
121  "audio/x-raw-int, " +
122  "rate = (int) [1, MAX], " +
123  "channels = (int) [1, MAX], " +
124  "endianness = (int) BYTE_ORDER, " +
125  "width = (int) 16," +
126  "depth = (int) 16," +
127  "signed = (bool) {true, false}; " +
128  "audio/x-raw-int, " +
129  "rate = (int) [1, MAX], " +
130  "channels = (int) [1, MAX], " +
131  "endianness = (int) BYTE_ORDER, " +
132  "width = (int) 32," +
133  "depth = (int) 32," +
134  "signed = (bool) {true, false}; " +
135  "audio/x-raw-int, " +
136  "rate = (int) [1, MAX], " +
137  "channels = (int) [1, MAX], " +
138  "endianness = (int) BYTE_ORDER, " +
139  "width = (int) 64," +
140  "depth = (int) 64," +
141  "signed = (bool) {true, false}"
142  )
143  ),
144  gst.PadTemplate("src",
145  gst.PAD_SRC,
146  gst.PAD_ALWAYS,
147  gst.caps_from_string(
148  "audio/x-raw-float, " +
149  "rate = (int) [1, MAX], " +
150  "channels = (int) [1, MAX], " +
151  "endianness = (int) BYTE_ORDER, " +
152  "width = (int) {32, 64};" +
153  "audio/x-raw-complex, " +
154  "rate = (int) [1, MAX], " +
155  "channels = (int) [1, MAX], " +
156  "endianness = (int) BYTE_ORDER, " +
157  "width = (int) {64, 128};" +
158  "audio/x-raw-int, " +
159  "rate = (int) [1, MAX], " +
160  "channels = (int) [1, MAX], " +
161  "endianness = (int) BYTE_ORDER, " +
162  "width = (int) 16," +
163  "depth = (int) 16," +
164  "signed = (bool) {true, false}; " +
165  "audio/x-raw-int, " +
166  "rate = (int) [1, MAX], " +
167  "channels = (int) [1, MAX], " +
168  "endianness = (int) BYTE_ORDER, " +
169  "width = (int) 32," +
170  "depth = (int) 32," +
171  "signed = (bool) {true, false}; " +
172  "audio/x-raw-int, " +
173  "rate = (int) [1, MAX], " +
174  "channels = (int) [1, MAX], " +
175  "endianness = (int) BYTE_ORDER, " +
176  "width = (int) 64," +
177  "depth = (int) 64," +
178  "signed = (bool) {true, false}"
179  )
180  )
181  )
182 
183 
184  def __init__(self):
185  super(lal_checktimestamps, self).__init__()
186  self.set_passthrough(True)
187 
188 
189  def do_set_property(self, prop, val):
190  if prop.name == "timestamp-fuzz":
191  self.timestamp_fuzz = val
192  elif prop.name == "silent":
193  self.silent = val
194 
195 
196  def do_get_property(self, prop):
197  if prop.name == "timestamp-fuzz":
198  return self.timestamp_fuzz
199  elif prop.name == "silent":
200  return self.silent
201 
202 
203  def do_set_caps(self, incaps, outcaps):
204  self.unit_size = incaps[0]["width"] // 8 * incaps[0]["channels"]
205  self.units_per_second = incaps[0]["rate"]
206  return True
207 
208 
209  def do_start(self):
210  self.t0 = None
211  self.offset0 = None
212  self.next_offset = None
213  self.next_timestamp = None
214  return True
215 
216 
217  def check_time_offset_mismatch(self, buf):
218  expected_offset = self.offset0 + int(round((buf.timestamp - self.t0) * float(self.units_per_second) / gst.SECOND))
219  expected_timestamp = self.t0 + int(round((buf.offset - self.offset0) * gst.SECOND / float(self.units_per_second)))
220  if buf.offset != expected_offset:
221  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"), (buf.flag_is_set(gst.BUFFER_FLAG_DISCONT) and " at discontinuity" or ""), buf.offset, printable_timestamp(buf.timestamp), expected_offset, buf.offset - expected_offset)
222  elif abs(buf.timestamp - expected_timestamp) > self.timestamp_fuzz:
223  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"), (buf.flag_is_set(gst.BUFFER_FLAG_DISCONT) and " at discontinuity" or ""), printable_timestamp(buf.timestamp), buf.offset, printable_timestamp(expected_timestamp), buf.timestamp - expected_timestamp)
224 
225 
226  def do_transform_ip(self, buf):
227  if self.t0 is None or buf.flag_is_set(gst.BUFFER_FLAG_DISCONT):
228  if self.t0 is None:
229  if not self.silent:
230  print >>sys.stderr, "%s: initial timestamp = %s, offset = %d" % (self.get_property("name"), printable_timestamp(buf.timestamp), buf.offset)
231  elif buf.flag_is_set(gst.BUFFER_FLAG_DISCONT):
232  print >>sys.stderr, "%s: discontinuity: timestamp = %s, offset = %d; would have been %s, offset = %d" % (self.get_property("name"), printable_timestamp(buf.timestamp), buf.offset, printable_timestamp(self.next_timestamp), self.next_offset)
233 
234  #
235  # check for timestamp/offset mismatch
236  #
237 
239 
240  #
241  # reset/initialize timestamp book-keeping
242  #
243 
244  self.next_timestamp = self.t0 = buf.timestamp
245  self.next_offset = self.offset0 = buf.offset
246  else:
247  #
248  # check for timestamp/offset discontinuities
249  #
250 
251  if buf.timestamp != self.next_timestamp:
252  print >>sys.stderr, "%s: got timestamp %s expected %s (discont flag is %s)" % (self.get_property("name"), printable_timestamp(buf.timestamp), printable_timestamp(self.next_timestamp), buf.flag_is_set(gst.BUFFER_FLAG_DISCONT) and "set" or "not set")
253  if buf.offset != self.next_offset:
254  print >>sys.stderr, "%s: got offset %d expected %d (discont flag is %s)" % (self.get_property("name"), buf.offset, self.next_offset, buf.flag_is_set(gst.BUFFER_FLAG_DISCONT) and "set" or "not set")
255 
256  #
257  # check for timestamp/offset mismatch
258  #
259 
261 
262  #
263  # check for buffer size / sample count mismatch
264  #
265 
266  length = buf.offset_end - buf.offset
267  allowed_sizes = [length * self.unit_size]
268  if buf.flag_is_set(gst.BUFFER_FLAG_GAP):
269  allowed_sizes.append(0)
270  if buf.size not in allowed_sizes:
271  print >>sys.stderr, "%s: got buffer size %d, buffer length %d corresponds to size %d" % (self.get_property("name"), buf.size, length, length * self.unit_size)
272 
273  #
274  # reset for next buffer
275  #
276 
277  self.next_offset = buf.offset_end
278  self.next_timestamp = buf.timestamp + buf.duration
279 
280  #
281  # done
282  #
283 
284  return gst.FLOW_OK
285 
286 
287 #
288 # register element class
289 #
290 
291 
292 gobject.type_register(lal_checktimestamps)
293 
294 __gstelementfactory__ = (
295  lal_checktimestamps.__name__,
296  gst.RANK_NONE,
297  lal_checktimestamps
298 )