| /* 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; |
| GstClockTime timestamp_at_discont; |
| guint64 samples_since_discont; |
| |
| /* 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. |
| * A negative rate can be used for reverse playback. |
| * |
| * @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; |
| |
| align->timestamp_at_discont = GST_CLOCK_TIME_NONE; |
| align->samples_since_discont = 0; |
| 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_get_timestamp_at_discont: |
| * @align: a #GstAudioStreamAlign |
| * |
| * Timestamp that was passed when a discontinuity was detected, i.e. the first |
| * timestamp after the discontinuity. |
| * |
| * Returns: The last timestamp at when a discontinuity was detected |
| * |
| * Since: 1.14 |
| */ |
| GstClockTime |
| gst_audio_stream_align_get_timestamp_at_discont (GstAudioStreamAlign * align) |
| { |
| g_return_val_if_fail (align != NULL, GST_CLOCK_TIME_NONE); |
| |
| return align->timestamp_at_discont; |
| } |
| |
| /** |
| * gst_audio_stream_align_get_samples_since_discont: |
| * @align: a #GstAudioStreamAlign |
| * |
| * Returns the number of samples that were processed since the last |
| * discontinuity was detected. |
| * |
| * Returns: The number of samples processed since the last discontinuity. |
| * |
| * Since: 1.14 |
| */ |
| guint64 |
| gst_audio_stream_align_get_samples_since_discont (GstAudioStreamAlign * align) |
| { |
| g_return_val_if_fail (align != NULL, 0); |
| |
| return align->samples_since_discont; |
| } |
| |
| /** |
| * 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. |
| * |
| * Note: In reverse playback, every buffer is considered discontinuous in the |
| * context of buffer flags because the last sample of the previous buffer is |
| * discontinuous with the first sample of the current one. However for this |
| * function they are only considered discontinuous in reverse playback if the |
| * first sample of the previous buffer is discontinuous with the last sample |
| * of the current one. |
| * |
| * Returns: %TRUE if a discontinuity was detected, %FALSE otherwise. |
| * |
| * Since: 1.14 |
| */ |
| #define ABSDIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a)) |
| 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, ABS (align->rate), GST_SECOND); |
| |
| end_offset = start_offset + n_samples; |
| end_time = |
| gst_util_uint64_scale_int (end_offset, GST_SECOND, ABS (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 (align->rate > 0) { |
| diff = ABSDIFF (start_offset, align->next_offset); |
| } else { |
| diff = ABSDIFF (end_offset, align->next_offset); |
| } |
| |
| max_sample_diff = |
| gst_util_uint64_scale_int (align->alignment_threshold, |
| ABS (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 = align->rate > 0 ? start_time : end_time; |
| } else if ((align->rate > 0 |
| && ABSDIFF (start_time, |
| align->discont_time) >= align->discont_wait) |
| || (align->rate < 0 |
| && ABSDIFF (end_time, |
| align->discont_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 = align->rate > 0 ? end_offset : start_offset; |
| align->timestamp_at_discont = start_time; |
| align->samples_since_discont = 0; |
| |
| /* Got a discont and adjusted, reset the discont_time marker */ |
| align->discont_time = GST_CLOCK_TIME_NONE; |
| } else { |
| |
| /* No discont, just keep counting */ |
| if (align->rate > 0) { |
| timestamp = |
| gst_util_uint64_scale (align->next_offset, GST_SECOND, |
| ABS (align->rate)); |
| |
| start_offset = align->next_offset; |
| align->next_offset += n_samples; |
| |
| duration = |
| gst_util_uint64_scale (align->next_offset, GST_SECOND, |
| ABS (align->rate)) - timestamp; |
| } else { |
| guint64 old_offset = align->next_offset; |
| |
| if (align->next_offset > n_samples) |
| align->next_offset -= n_samples; |
| else |
| align->next_offset = 0; |
| start_offset = align->next_offset; |
| |
| timestamp = |
| gst_util_uint64_scale (align->next_offset, GST_SECOND, |
| ABS (align->rate)); |
| |
| duration = |
| gst_util_uint64_scale (old_offset, GST_SECOND, |
| ABS (align->rate)) - timestamp; |
| } |
| } |
| |
| align->samples_since_discont += n_samples; |
| |
| if (out_timestamp) |
| *out_timestamp = timestamp; |
| if (out_duration) |
| *out_duration = duration; |
| if (out_sample_position) |
| *out_sample_position = start_offset; |
| |
| return discont; |
| } |
| |
| #undef ABSDIFF |