diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index de28e4e587..1e3190e3b7 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -244,6 +244,19 @@ GST_AUDIO_RESAMPLER_QUALITY_DEFAULT GST_AUDIO_RESAMPLER_QUALITY_MAX GST_AUDIO_RESAMPLER_QUALITY_MIN +GstAudioStreamAlign +gst_audio_stream_align_new +gst_audio_stream_align_copy +gst_audio_stream_align_free +gst_audio_stream_align_mark_discont +gst_audio_stream_align_process +gst_audio_stream_align_get_alignment_threshold +gst_audio_stream_align_set_alignment_threshold +gst_audio_stream_align_get_discont_wait +gst_audio_stream_align_set_discont_wait +gst_audio_stream_align_get_rate +gst_audio_stream_align_set_rate + GST_TYPE_BUFFER_FORMAT GST_TYPE_BUFFER_FORMAT_TYPE @@ -265,6 +278,7 @@ gst_audio_resampler_filter_interpolation_get_type gst_audio_resampler_filter_mode_get_type gst_audio_resampler_flags_get_type gst_audio_resampler_method_get_type +gst_audio_stream_align_get_type _GST_AUDIO_FORMAT_NE diff --git a/gst-libs/gst/audio/Makefile.am b/gst-libs/gst/audio/Makefile.am index e6fcde15c6..100867db4e 100644 --- a/gst-libs/gst/audio/Makefile.am +++ b/gst-libs/gst/audio/Makefile.am @@ -54,7 +54,8 @@ libgstaudio_@GST_API_VERSION@_la_SOURCES = \ gstaudiosrc.c \ gstaudioutilsprivate.c \ streamvolume.c \ - gstaudioiec61937.c + gstaudioiec61937.c \ + gstaudiostreamalign.c nodist_libgstaudio_@GST_API_VERSION@_la_SOURCES = $(BUILT_SOURCES) @@ -80,7 +81,8 @@ libgstaudio_@GST_API_VERSION@include_HEADERS = \ gstaudiosink.h \ gstaudiosrc.h \ streamvolume.h \ - gstaudioiec61937.h + gstaudioiec61937.h \ + gstaudiostreamalign.h nodist_libgstaudio_@GST_API_VERSION@include_HEADERS = \ audio-enumtypes.h diff --git a/gst-libs/gst/audio/audio.h b/gst-libs/gst/audio/audio.h index edb8136853..42d112db25 100644 --- a/gst-libs/gst/audio/audio.h +++ b/gst-libs/gst/audio/audio.h @@ -31,6 +31,7 @@ #include #include #include +#include G_BEGIN_DECLS diff --git a/gst-libs/gst/audio/gstaudiostreamalign.c b/gst-libs/gst/audio/gstaudiostreamalign.c new file mode 100644 index 0000000000..846a03cd88 --- /dev/null +++ b/gst-libs/gst/audio/gstaudiostreamalign.c @@ -0,0 +1,377 @@ +/* GStreamer + * Copyright (C) 2017 Sebastian Dröge + * + * 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; +} diff --git a/gst-libs/gst/audio/gstaudiostreamalign.h b/gst-libs/gst/audio/gstaudiostreamalign.h new file mode 100644 index 0000000000..541c06d0ca --- /dev/null +++ b/gst-libs/gst/audio/gstaudiostreamalign.h @@ -0,0 +1,74 @@ +/* GStreamer + * Copyright (C) 2017 Sebastian Dröge + * + * 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 + +#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__ */ diff --git a/gst-libs/gst/audio/meson.build b/gst-libs/gst/audio/meson.build index f22529436b..1f14a2778e 100644 --- a/gst-libs/gst/audio/meson.build +++ b/gst-libs/gst/audio/meson.build @@ -21,6 +21,7 @@ audio_src= [ 'gstaudiosrc.c', 'gstaudioutilsprivate.c', 'streamvolume.c', + 'gstaudiostreamalign.c', ] audio_mkenum_headers = [ @@ -36,6 +37,7 @@ audio_mkenum_headers = [ 'gstaudiobasesrc.h', 'gstaudiocdsrc.h', 'gstaudiobasesink.h', + 'gstaudiostreamalign.h', ] # FIXME: check headers diff --git a/tests/check/libs/audio.c b/tests/check/libs/audio.c index 3cbc9e9593..996b8886e4 100644 --- a/tests/check/libs/audio.c +++ b/tests/check/libs/audio.c @@ -705,6 +705,184 @@ GST_START_TEST (test_fill_silence) 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) { @@ -732,6 +910,7 @@ 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; } diff --git a/win32/common/libgstaudio.def b/win32/common/libgstaudio.def index 26e303856d..9a47e43dda 100644 --- a/win32/common/libgstaudio.def +++ b/win32/common/libgstaudio.def @@ -209,6 +209,18 @@ EXPORTS gst_audio_ring_buffer_stop gst_audio_sink_get_type gst_audio_src_get_type + gst_audio_stream_align_copy + gst_audio_stream_align_free + gst_audio_stream_align_get_alignment_threshold + gst_audio_stream_align_get_discont_wait + gst_audio_stream_align_get_rate + gst_audio_stream_align_get_type + gst_audio_stream_align_mark_discont + gst_audio_stream_align_new + gst_audio_stream_align_process + gst_audio_stream_align_set_alignment_threshold + gst_audio_stream_align_set_discont_wait + gst_audio_stream_align_set_rate gst_buffer_add_audio_clipping_meta gst_buffer_add_audio_downmix_meta gst_buffer_get_audio_downmix_meta_for_channels