• 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   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