• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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