From d2fd740388bbb3e5d10c402304b037ec07dd8aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 12 Sep 2017 16:03:44 +0300 Subject: [PATCH] audio: Add reverse playback support to GstAudioStreamAlign https://bugzilla.gnome.org/show_bug.cgi?id=787560 --- gst-libs/gst/audio/gstaudiostreamalign.c | 79 +++++++--- tests/check/libs/audio.c | 179 +++++++++++++++++++++++ 2 files changed, 236 insertions(+), 22 deletions(-) diff --git a/gst-libs/gst/audio/gstaudiostreamalign.c b/gst-libs/gst/audio/gstaudiostreamalign.c index 846a03cd88..a2ac2fcefa 100644 --- a/gst-libs/gst/audio/gstaudiostreamalign.c +++ b/gst-libs/gst/audio/gstaudiostreamalign.c @@ -64,6 +64,7 @@ struct _GstAudioStreamAlign * 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, @@ -82,7 +83,7 @@ gst_audio_stream_align_new (gint rate, GstClockTime alignment_threshold, { GstAudioStreamAlign *align; - g_return_val_if_fail (rate > 0, NULL); + g_return_val_if_fail (rate != 0, NULL); align = g_new0 (GstAudioStreamAlign, 1); align->rate = rate; @@ -147,7 +148,7 @@ void gst_audio_stream_align_set_rate (GstAudioStreamAlign * align, gint rate) { g_return_if_fail (align != NULL); - g_return_if_fail (rate > 0); + g_return_if_fail (rate != 0); if (align->rate == rate) return; @@ -283,10 +284,18 @@ gst_audio_stream_align_mark_discont (GstAudioStreamAlign * align) * 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, @@ -299,10 +308,12 @@ gst_audio_stream_align_process (GstAudioStreamAlign * align, g_return_val_if_fail (align != NULL, FALSE); start_time = timestamp; - start_offset = gst_util_uint64_scale (start_time, align->rate, GST_SECOND); + 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, align->rate); + end_time = + gst_util_uint64_scale_int (end_offset, GST_SECOND, ABS (align->rate)); duration = end_time - start_time; @@ -312,24 +323,27 @@ gst_audio_stream_align_process (GstAudioStreamAlign * align, 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; + 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, - align->rate, GST_SECOND); + 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 = 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)) { + 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; } @@ -348,22 +362,41 @@ gst_audio_stream_align_process (GstAudioStreamAlign * align, GST_INFO ("Have discont. Expected %" G_GUINT64_FORMAT ", got %" G_GUINT64_FORMAT, align->next_offset, start_offset); - align->next_offset = end_offset; + align->next_offset = align->rate > 0 ? end_offset : start_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); + 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; + start_offset = align->next_offset; + align->next_offset += n_samples; - duration = - gst_util_uint64_scale (align->next_offset, GST_SECOND, - align->rate) - timestamp; + 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; + } } if (out_timestamp) @@ -375,3 +408,5 @@ gst_audio_stream_align_process (GstAudioStreamAlign * align, return discont; } + +#undef ABSDIFF diff --git a/tests/check/libs/audio.c b/tests/check/libs/audio.c index 996b8886e4..9099533c12 100644 --- a/tests/check/libs/audio.c +++ b/tests/check/libs/audio.c @@ -883,6 +883,184 @@ GST_START_TEST (test_stream_align) GST_END_TEST; +GST_START_TEST (test_stream_align_reverse) +{ + GstAudioStreamAlign *align; + gint i; + GstClockTime timestamp; + GstClockTime out_timestamp, out_duration; + gboolean discont; + + align = gst_audio_stream_align_new (-1000); + + for (i = 499; i >= 0; i--) { + timestamp = 10 * GST_MSECOND * i; + discont = i == 499; + + 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 == 499) + 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 = 499; i >= 0; i--) { + timestamp = 10 * GST_MSECOND * i; + discont = i == 499; + if (i < 499) + timestamp += 1 * GST_MSECOND * MIN (499 - i, 40); + + discont = + gst_audio_stream_align_process (align, discont, timestamp, 10, + &out_timestamp, &out_duration, NULL); + + if (i >= 500 - 140) { + fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i); + fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND); + if (i == 499) + fail_unless (discont); + else + fail_unless (!discont); + } else { + if (i == 499 - 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 = 499; i >= 4; i--) { + timestamp = 10 * GST_MSECOND * i; + discont = i == 499; + if (i < 499) + timestamp -= 1 * GST_MSECOND * MIN (499 - i, 40); + + discont = + gst_audio_stream_align_process (align, discont, timestamp, 10, + &out_timestamp, &out_duration, NULL); + + if (i >= 500 - 140) { + fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i); + fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND); + if (i == 499) + fail_unless (discont); + else + fail_unless (!discont); + } else { + if (i == 499 - 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 = 499; i >= 0; i--) { + timestamp = 10 * GST_MSECOND * i; + discont = i == 499; + if (i < 499) + timestamp += 40 * GST_MSECOND; + + discont = + gst_audio_stream_align_process (align, discont, timestamp, 10, + &out_timestamp, &out_duration, NULL); + + if (i >= 500 - 101) { + fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i); + fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND); + if (i == 499) + fail_unless (discont); + else + fail_unless (!discont); + } else { + if (i == 499 - 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 = 499; i >= 0; i--) { + timestamp = 10 * GST_MSECOND * i; + discont = i == 499; + + if (i % 2 == 0 && i < 499) + 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 == 499) + fail_unless (discont); + else + fail_unless (!discont); + } + + /* Shift buffer 100 by 2: discont at buffer 200 + */ + for (i = 499; i >= 0; i--) { + timestamp = 10 * GST_MSECOND * i; + discont = i == 499; + if (i < 500 - 100) + timestamp += 2 * GST_SECOND; + + discont = + gst_audio_stream_align_process (align, discont, timestamp, 10, + &out_timestamp, &out_duration, NULL); + + if (i >= 500 - 200) { + fail_unless_equals_uint64 (out_timestamp, 10 * GST_MSECOND * i); + fail_unless_equals_uint64 (out_duration, 10 * GST_MSECOND); + if (i == 499) + 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 == 499 - 200) + fail_unless (discont); + else + fail_unless (!discont); + } + } +} + +GST_END_TEST; + static Suite * audio_suite (void) { @@ -911,6 +1089,7 @@ audio_suite (void) 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); + tcase_add_test (tc_chain, test_stream_align_reverse); return s; }