--- /dev/null
+/* GStreamer
+ * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * gstaudiostreamalign.h:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstaudiostreamalign.h"
+
+/**
+ * SECTION:gstaudiostreamalign
+ * @title: GstAudioStreamAlign
+ * @short_description: Helper object for tracking audio stream alignment and discontinuities
+ *
+ * #GstAudioStreamAlign provides a helper object that helps tracking audio
+ * stream alignment and discontinuities, and detects discontinuities if
+ * possible.
+ *
+ * See gst_audio_stream_align_new() for a description of its parameters and
+ * gst_audio_stream_align_process() for the details of the processing.
+ */
+
+G_DEFINE_BOXED_TYPE (GstAudioStreamAlign, gst_audio_stream_align,
+ (GBoxedCopyFunc) gst_audio_stream_align_copy,
+ (GBoxedFreeFunc) gst_audio_stream_align_free);
+
+struct _GstAudioStreamAlign
+{
+ gint rate;
+ GstClockTime alignment_threshold;
+ GstClockTime discont_wait;
+
+ /* counter to keep track of timestamps */
+ guint64 next_offset;
+
+ /* Last time we noticed a discont */
+ GstClockTime discont_time;
+};
+
+/**
+ * gst_audio_stream_align_new:
+ * @rate: a sample rate
+ * @alignment_threshold: a alignment threshold in nanoseconds
+ * @discont_wait: discont wait in nanoseconds
+ *
+ * Allocate a new #GstAudioStreamAlign with the given configuration. All
+ * processing happens according to sample rate @rate, until
+ * gst_audio_discont_wait_set_rate() is called with a new @rate.
+ *
+ * @alignment_threshold gives the tolerance in nanoseconds after which a
+ * timestamp difference is considered a discontinuity. Once detected,
+ * @discont_wait nanoseconds have to pass without going below the threshold
+ * again until the output buffer is marked as a discontinuity. These can later
+ * be re-configured with gst_audio_stream_align_set_alignment_threshold() and
+ * gst_audio_stream_align_set_discont_wait().
+ *
+ * Returns: a new #GstAudioStreamAlign. free with gst_audio_stream_align_free().
+ *
+ * Since: 1.14
+ */
+GstAudioStreamAlign *
+gst_audio_stream_align_new (gint rate, GstClockTime alignment_threshold,
+ GstClockTime discont_wait)
+{
+ GstAudioStreamAlign *align;
+
+ g_return_val_if_fail (rate > 0, NULL);
+
+ align = g_new0 (GstAudioStreamAlign, 1);
+ align->rate = rate;
+ align->alignment_threshold = alignment_threshold;
+ align->discont_wait = discont_wait;
+
+ gst_audio_stream_align_mark_discont (align);
+
+ return align;
+}
+
+/**
+ * gst_audio_stream_align_copy:
+ * @align: a #GstAudioStreamAlign
+ *
+ * Copy a GstAudioStreamAlign structure.
+ *
+ * Returns: a new #GstAudioStreamAlign. free with gst_audio_stream_align_free.
+ *
+ * Since: 1.14
+ */
+GstAudioStreamAlign *
+gst_audio_stream_align_copy (const GstAudioStreamAlign * align)
+{
+ GstAudioStreamAlign *copy;
+
+ g_return_val_if_fail (align != NULL, NULL);
+
+ copy = g_new0 (GstAudioStreamAlign, 1);
+ *copy = *align;
+
+ return copy;
+}
+
+/**
+ * gst_audio_stream_align_free:
+ * @align: a #GstAudioStreamAlign
+ *
+ * Free a GstAudioStreamAlign structure previously allocated with gst_audio_stream_align_new()
+ * or gst_audio_stream_align_copy().
+ *
+ * Since: 1.14
+ */
+void
+gst_audio_stream_align_free (GstAudioStreamAlign * align)
+{
+ g_return_if_fail (align != NULL);
+ g_free (align);
+}
+
+/**
+ * gst_audio_discont_set_rate:
+ * @align: a #GstAudioStreamAlign
+ * @rate: a new sample rate
+ *
+ * Sets @rate as new sample rate for the following processing. If the sample
+ * rate differs this implicitely marks the next data as discontinuous.
+ *
+ * Since: 1.14
+ */
+void
+gst_audio_stream_align_set_rate (GstAudioStreamAlign * align, gint rate)
+{
+ g_return_if_fail (align != NULL);
+ g_return_if_fail (rate > 0);
+
+ if (align->rate == rate)
+ return;
+
+ align->rate = rate;
+ gst_audio_stream_align_mark_discont (align);
+}
+
+/**
+ * gst_audio_discont_get_rate:
+ * @align: a #GstAudioStreamAlign
+ *
+ * Gets the currently configured sample rate.
+ *
+ * Returns: The currently configured sample rate
+ *
+ * Since: 1.14
+ */
+gint
+gst_audio_stream_align_get_rate (GstAudioStreamAlign * align)
+{
+ g_return_val_if_fail (align != NULL, 0);
+
+ return align->rate;
+}
+
+/**
+ * gst_audio_discont_set_alignment_threshold:
+ * @align: a #GstAudioStreamAlign
+ * @alignment_treshold: a new alignment threshold
+ *
+ * Sets @alignment_treshold as new alignment threshold for the following processing.
+ *
+ * Since: 1.14
+ */
+void
+gst_audio_stream_align_set_alignment_threshold (GstAudioStreamAlign *
+ align, GstClockTime alignment_threshold)
+{
+ g_return_if_fail (align != NULL);
+
+ align->alignment_threshold = alignment_threshold;
+}
+
+/**
+ * gst_audio_discont_get_alignment_threshold:
+ * @align: a #GstAudioStreamAlign
+ *
+ * Gets the currently configured alignment threshold.
+ *
+ * Returns: The currently configured alignment threshold
+ *
+ * Since: 1.14
+ */
+GstClockTime
+gst_audio_stream_align_get_alignment_threshold (GstAudioStreamAlign * align)
+{
+ g_return_val_if_fail (align != NULL, 0);
+
+ return align->alignment_threshold;
+}
+
+/**
+ * gst_audio_discont_set_discont_wait:
+ * @align: a #GstAudioStreamAlign
+ * @alignment_treshold: a new discont wait
+ *
+ * Sets @alignment_treshold as new discont wait for the following processing.
+ *
+ * Since: 1.14
+ */
+void
+gst_audio_stream_align_set_discont_wait (GstAudioStreamAlign * align,
+ GstClockTime discont_wait)
+{
+ g_return_if_fail (align != NULL);
+
+ align->discont_wait = discont_wait;
+}
+
+/**
+ * gst_audio_discont_get_discont_wait:
+ * @align: a #GstAudioStreamAlign
+ *
+ * Gets the currently configured discont wait.
+ *
+ * Returns: The currently configured discont wait
+ *
+ * Since: 1.14
+ */
+GstClockTime
+gst_audio_stream_align_get_discont_wait (GstAudioStreamAlign * align)
+{
+ g_return_val_if_fail (align != NULL, 0);
+
+ return align->discont_wait;
+}
+
+/**
+ * gst_audio_stream_align_mark_discont:
+ * @align: a #GstAudioStreamAlign
+ *
+ * Marks the next buffer as discontinuous and resets timestamp tracking.
+ *
+ * Since: 1.14
+ */
+void
+gst_audio_stream_align_mark_discont (GstAudioStreamAlign * align)
+{
+ g_return_if_fail (align != NULL);
+
+ align->next_offset = -1;
+ align->discont_time = GST_CLOCK_TIME_NONE;
+}
+
+/**
+ * gst_audio_stream_align_process:
+ * @align: a #GstAudioStreamAlign
+ * @discont: if this data is considered to be discontinuous
+ * @timestamp: a #GstClockTime of the start of the data
+ * @n_samples: number of samples to process
+ * @out_timestamp: (out): output timestamp of the data
+ * @out_duration: (out): output duration of the data
+ * @out_sample_position: (out): output sample position of the start of the data
+ *
+ * Processes data with @timestamp and @n_samples, and returns the output
+ * timestamp, duration and sample position together with a boolean to signal
+ * whether a discontinuity was detected or not. All non-discontinuous data
+ * will have perfect timestamps and durations.
+ *
+ * A discontinuity is detected once the difference between the actual
+ * timestamp and the timestamp calculated from the sample count since the last
+ * discontinuity differs by more than the alignment threshold for a duration
+ * longer than discont wait.
+ *
+ * Returns: %TRUE if a discontinuity was detected, %FALSE otherwise.
+ *
+ * Since: 1.14
+ */
+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)
+{
+ GstClockTime start_time, end_time, duration;
+ guint64 start_offset, end_offset;
+
+ g_return_val_if_fail (align != NULL, FALSE);
+
+ start_time = timestamp;
+ start_offset = gst_util_uint64_scale (start_time, align->rate, GST_SECOND);
+
+ end_offset = start_offset + n_samples;
+ end_time = gst_util_uint64_scale_int (end_offset, GST_SECOND, align->rate);
+
+ duration = end_time - start_time;
+
+ if (align->next_offset == (guint64) - 1 || discont) {
+ discont = TRUE;
+ } else {
+ guint64 diff, max_sample_diff;
+
+ /* Check discont */
+ if (start_offset <= align->next_offset)
+ diff = align->next_offset - start_offset;
+ else
+ diff = start_offset - align->next_offset;
+
+ max_sample_diff =
+ gst_util_uint64_scale_int (align->alignment_threshold,
+ align->rate, GST_SECOND);
+
+ /* Discont! */
+ if (G_UNLIKELY (diff >= max_sample_diff)) {
+ if (align->discont_wait > 0) {
+ if (align->discont_time == GST_CLOCK_TIME_NONE) {
+ align->discont_time = start_time;
+ } else if ((start_time >= align->discont_time
+ && start_time - align->discont_time >= align->discont_wait)
+ || (start_time < align->discont_time
+ && align->discont_time - start_time >= align->discont_wait)) {
+ discont = TRUE;
+ align->discont_time = GST_CLOCK_TIME_NONE;
+ }
+ } else {
+ discont = TRUE;
+ }
+ } else if (G_UNLIKELY (align->discont_time != GST_CLOCK_TIME_NONE)) {
+ /* we have had a discont, but are now back on track! */
+ align->discont_time = GST_CLOCK_TIME_NONE;
+ }
+ }
+
+ if (discont) {
+ /* Have discont, need resync and use the capture timestamps */
+ if (align->next_offset != (guint64) - 1)
+ GST_INFO ("Have discont. Expected %"
+ G_GUINT64_FORMAT ", got %" G_GUINT64_FORMAT,
+ align->next_offset, start_offset);
+ align->next_offset = end_offset;
+
+ /* Got a discont and adjusted, reset the discont_time marker */
+ align->discont_time = GST_CLOCK_TIME_NONE;
+ } else {
+
+ /* No discont, just keep counting */
+ timestamp =
+ gst_util_uint64_scale (align->next_offset, GST_SECOND, align->rate);
+
+ start_offset = align->next_offset;
+ align->next_offset += n_samples;
+
+ duration =
+ gst_util_uint64_scale (align->next_offset, GST_SECOND,
+ align->rate) - timestamp;
+ }
+
+ if (out_timestamp)
+ *out_timestamp = timestamp;
+ if (out_duration)
+ *out_duration = duration;
+ if (out_sample_position)
+ *out_sample_position = start_offset;
+
+ return discont;
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * gstaudiostreamalign.h:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_AUDIO_STREAM_ALIGN_H__
+#define __GST_AUDIO_STREAM_ALIGN_H__
+
+#include <gst/gst.h>
+
+#define GST_TYPE_AUDIO_INFO_STREAM_ALIGN (gst_audio_stream_align_get_type ())
+
+typedef struct _GstAudioStreamAlign GstAudioStreamAlign;
+
+GST_EXPORT
+GType gst_audio_stream_align_get_type (void);
+
+GST_EXPORT
+GstAudioStreamAlign * gst_audio_stream_align_new (gint rate,
+ GstClockTime alignment_threshold,
+ GstClockTime discont_wait);
+GST_EXPORT
+GstAudioStreamAlign * gst_audio_stream_align_copy (const GstAudioStreamAlign * align);
+GST_EXPORT
+void gst_audio_stream_align_free (GstAudioStreamAlign * align);
+
+GST_EXPORT
+void gst_audio_stream_align_set_rate (GstAudioStreamAlign * align,
+ gint rate);
+GST_EXPORT
+gint gst_audio_stream_align_get_rate (GstAudioStreamAlign * align);
+
+GST_EXPORT
+void gst_audio_stream_align_set_alignment_threshold (GstAudioStreamAlign * align,
+ GstClockTime alignment_threshold);
+GST_EXPORT
+GstClockTime gst_audio_stream_align_get_alignment_threshold (GstAudioStreamAlign * align);
+
+GST_EXPORT
+void gst_audio_stream_align_set_discont_wait (GstAudioStreamAlign * align,
+ GstClockTime discont_wait);
+GST_EXPORT
+GstClockTime gst_audio_stream_align_get_discont_wait (GstAudioStreamAlign * align);
+
+
+GST_EXPORT
+void gst_audio_stream_align_mark_discont (GstAudioStreamAlign * align);
+
+GST_EXPORT
+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);
+
+#endif /* __GST_AUDIO_STREAM_ALIGN_H__ */
GST_END_TEST;
+GST_START_TEST (test_stream_align)
+{
+ GstAudioStreamAlign *align;
+ guint i;
+ GstClockTime timestamp;
+ GstClockTime out_timestamp, out_duration;
+ gboolean discont;
+
+ align = gst_audio_stream_align_new (1000);
+
+ for (i = 0; i < 500; i++) {
+ timestamp = 10 * GST_MSECOND * i;
+ discont = i == 0;
+
+ discont =
+ gst_audio_stream_align_process (align, discont, timestamp, 10,
+ &out_timestamp, &out_duration, NULL);
+
+ fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ if (i == 0)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ }
+
+ /* Drift forwards by 1ms per 10ms buffer for the first 40 buffers.
+ * - after 40 buffers we're above alignment threshold
+ * - after 40 + 100 buffers we're at discont wait
+ */
+ for (i = 0; i < 500; i++) {
+ timestamp = 10 * GST_MSECOND * i;
+ discont = i == 0;
+ if (i > 0)
+ timestamp += 1 * GST_MSECOND * MIN (i, 40);
+
+ discont =
+ gst_audio_stream_align_process (align, discont, timestamp, 10,
+ &out_timestamp, &out_duration, NULL);
+
+ if (i < 140) {
+ fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ if (i == 0)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ } else {
+ if (i == 140)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ fail_unless_equals_uint64 (out_timestamp,
+ 10 * GST_MSECOND * i + 40 * GST_MSECOND);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ }
+ }
+
+ /* Drift backwards by 1ms per 10ms buffer for the first 40 buffers.
+ * - after 40 buffers we're above alignment threshold
+ * - after 40 + 100 buffers we're at discont wait
+ */
+ for (i = 0; i < 500; i++) {
+ timestamp = 10 * GST_MSECOND * i;
+ discont = i == 0;
+ if (i > 0)
+ timestamp -= 1 * GST_MSECOND * MIN (i, 40);
+
+ discont =
+ gst_audio_stream_align_process (align, discont, timestamp, 10,
+ &out_timestamp, &out_duration, NULL);
+
+ if (i < 140) {
+ fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ if (i == 0)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ } else {
+ if (i == 140)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+
+ fail_unless_equals_uint64 (out_timestamp,
+ 10 * GST_MSECOND * i - 40 * GST_MSECOND);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ }
+ }
+
+ /* Shift all buffers but the first by 40ms
+ * - after 1 buffers we're above alignment threshold
+ * - after 101 buffers we're at discont wait
+ */
+ for (i = 0; i < 500; i++) {
+ timestamp = 10 * GST_MSECOND * i;
+ discont = i == 0;
+ if (i > 0)
+ timestamp += 40 * GST_MSECOND;
+
+ discont =
+ gst_audio_stream_align_process (align, discont, timestamp, 10,
+ &out_timestamp, &out_duration, NULL);
+
+ if (i < 101) {
+ fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ if (i == 0)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ } else {
+ if (i == 101)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ fail_unless_equals_uint64 (out_timestamp,
+ 10 * GST_MSECOND * i + 40 * GST_MSECOND);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ }
+ }
+
+ /* Shift every second buffer by 40ms:
+ * - never discont!
+ */
+ for (i = 0; i < 500; i++) {
+ timestamp = 10 * GST_MSECOND * i;
+ discont = i == 0;
+
+ if (i % 2 == 0 && i > 0)
+ timestamp += 40 * GST_MSECOND;
+
+ discont =
+ gst_audio_stream_align_process (align, discont, timestamp, 10,
+ &out_timestamp, &out_duration, NULL);
+
+ fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ if (i == 0)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ }
+
+ /* Shift every buffer 100 by 2: discont at buffer 200
+ */
+ for (i = 0; i < 500; i++) {
+ timestamp = 10 * GST_MSECOND * i;
+ discont = i == 0;
+ if (i >= 100)
+ timestamp += 2 * GST_SECOND;
+
+ discont =
+ gst_audio_stream_align_process (align, discont, timestamp, 10,
+ &out_timestamp, &out_duration, NULL);
+
+ if (i < 200) {
+ fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ if (i == 0)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ } else {
+ fail_unless_equals_uint64 (out_timestamp,
+ 10 * GST_MSECOND * i + 2 * GST_SECOND);
+ fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND);
+ if (i == 200)
+ fail_unless (discont);
+ else
+ fail_unless (!discont);
+ }
+ }
+}
+
+GST_END_TEST;
+
static Suite *
audio_suite (void)
{
tcase_add_test (tc_chain, test_audio_format_s8);
tcase_add_test (tc_chain, test_audio_format_u8);
tcase_add_test (tc_chain, test_fill_silence);
+ tcase_add_test (tc_chain, test_stream_align);
return s;
}