/src/gstreamer/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiostreamalign.c
Line | Count | Source |
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 | 0 | G_DEFINE_BOXED_TYPE (GstAudioStreamAlign, gst_audio_stream_align, |
42 | 0 | (GBoxedCopyFunc) gst_audio_stream_align_copy, |
43 | 0 | (GBoxedFreeFunc) gst_audio_stream_align_free); |
44 | 0 |
|
45 | 0 | struct _GstAudioStreamAlign |
46 | 0 | { |
47 | 0 | gint rate; |
48 | 0 | GstClockTime alignment_threshold; |
49 | 0 | GstClockTime discont_wait; |
50 | 0 |
|
51 | 0 | /* counter to keep track of timestamps */ |
52 | 0 | guint64 next_offset; |
53 | 0 | GstClockTime timestamp_at_discont; |
54 | 0 | guint64 samples_since_discont; |
55 | 0 |
|
56 | 0 | /* Last time we noticed a discont */ |
57 | 0 | GstClockTime discont_time; |
58 | 0 | }; |
59 | 0 |
|
60 | 0 | /** |
61 | 0 | * gst_audio_stream_align_new: |
62 | 0 | * @rate: a sample rate |
63 | 0 | * @alignment_threshold: a alignment threshold in nanoseconds |
64 | 0 | * @discont_wait: discont wait in nanoseconds |
65 | 0 | * |
66 | 0 | * Allocate a new #GstAudioStreamAlign with the given configuration. All |
67 | 0 | * processing happens according to sample rate @rate, until |
68 | 0 | * gst_audio_stream_align_set_rate() is called with a new @rate. |
69 | 0 | * A negative rate can be used for reverse playback. |
70 | 0 | * |
71 | 0 | * @alignment_threshold gives the tolerance in nanoseconds after which a |
72 | 0 | * timestamp difference is considered a discontinuity. Once detected, |
73 | 0 | * @discont_wait nanoseconds have to pass without going below the threshold |
74 | 0 | * again until the output buffer is marked as a discontinuity. These can later |
75 | 0 | * be re-configured with gst_audio_stream_align_set_alignment_threshold() and |
76 | 0 | * gst_audio_stream_align_set_discont_wait(). |
77 | 0 | * |
78 | 0 | * Returns: a new #GstAudioStreamAlign. free with gst_audio_stream_align_free(). |
79 | 0 | * |
80 | 0 | * Since: 1.14 |
81 | 0 | */ |
82 | 0 | GstAudioStreamAlign * |
83 | 0 | gst_audio_stream_align_new (gint rate, GstClockTime alignment_threshold, |
84 | 0 | GstClockTime discont_wait) |
85 | 0 | { |
86 | 0 | GstAudioStreamAlign *align; |
87 | |
|
88 | 0 | g_return_val_if_fail (rate != 0, NULL); |
89 | 0 | g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (alignment_threshold), NULL); |
90 | 0 | g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (discont_wait), NULL); |
91 | | |
92 | 0 | align = g_new0 (GstAudioStreamAlign, 1); |
93 | 0 | align->rate = rate; |
94 | 0 | align->alignment_threshold = alignment_threshold; |
95 | 0 | align->discont_wait = discont_wait; |
96 | |
|
97 | 0 | align->timestamp_at_discont = GST_CLOCK_TIME_NONE; |
98 | 0 | align->samples_since_discont = 0; |
99 | 0 | gst_audio_stream_align_mark_discont (align); |
100 | |
|
101 | 0 | return align; |
102 | 0 | } |
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 * |
115 | | gst_audio_stream_align_copy (const GstAudioStreamAlign * align) |
116 | 0 | { |
117 | 0 | GstAudioStreamAlign *copy; |
118 | |
|
119 | 0 | g_return_val_if_fail (align != NULL, NULL); |
120 | | |
121 | 0 | copy = g_new0 (GstAudioStreamAlign, 1); |
122 | 0 | *copy = *align; |
123 | |
|
124 | 0 | return copy; |
125 | 0 | } |
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 |
137 | | gst_audio_stream_align_free (GstAudioStreamAlign * align) |
138 | 0 | { |
139 | 0 | g_return_if_fail (align != NULL); |
140 | 0 | g_free (align); |
141 | 0 | } |
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 |
154 | | gst_audio_stream_align_set_rate (GstAudioStreamAlign * align, gint rate) |
155 | 0 | { |
156 | 0 | g_return_if_fail (align != NULL); |
157 | 0 | g_return_if_fail (rate != 0); |
158 | | |
159 | 0 | if (align->rate == rate) |
160 | 0 | return; |
161 | | |
162 | 0 | align->rate = rate; |
163 | 0 | gst_audio_stream_align_mark_discont (align); |
164 | 0 | } |
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 |
177 | | gst_audio_stream_align_get_rate (const GstAudioStreamAlign * align) |
178 | 0 | { |
179 | 0 | g_return_val_if_fail (align != NULL, 0); |
180 | | |
181 | 0 | return align->rate; |
182 | 0 | } |
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 |
194 | | gst_audio_stream_align_set_alignment_threshold (GstAudioStreamAlign * |
195 | | align, GstClockTime alignment_threshold) |
196 | 0 | { |
197 | 0 | g_return_if_fail (align != NULL); |
198 | 0 | g_return_if_fail (GST_CLOCK_TIME_IS_VALID (alignment_threshold)); |
199 | | |
200 | 0 | align->alignment_threshold = alignment_threshold; |
201 | 0 | } |
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 |
214 | | gst_audio_stream_align_get_alignment_threshold (const GstAudioStreamAlign * |
215 | | align) |
216 | 0 | { |
217 | 0 | g_return_val_if_fail (align != NULL, 0); |
218 | | |
219 | 0 | return align->alignment_threshold; |
220 | 0 | } |
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 |
232 | | gst_audio_stream_align_set_discont_wait (GstAudioStreamAlign * align, |
233 | | GstClockTime discont_wait) |
234 | 0 | { |
235 | 0 | g_return_if_fail (align != NULL); |
236 | 0 | g_return_if_fail (GST_CLOCK_TIME_IS_VALID (discont_wait)); |
237 | | |
238 | 0 | align->discont_wait = discont_wait; |
239 | 0 | } |
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 |
252 | | gst_audio_stream_align_get_discont_wait (const GstAudioStreamAlign * align) |
253 | 0 | { |
254 | 0 | g_return_val_if_fail (align != NULL, 0); |
255 | | |
256 | 0 | return align->discont_wait; |
257 | 0 | } |
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 |
268 | | gst_audio_stream_align_mark_discont (GstAudioStreamAlign * align) |
269 | 0 | { |
270 | 0 | g_return_if_fail (align != NULL); |
271 | | |
272 | 0 | align->next_offset = -1; |
273 | 0 | align->discont_time = GST_CLOCK_TIME_NONE; |
274 | 0 | } |
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 |
288 | | gst_audio_stream_align_get_timestamp_at_discont (const GstAudioStreamAlign * |
289 | | align) |
290 | 0 | { |
291 | 0 | g_return_val_if_fail (align != NULL, GST_CLOCK_TIME_NONE); |
292 | | |
293 | 0 | return align->timestamp_at_discont; |
294 | 0 | } |
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 |
308 | | gst_audio_stream_align_get_samples_since_discont (const GstAudioStreamAlign * |
309 | | align) |
310 | 0 | { |
311 | 0 | g_return_val_if_fail (align != NULL, 0); |
312 | | |
313 | 0 | return align->samples_since_discont; |
314 | 0 | } |
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 | 0 | #define ABSDIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a)) |
348 | | gboolean |
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 | 0 | { |
354 | 0 | GstClockTime start_time, end_time, duration; |
355 | 0 | guint64 start_offset, end_offset; |
356 | |
|
357 | 0 | g_return_val_if_fail (align != NULL, FALSE); |
358 | | |
359 | 0 | start_time = timestamp; |
360 | 0 | start_offset = |
361 | 0 | gst_util_uint64_scale (start_time, ABS (align->rate), GST_SECOND); |
362 | |
|
363 | 0 | end_offset = start_offset + n_samples; |
364 | 0 | end_time = |
365 | 0 | gst_util_uint64_scale_int (end_offset, GST_SECOND, ABS (align->rate)); |
366 | |
|
367 | 0 | duration = end_time - start_time; |
368 | |
|
369 | 0 | if (align->next_offset == (guint64) - 1 || discont) { |
370 | 0 | discont = TRUE; |
371 | 0 | } else { |
372 | 0 | guint64 diff, max_sample_diff; |
373 | 0 | GstClockTime expected_time; |
374 | | |
375 | | /* Check discont */ |
376 | 0 | if (align->rate > 0) { |
377 | 0 | diff = ABSDIFF (start_offset, align->next_offset); |
378 | 0 | } else { |
379 | 0 | diff = ABSDIFF (end_offset, align->next_offset); |
380 | 0 | } |
381 | |
|
382 | 0 | expected_time = |
383 | 0 | gst_util_uint64_scale (align->next_offset, GST_SECOND, |
384 | 0 | ABS (align->rate)); |
385 | |
|
386 | 0 | max_sample_diff = MAX (1, |
387 | 0 | gst_util_uint64_scale_int (align->alignment_threshold, |
388 | 0 | ABS (align->rate), GST_SECOND)); |
389 | | |
390 | | /* Discont! */ |
391 | 0 | if (G_UNLIKELY (diff >= max_sample_diff)) { |
392 | 0 | if (align->discont_wait > 0) { |
393 | 0 | if (align->discont_time == GST_CLOCK_TIME_NONE) { |
394 | 0 | if (align->rate > 0 |
395 | 0 | && ABSDIFF (expected_time, start_time) >= align->discont_wait) |
396 | 0 | discont = TRUE; |
397 | 0 | else if (align->rate < 0 |
398 | 0 | && ABSDIFF (expected_time, end_time) >= align->discont_wait) |
399 | 0 | discont = TRUE; |
400 | 0 | else |
401 | 0 | align->discont_time = expected_time; |
402 | 0 | } else if ((align->rate > 0 |
403 | 0 | && ABSDIFF (start_time, |
404 | 0 | align->discont_time) >= align->discont_wait) |
405 | 0 | || (align->rate < 0 |
406 | 0 | && ABSDIFF (end_time, |
407 | 0 | align->discont_time) >= align->discont_wait)) { |
408 | 0 | discont = TRUE; |
409 | 0 | align->discont_time = GST_CLOCK_TIME_NONE; |
410 | 0 | } |
411 | 0 | } else { |
412 | 0 | discont = TRUE; |
413 | 0 | } |
414 | 0 | } else if (G_UNLIKELY (align->discont_time != GST_CLOCK_TIME_NONE)) { |
415 | | /* we have had a discont, but are now back on track! */ |
416 | 0 | align->discont_time = GST_CLOCK_TIME_NONE; |
417 | 0 | } |
418 | 0 | } |
419 | |
|
420 | 0 | if (discont) { |
421 | | /* Have discont, need resync and use the capture timestamps */ |
422 | 0 | if (align->next_offset != (guint64) - 1) |
423 | 0 | GST_INFO ("Have discont. Expected %" |
424 | 0 | G_GUINT64_FORMAT ", got %" G_GUINT64_FORMAT, |
425 | 0 | align->next_offset, start_offset); |
426 | 0 | align->next_offset = align->rate > 0 ? end_offset : start_offset; |
427 | 0 | align->timestamp_at_discont = start_time; |
428 | 0 | align->samples_since_discont = 0; |
429 | | |
430 | | /* Got a discont and adjusted, reset the discont_time marker */ |
431 | 0 | align->discont_time = GST_CLOCK_TIME_NONE; |
432 | 0 | } else { |
433 | | |
434 | | /* No discont, just keep counting */ |
435 | 0 | if (align->rate > 0) { |
436 | 0 | timestamp = |
437 | 0 | gst_util_uint64_scale (align->next_offset, GST_SECOND, |
438 | 0 | ABS (align->rate)); |
439 | |
|
440 | 0 | start_offset = align->next_offset; |
441 | 0 | align->next_offset += n_samples; |
442 | |
|
443 | 0 | duration = |
444 | 0 | gst_util_uint64_scale (align->next_offset, GST_SECOND, |
445 | 0 | ABS (align->rate)) - timestamp; |
446 | 0 | } else { |
447 | 0 | guint64 old_offset = align->next_offset; |
448 | |
|
449 | 0 | if (align->next_offset > n_samples) |
450 | 0 | align->next_offset -= n_samples; |
451 | 0 | else |
452 | 0 | align->next_offset = 0; |
453 | 0 | start_offset = align->next_offset; |
454 | |
|
455 | 0 | timestamp = |
456 | 0 | gst_util_uint64_scale (align->next_offset, GST_SECOND, |
457 | 0 | ABS (align->rate)); |
458 | |
|
459 | 0 | duration = |
460 | 0 | gst_util_uint64_scale (old_offset, GST_SECOND, |
461 | 0 | ABS (align->rate)) - timestamp; |
462 | 0 | } |
463 | 0 | } |
464 | |
|
465 | 0 | align->samples_since_discont += n_samples; |
466 | |
|
467 | 0 | if (out_timestamp) |
468 | 0 | *out_timestamp = timestamp; |
469 | 0 | if (out_duration) |
470 | 0 | *out_duration = duration; |
471 | 0 | if (out_sample_position) |
472 | 0 | *out_sample_position = start_offset; |
473 | |
|
474 | 0 | return discont; |
475 | 0 | } |
476 | | |
477 | | #undef ABSDIFF |