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 g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (alignment_threshold), NULL);
90 g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (discont_wait), NULL);
91
92 align = g_new0 (GstAudioStreamAlign, 1);
93 align->rate = rate;
94 align->alignment_threshold = alignment_threshold;
95 align->discont_wait = discont_wait;
96
97 align->timestamp_at_discont = GST_CLOCK_TIME_NONE;
98 align->samples_since_discont = 0;
99 gst_audio_stream_align_mark_discont (align);
100
101 return align;
102 }
103
104 /**
105 * gst_audio_stream_align_copy:
106 * @align: a #GstAudioStreamAlign
107 *
108 * Copy a GstAudioStreamAlign structure.
109 *
110 * Returns: a new #GstAudioStreamAlign. free with gst_audio_stream_align_free.
111 *
112 * Since: 1.14
113 */
114 GstAudioStreamAlign *
gst_audio_stream_align_copy(const GstAudioStreamAlign * align)115 gst_audio_stream_align_copy (const GstAudioStreamAlign * align)
116 {
117 GstAudioStreamAlign *copy;
118
119 g_return_val_if_fail (align != NULL, NULL);
120
121 copy = g_new0 (GstAudioStreamAlign, 1);
122 *copy = *align;
123
124 return copy;
125 }
126
127 /**
128 * gst_audio_stream_align_free:
129 * @align: a #GstAudioStreamAlign
130 *
131 * Free a GstAudioStreamAlign structure previously allocated with gst_audio_stream_align_new()
132 * or gst_audio_stream_align_copy().
133 *
134 * Since: 1.14
135 */
136 void
gst_audio_stream_align_free(GstAudioStreamAlign * align)137 gst_audio_stream_align_free (GstAudioStreamAlign * align)
138 {
139 g_return_if_fail (align != NULL);
140 g_free (align);
141 }
142
143 /**
144 * gst_audio_stream_align_set_rate:
145 * @align: a #GstAudioStreamAlign
146 * @rate: a new sample rate
147 *
148 * Sets @rate as new sample rate for the following processing. If the sample
149 * rate differs this implicitly marks the next data as discontinuous.
150 *
151 * Since: 1.14
152 */
153 void
gst_audio_stream_align_set_rate(GstAudioStreamAlign * align,gint rate)154 gst_audio_stream_align_set_rate (GstAudioStreamAlign * align, gint rate)
155 {
156 g_return_if_fail (align != NULL);
157 g_return_if_fail (rate != 0);
158
159 if (align->rate == rate)
160 return;
161
162 align->rate = rate;
163 gst_audio_stream_align_mark_discont (align);
164 }
165
166 /**
167 * gst_audio_stream_align_get_rate:
168 * @align: a #GstAudioStreamAlign
169 *
170 * Gets the currently configured sample rate.
171 *
172 * Returns: The currently configured sample rate
173 *
174 * Since: 1.14
175 */
176 gint
gst_audio_stream_align_get_rate(const GstAudioStreamAlign * align)177 gst_audio_stream_align_get_rate (const GstAudioStreamAlign * align)
178 {
179 g_return_val_if_fail (align != NULL, 0);
180
181 return align->rate;
182 }
183
184 /**
185 * gst_audio_stream_align_set_alignment_threshold:
186 * @align: a #GstAudioStreamAlign
187 * @alignment_threshold: a new alignment threshold
188 *
189 * Sets @alignment_treshold as new alignment threshold for the following processing.
190 *
191 * Since: 1.14
192 */
193 void
gst_audio_stream_align_set_alignment_threshold(GstAudioStreamAlign * align,GstClockTime alignment_threshold)194 gst_audio_stream_align_set_alignment_threshold (GstAudioStreamAlign *
195 align, GstClockTime alignment_threshold)
196 {
197 g_return_if_fail (align != NULL);
198 g_return_if_fail (GST_CLOCK_TIME_IS_VALID (alignment_threshold));
199
200 align->alignment_threshold = alignment_threshold;
201 }
202
203 /**
204 * gst_audio_stream_align_get_alignment_threshold:
205 * @align: a #GstAudioStreamAlign
206 *
207 * Gets the currently configured alignment threshold.
208 *
209 * Returns: The currently configured alignment threshold
210 *
211 * Since: 1.14
212 */
213 GstClockTime
gst_audio_stream_align_get_alignment_threshold(const GstAudioStreamAlign * align)214 gst_audio_stream_align_get_alignment_threshold (const GstAudioStreamAlign *
215 align)
216 {
217 g_return_val_if_fail (align != NULL, 0);
218
219 return align->alignment_threshold;
220 }
221
222 /**
223 * gst_audio_stream_align_set_discont_wait:
224 * @align: a #GstAudioStreamAlign
225 * @discont_wait: a new discont wait
226 *
227 * Sets @alignment_treshold as new discont wait for the following processing.
228 *
229 * Since: 1.14
230 */
231 void
gst_audio_stream_align_set_discont_wait(GstAudioStreamAlign * align,GstClockTime discont_wait)232 gst_audio_stream_align_set_discont_wait (GstAudioStreamAlign * align,
233 GstClockTime discont_wait)
234 {
235 g_return_if_fail (align != NULL);
236 g_return_if_fail (GST_CLOCK_TIME_IS_VALID (discont_wait));
237
238 align->discont_wait = discont_wait;
239 }
240
241 /**
242 * gst_audio_stream_align_get_discont_wait:
243 * @align: a #GstAudioStreamAlign
244 *
245 * Gets the currently configured discont wait.
246 *
247 * Returns: The currently configured discont wait
248 *
249 * Since: 1.14
250 */
251 GstClockTime
gst_audio_stream_align_get_discont_wait(const GstAudioStreamAlign * align)252 gst_audio_stream_align_get_discont_wait (const GstAudioStreamAlign * align)
253 {
254 g_return_val_if_fail (align != NULL, 0);
255
256 return align->discont_wait;
257 }
258
259 /**
260 * gst_audio_stream_align_mark_discont:
261 * @align: a #GstAudioStreamAlign
262 *
263 * Marks the next buffer as discontinuous and resets timestamp tracking.
264 *
265 * Since: 1.14
266 */
267 void
gst_audio_stream_align_mark_discont(GstAudioStreamAlign * align)268 gst_audio_stream_align_mark_discont (GstAudioStreamAlign * align)
269 {
270 g_return_if_fail (align != NULL);
271
272 align->next_offset = -1;
273 align->discont_time = GST_CLOCK_TIME_NONE;
274 }
275
276 /**
277 * gst_audio_stream_align_get_timestamp_at_discont:
278 * @align: a #GstAudioStreamAlign
279 *
280 * Timestamp that was passed when a discontinuity was detected, i.e. the first
281 * timestamp after the discontinuity.
282 *
283 * Returns: The last timestamp at when a discontinuity was detected
284 *
285 * Since: 1.14
286 */
287 GstClockTime
gst_audio_stream_align_get_timestamp_at_discont(const GstAudioStreamAlign * align)288 gst_audio_stream_align_get_timestamp_at_discont (const GstAudioStreamAlign *
289 align)
290 {
291 g_return_val_if_fail (align != NULL, GST_CLOCK_TIME_NONE);
292
293 return align->timestamp_at_discont;
294 }
295
296 /**
297 * gst_audio_stream_align_get_samples_since_discont:
298 * @align: a #GstAudioStreamAlign
299 *
300 * Returns the number of samples that were processed since the last
301 * discontinuity was detected.
302 *
303 * Returns: The number of samples processed since the last discontinuity.
304 *
305 * Since: 1.14
306 */
307 guint64
gst_audio_stream_align_get_samples_since_discont(const GstAudioStreamAlign * align)308 gst_audio_stream_align_get_samples_since_discont (const GstAudioStreamAlign *
309 align)
310 {
311 g_return_val_if_fail (align != NULL, 0);
312
313 return align->samples_since_discont;
314 }
315
316 /**
317 * gst_audio_stream_align_process:
318 * @align: a #GstAudioStreamAlign
319 * @discont: if this data is considered to be discontinuous
320 * @timestamp: a #GstClockTime of the start of the data
321 * @n_samples: number of samples to process
322 * @out_timestamp: (out): output timestamp of the data
323 * @out_duration: (out): output duration of the data
324 * @out_sample_position: (out): output sample position of the start of the data
325 *
326 * Processes data with @timestamp and @n_samples, and returns the output
327 * timestamp, duration and sample position together with a boolean to signal
328 * whether a discontinuity was detected or not. All non-discontinuous data
329 * will have perfect timestamps and durations.
330 *
331 * A discontinuity is detected once the difference between the actual
332 * timestamp and the timestamp calculated from the sample count since the last
333 * discontinuity differs by more than the alignment threshold for a duration
334 * longer than discont wait.
335 *
336 * Note: In reverse playback, every buffer is considered discontinuous in the
337 * context of buffer flags because the last sample of the previous buffer is
338 * discontinuous with the first sample of the current one. However for this
339 * function they are only considered discontinuous in reverse playback if the
340 * first sample of the previous buffer is discontinuous with the last sample
341 * of the current one.
342 *
343 * Returns: %TRUE if a discontinuity was detected, %FALSE otherwise.
344 *
345 * Since: 1.14
346 */
347 #define ABSDIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a))
348 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)349 gst_audio_stream_align_process (GstAudioStreamAlign * align,
350 gboolean discont, GstClockTime timestamp, guint n_samples,
351 GstClockTime * out_timestamp, GstClockTime * out_duration,
352 guint64 * out_sample_position)
353 {
354 GstClockTime start_time, end_time, duration;
355 guint64 start_offset, end_offset;
356
357 g_return_val_if_fail (align != NULL, FALSE);
358
359 start_time = timestamp;
360 start_offset =
361 gst_util_uint64_scale (start_time, ABS (align->rate), GST_SECOND);
362
363 end_offset = start_offset + n_samples;
364 end_time =
365 gst_util_uint64_scale_int (end_offset, GST_SECOND, ABS (align->rate));
366
367 duration = end_time - start_time;
368
369 if (align->next_offset == (guint64) - 1 || discont) {
370 discont = TRUE;
371 } else {
372 guint64 diff, max_sample_diff;
373
374 /* Check discont */
375 if (align->rate > 0) {
376 diff = ABSDIFF (start_offset, align->next_offset);
377 } else {
378 diff = ABSDIFF (end_offset, align->next_offset);
379 }
380
381 max_sample_diff =
382 gst_util_uint64_scale_int (align->alignment_threshold,
383 ABS (align->rate), GST_SECOND);
384
385 /* Discont! */
386 if (G_UNLIKELY (diff >= max_sample_diff)) {
387 if (align->discont_wait > 0) {
388 if (align->discont_time == GST_CLOCK_TIME_NONE) {
389 align->discont_time = align->rate > 0 ? start_time : end_time;
390 } else if ((align->rate > 0
391 && ABSDIFF (start_time,
392 align->discont_time) >= align->discont_wait)
393 || (align->rate < 0
394 && ABSDIFF (end_time,
395 align->discont_time) >= align->discont_wait)) {
396 discont = TRUE;
397 align->discont_time = GST_CLOCK_TIME_NONE;
398 }
399 } else {
400 discont = TRUE;
401 }
402 } else if (G_UNLIKELY (align->discont_time != GST_CLOCK_TIME_NONE)) {
403 /* we have had a discont, but are now back on track! */
404 align->discont_time = GST_CLOCK_TIME_NONE;
405 }
406 }
407
408 if (discont) {
409 /* Have discont, need resync and use the capture timestamps */
410 if (align->next_offset != (guint64) - 1)
411 GST_INFO ("Have discont. Expected %"
412 G_GUINT64_FORMAT ", got %" G_GUINT64_FORMAT,
413 align->next_offset, start_offset);
414 align->next_offset = align->rate > 0 ? end_offset : start_offset;
415 align->timestamp_at_discont = start_time;
416 align->samples_since_discont = 0;
417
418 /* Got a discont and adjusted, reset the discont_time marker */
419 align->discont_time = GST_CLOCK_TIME_NONE;
420 } else {
421
422 /* No discont, just keep counting */
423 if (align->rate > 0) {
424 timestamp =
425 gst_util_uint64_scale (align->next_offset, GST_SECOND,
426 ABS (align->rate));
427
428 start_offset = align->next_offset;
429 align->next_offset += n_samples;
430
431 duration =
432 gst_util_uint64_scale (align->next_offset, GST_SECOND,
433 ABS (align->rate)) - timestamp;
434 } else {
435 guint64 old_offset = align->next_offset;
436
437 if (align->next_offset > n_samples)
438 align->next_offset -= n_samples;
439 else
440 align->next_offset = 0;
441 start_offset = align->next_offset;
442
443 timestamp =
444 gst_util_uint64_scale (align->next_offset, GST_SECOND,
445 ABS (align->rate));
446
447 duration =
448 gst_util_uint64_scale (old_offset, GST_SECOND,
449 ABS (align->rate)) - timestamp;
450 }
451 }
452
453 align->samples_since_discont += n_samples;
454
455 if (out_timestamp)
456 *out_timestamp = timestamp;
457 if (out_duration)
458 *out_duration = duration;
459 if (out_sample_position)
460 *out_sample_position = start_offset;
461
462 return discont;
463 }
464
465 #undef ABSDIFF
466