1 /* GStreamer
2 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
3 *
4 * gstaudiostreamalign.h:
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "gstaudiostreamalign.h"
27
28 /**
29 * SECTION:gstaudiostreamalign
30 * @title: GstAudioStreamAlign
31 * @short_description: Helper object for tracking audio stream alignment and discontinuities
32 *
33 * #GstAudioStreamAlign provides a helper object that helps tracking audio
34 * stream alignment and discontinuities, and detects discontinuities if
35 * possible.
36 *
37 * See gst_audio_stream_align_new() for a description of its parameters and
38 * gst_audio_stream_align_process() for the details of the processing.
39 */
40
41 G_DEFINE_BOXED_TYPE (GstAudioStreamAlign, gst_audio_stream_align,
42 (GBoxedCopyFunc) gst_audio_stream_align_copy,
43 (GBoxedFreeFunc) gst_audio_stream_align_free);
44
45 struct _GstAudioStreamAlign
46 {
47 gint rate;
48 GstClockTime alignment_threshold;
49 GstClockTime discont_wait;
50
51 /* counter to keep track of timestamps */
52 guint64 next_offset;
53 GstClockTime timestamp_at_discont;
54 guint64 samples_since_discont;
55
56 /* Last time we noticed a discont */
57 GstClockTime discont_time;
58 };
59
60 /**
61 * gst_audio_stream_align_new:
62 * @rate: a sample rate
63 * @alignment_threshold: a alignment threshold in nanoseconds
64 * @discont_wait: discont wait in nanoseconds
65 *
66 * Allocate a new #GstAudioStreamAlign with the given configuration. All
67 * processing happens according to sample rate @rate, until
68 * gst_audio_stream_align_set_rate() is called with a new @rate.
69 * A negative rate can be used for reverse playback.
70 *
71 * @alignment_threshold gives the tolerance in nanoseconds after which a
72 * timestamp difference is considered a discontinuity. Once detected,
73 * @discont_wait nanoseconds have to pass without going below the threshold
74 * again until the output buffer is marked as a discontinuity. These can later
75 * be re-configured with gst_audio_stream_align_set_alignment_threshold() and
76 * gst_audio_stream_align_set_discont_wait().
77 *
78 * Returns: a new #GstAudioStreamAlign. free with gst_audio_stream_align_free().
79 *
80 * Since: 1.14
81 */
82 GstAudioStreamAlign *
gst_audio_stream_align_new(gint rate,GstClockTime alignment_threshold,GstClockTime discont_wait)83 gst_audio_stream_align_new (gint rate, GstClockTime alignment_threshold,
84 GstClockTime discont_wait)
85 {
86 GstAudioStreamAlign *align;
87
88 g_return_val_if_fail (rate != 0, NULL);
89
90 align = g_new0 (GstAudioStreamAlign, 1);
91 align->rate = rate;
92 align->alignment_threshold = alignment_threshold;
93 align->discont_wait = discont_wait;
94
95 align->timestamp_at_discont = GST_CLOCK_TIME_NONE;
96 align->samples_since_discont = 0;
97 gst_audio_stream_align_mark_discont (align);
98
99 return align;
100 }
101
102 /**
103 * gst_audio_stream_align_copy:
104 * @align: a #GstAudioStreamAlign
105 *
106 * Copy a GstAudioStreamAlign structure.
107 *
108 * Returns: a new #GstAudioStreamAlign. free with gst_audio_stream_align_free.
109 *
110 * Since: 1.14
111 */
112 GstAudioStreamAlign *
gst_audio_stream_align_copy(const GstAudioStreamAlign * align)113 gst_audio_stream_align_copy (const GstAudioStreamAlign * align)
114 {
115 GstAudioStreamAlign *copy;
116
117 g_return_val_if_fail (align != NULL, NULL);
118
119 copy = g_new0 (GstAudioStreamAlign, 1);
120 *copy = *align;
121
122 return copy;
123 }
124
125 /**
126 * gst_audio_stream_align_free:
127 * @align: a #GstAudioStreamAlign
128 *
129 * Free a GstAudioStreamAlign structure previously allocated with gst_audio_stream_align_new()
130 * or gst_audio_stream_align_copy().
131 *
132 * Since: 1.14
133 */
134 void
gst_audio_stream_align_free(GstAudioStreamAlign * align)135 gst_audio_stream_align_free (GstAudioStreamAlign * align)
136 {
137 g_return_if_fail (align != NULL);
138 g_free (align);
139 }
140
141 /**
142 * gst_audio_stream_align_set_rate:
143 * @align: a #GstAudioStreamAlign
144 * @rate: a new sample rate
145 *
146 * Sets @rate as new sample rate for the following processing. If the sample
147 * rate differs this implicitely marks the next data as discontinuous.
148 *
149 * Since: 1.14
150 */
151 void
gst_audio_stream_align_set_rate(GstAudioStreamAlign * align,gint rate)152 gst_audio_stream_align_set_rate (GstAudioStreamAlign * align, gint rate)
153 {
154 g_return_if_fail (align != NULL);
155 g_return_if_fail (rate != 0);
156
157 if (align->rate == rate)
158 return;
159
160 align->rate = rate;
161 gst_audio_stream_align_mark_discont (align);
162 }
163
164 /**
165 * gst_audio_stream_align_get_rate:
166 * @align: a #GstAudioStreamAlign
167 *
168 * Gets the currently configured sample rate.
169 *
170 * Returns: The currently configured sample rate
171 *
172 * Since: 1.14
173 */
174 gint
gst_audio_stream_align_get_rate(GstAudioStreamAlign * align)175 gst_audio_stream_align_get_rate (GstAudioStreamAlign * align)
176 {
177 g_return_val_if_fail (align != NULL, 0);
178
179 return align->rate;
180 }
181
182 /**
183 * gst_audio_stream_align_set_alignment_threshold:
184 * @align: a #GstAudioStreamAlign
185 * @alignment_threshold: a new alignment threshold
186 *
187 * Sets @alignment_treshold as new alignment threshold for the following processing.
188 *
189 * Since: 1.14
190 */
191 void
gst_audio_stream_align_set_alignment_threshold(GstAudioStreamAlign * align,GstClockTime alignment_threshold)192 gst_audio_stream_align_set_alignment_threshold (GstAudioStreamAlign *
193 align, GstClockTime alignment_threshold)
194 {
195 g_return_if_fail (align != NULL);
196
197 align->alignment_threshold = alignment_threshold;
198 }
199
200 /**
201 * gst_audio_stream_align_get_alignment_threshold:
202 * @align: a #GstAudioStreamAlign
203 *
204 * Gets the currently configured alignment threshold.
205 *
206 * Returns: The currently configured alignment threshold
207 *
208 * Since: 1.14
209 */
210 GstClockTime
gst_audio_stream_align_get_alignment_threshold(GstAudioStreamAlign * align)211 gst_audio_stream_align_get_alignment_threshold (GstAudioStreamAlign * align)
212 {
213 g_return_val_if_fail (align != NULL, 0);
214
215 return align->alignment_threshold;
216 }
217
218 /**
219 * gst_audio_stream_align_set_discont_wait:
220 * @align: a #GstAudioStreamAlign
221 * @discont_wait: a new discont wait
222 *
223 * Sets @alignment_treshold as new discont wait for the following processing.
224 *
225 * Since: 1.14
226 */
227 void
gst_audio_stream_align_set_discont_wait(GstAudioStreamAlign * align,GstClockTime discont_wait)228 gst_audio_stream_align_set_discont_wait (GstAudioStreamAlign * align,
229 GstClockTime discont_wait)
230 {
231 g_return_if_fail (align != NULL);
232
233 align->discont_wait = discont_wait;
234 }
235
236 /**
237 * gst_audio_stream_align_get_discont_wait:
238 * @align: a #GstAudioStreamAlign
239 *
240 * Gets the currently configured discont wait.
241 *
242 * Returns: The currently configured discont wait
243 *
244 * Since: 1.14
245 */
246 GstClockTime
gst_audio_stream_align_get_discont_wait(GstAudioStreamAlign * align)247 gst_audio_stream_align_get_discont_wait (GstAudioStreamAlign * align)
248 {
249 g_return_val_if_fail (align != NULL, 0);
250
251 return align->discont_wait;
252 }
253
254 /**
255 * gst_audio_stream_align_mark_discont:
256 * @align: a #GstAudioStreamAlign
257 *
258 * Marks the next buffer as discontinuous and resets timestamp tracking.
259 *
260 * Since: 1.14
261 */
262 void
gst_audio_stream_align_mark_discont(GstAudioStreamAlign * align)263 gst_audio_stream_align_mark_discont (GstAudioStreamAlign * align)
264 {
265 g_return_if_fail (align != NULL);
266
267 align->next_offset = -1;
268 align->discont_time = GST_CLOCK_TIME_NONE;
269 }
270
271 /**
272 * gst_audio_stream_align_get_timestamp_at_discont:
273 * @align: a #GstAudioStreamAlign
274 *
275 * Timestamp that was passed when a discontinuity was detected, i.e. the first
276 * timestamp after the discontinuity.
277 *
278 * Returns: The last timestamp at when a discontinuity was detected
279 *
280 * Since: 1.14
281 */
282 GstClockTime
gst_audio_stream_align_get_timestamp_at_discont(GstAudioStreamAlign * align)283 gst_audio_stream_align_get_timestamp_at_discont (GstAudioStreamAlign * align)
284 {
285 g_return_val_if_fail (align != NULL, GST_CLOCK_TIME_NONE);
286
287 return align->timestamp_at_discont;
288 }
289
290 /**
291 * gst_audio_stream_align_get_samples_since_discont:
292 * @align: a #GstAudioStreamAlign
293 *
294 * Returns the number of samples that were processed since the last
295 * discontinuity was detected.
296 *
297 * Returns: The number of samples processed since the last discontinuity.
298 *
299 * Since: 1.14
300 */
301 guint64
gst_audio_stream_align_get_samples_since_discont(GstAudioStreamAlign * align)302 gst_audio_stream_align_get_samples_since_discont (GstAudioStreamAlign * align)
303 {
304 g_return_val_if_fail (align != NULL, 0);
305
306 return align->samples_since_discont;
307 }
308
309 /**
310 * gst_audio_stream_align_process:
311 * @align: a #GstAudioStreamAlign
312 * @discont: if this data is considered to be discontinuous
313 * @timestamp: a #GstClockTime of the start of the data
314 * @n_samples: number of samples to process
315 * @out_timestamp: (out): output timestamp of the data
316 * @out_duration: (out): output duration of the data
317 * @out_sample_position: (out): output sample position of the start of the data
318 *
319 * Processes data with @timestamp and @n_samples, and returns the output
320 * timestamp, duration and sample position together with a boolean to signal
321 * whether a discontinuity was detected or not. All non-discontinuous data
322 * will have perfect timestamps and durations.
323 *
324 * A discontinuity is detected once the difference between the actual
325 * timestamp and the timestamp calculated from the sample count since the last
326 * discontinuity differs by more than the alignment threshold for a duration
327 * longer than discont wait.
328 *
329 * Note: In reverse playback, every buffer is considered discontinuous in the
330 * context of buffer flags because the last sample of the previous buffer is
331 * discontinuous with the first sample of the current one. However for this
332 * function they are only considered discontinuous in reverse playback if the
333 * first sample of the previous buffer is discontinuous with the last sample
334 * of the current one.
335 *
336 * Returns: %TRUE if a discontinuity was detected, %FALSE otherwise.
337 *
338 * Since: 1.14
339 */
340 #define ABSDIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a))
341 gboolean
gst_audio_stream_align_process(GstAudioStreamAlign * align,gboolean discont,GstClockTime timestamp,guint n_samples,GstClockTime * out_timestamp,GstClockTime * out_duration,guint64 * out_sample_position)342 gst_audio_stream_align_process (GstAudioStreamAlign * align,
343 gboolean discont, GstClockTime timestamp, guint n_samples,
344 GstClockTime * out_timestamp, GstClockTime * out_duration,
345 guint64 * out_sample_position)
346 {
347 GstClockTime start_time, end_time, duration;
348 guint64 start_offset, end_offset;
349
350 g_return_val_if_fail (align != NULL, FALSE);
351
352 start_time = timestamp;
353 start_offset =
354 gst_util_uint64_scale (start_time, ABS (align->rate), GST_SECOND);
355
356 end_offset = start_offset + n_samples;
357 end_time =
358 gst_util_uint64_scale_int (end_offset, GST_SECOND, ABS (align->rate));
359
360 duration = end_time - start_time;
361
362 if (align->next_offset == (guint64) - 1 || discont) {
363 discont = TRUE;
364 } else {
365 guint64 diff, max_sample_diff;
366
367 /* Check discont */
368 if (align->rate > 0) {
369 diff = ABSDIFF (start_offset, align->next_offset);
370 } else {
371 diff = ABSDIFF (end_offset, align->next_offset);
372 }
373
374 max_sample_diff =
375 gst_util_uint64_scale_int (align->alignment_threshold,
376 ABS (align->rate), GST_SECOND);
377
378 /* Discont! */
379 if (G_UNLIKELY (diff >= max_sample_diff)) {
380 if (align->discont_wait > 0) {
381 if (align->discont_time == GST_CLOCK_TIME_NONE) {
382 align->discont_time = align->rate > 0 ? start_time : end_time;
383 } else if ((align->rate > 0
384 && ABSDIFF (start_time,
385 align->discont_time) >= align->discont_wait)
386 || (align->rate < 0
387 && ABSDIFF (end_time,
388 align->discont_time) >= align->discont_wait)) {
389 discont = TRUE;
390 align->discont_time = GST_CLOCK_TIME_NONE;
391 }
392 } else {
393 discont = TRUE;
394 }
395 } else if (G_UNLIKELY (align->discont_time != GST_CLOCK_TIME_NONE)) {
396 /* we have had a discont, but are now back on track! */
397 align->discont_time = GST_CLOCK_TIME_NONE;
398 }
399 }
400
401 if (discont) {
402 /* Have discont, need resync and use the capture timestamps */
403 if (align->next_offset != (guint64) - 1)
404 GST_INFO ("Have discont. Expected %"
405 G_GUINT64_FORMAT ", got %" G_GUINT64_FORMAT,
406 align->next_offset, start_offset);
407 align->next_offset = align->rate > 0 ? end_offset : start_offset;
408 align->timestamp_at_discont = start_time;
409 align->samples_since_discont = 0;
410
411 /* Got a discont and adjusted, reset the discont_time marker */
412 align->discont_time = GST_CLOCK_TIME_NONE;
413 } else {
414
415 /* No discont, just keep counting */
416 if (align->rate > 0) {
417 timestamp =
418 gst_util_uint64_scale (align->next_offset, GST_SECOND,
419 ABS (align->rate));
420
421 start_offset = align->next_offset;
422 align->next_offset += n_samples;
423
424 duration =
425 gst_util_uint64_scale (align->next_offset, GST_SECOND,
426 ABS (align->rate)) - timestamp;
427 } else {
428 guint64 old_offset = align->next_offset;
429
430 if (align->next_offset > n_samples)
431 align->next_offset -= n_samples;
432 else
433 align->next_offset = 0;
434 start_offset = align->next_offset;
435
436 timestamp =
437 gst_util_uint64_scale (align->next_offset, GST_SECOND,
438 ABS (align->rate));
439
440 duration =
441 gst_util_uint64_scale (old_offset, GST_SECOND,
442 ABS (align->rate)) - timestamp;
443 }
444 }
445
446 align->samples_since_discont += n_samples;
447
448 if (out_timestamp)
449 *out_timestamp = timestamp;
450 if (out_duration)
451 *out_duration = duration;
452 if (out_sample_position)
453 *out_sample_position = start_offset;
454
455 return discont;
456 }
457
458 #undef ABSDIFF
459