gstlal 1.13.0
Loading...
Searching...
No Matches
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
27import sys
28
29
30import gi
31gi.require_version('Gst', '1.0')
32gi.require_version('GstAudio', '1.0')
33gi.require_version('GstBase', '1.0')
34from gi.repository import GObject
35from gi.repository import Gst
36from gi.repository import GstAudio
37from gi.repository import GstBase
38
39
40GObject.threads_init()
41Gst.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
73def 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
82def 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
88class 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("%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), file=sys.stderr)
223 elif abs(buf.pts - expected_timestamp) > self.timestamp_fuzz:
224 print("%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), file=sys.stderr)
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("%s: initial timestamp = %s, offset = %d" % (self.get_property("name"), printable_timestamp(buf.pts), buf.offset), file=sys.stderr)
232 elif gst_buffer_flag_is_set(buf, Gst.BufferFlags.DISCONT):
233 print("%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), file=sys.stderr)
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("%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"), file=sys.stderr)
254 if buf.offset != self.next_offset:
255 print("%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"), file=sys.stderr)
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("%s: got buffer size %d, buffer length %d corresponds to size %d" % (self.get_property("name"), buf.get_size(), length, length * self.unit_size), file=sys.stderr)
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
293GObject.type_register(lal_checktimestamps)
294
295__gstelementfactory__ = (
296 lal_checktimestamps.__name__,
297 Gst.Rank.NONE,
298 lal_checktimestamps
299)
Gstreamer element that will verify that the timestamps agree with incoming buffers based on tracking ...
printable_timestamp(timestamp)
A function to nicely format a timestamp for printing.