Coverage Report

Created: 2026-05-16 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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