From f19edc8c835724d711094c7c230f542de1e4caf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 17 Aug 2018 16:37:45 +0300 Subject: [PATCH] audiobuffersplit: Add a gapless mode which inserts silence/drops samples on disconts The output is always a continguous stream without any gaps. --- gst/audiobuffersplit/gstaudiobuffersplit.c | 187 +++++++++++++++++++-- gst/audiobuffersplit/gstaudiobuffersplit.h | 2 + 2 files changed, 178 insertions(+), 11 deletions(-) diff --git a/gst/audiobuffersplit/gstaudiobuffersplit.c b/gst/audiobuffersplit/gstaudiobuffersplit.c index daf445b69c..418400c12f 100644 --- a/gst/audiobuffersplit/gstaudiobuffersplit.c +++ b/gst/audiobuffersplit/gstaudiobuffersplit.c @@ -46,6 +46,7 @@ enum PROP_ALIGNMENT_THRESHOLD, PROP_DISCONT_WAIT, PROP_STRICT_BUFFER_SIZE, + PROP_GAPLESS, LAST_PROP }; @@ -54,6 +55,7 @@ enum #define DEFAULT_ALIGNMENT_THRESHOLD (40 * GST_MSECOND) #define DEFAULT_DISCONT_WAIT (1 * GST_SECOND) #define DEFAULT_STRICT_BUFFER_SIZE (FALSE) +#define DEFAULT_GAPLESS (FALSE) #define parent_class gst_audio_buffer_split_parent_class G_DEFINE_TYPE (GstAudioBufferSplit, gst_audio_buffer_split, GST_TYPE_ELEMENT); @@ -114,6 +116,13 @@ gst_audio_buffer_split_class_init (GstAudioBufferSplitClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)); + g_object_class_install_property (gobject_class, PROP_GAPLESS, + g_param_spec_boolean ("gapless", "Gapless", + "Insert silence/drop samples instead of creating a discontinuity", + DEFAULT_GAPLESS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); + gst_element_class_set_static_metadata (gstelement_class, "Audio Buffer Split", "Audio/Filter", "Splits raw audio buffers into equal sized chunks", @@ -148,6 +157,7 @@ gst_audio_buffer_split_init (GstAudioBufferSplit * self) self->output_buffer_duration_n = DEFAULT_OUTPUT_BUFFER_DURATION_N; self->output_buffer_duration_d = DEFAULT_OUTPUT_BUFFER_DURATION_D; self->strict_buffer_size = DEFAULT_STRICT_BUFFER_SIZE; + self->gapless = DEFAULT_GAPLESS; self->adapter = gst_adapter_new (); @@ -240,6 +250,9 @@ gst_audio_buffer_split_set_property (GObject * object, guint property_id, case PROP_STRICT_BUFFER_SIZE: self->strict_buffer_size = g_value_get_boolean (value); break; + case PROP_GAPLESS: + self->gapless = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -272,6 +285,9 @@ gst_audio_buffer_split_get_property (GObject * object, guint property_id, case PROP_STRICT_BUFFER_SIZE: g_value_set_boolean (value, self->strict_buffer_size); break; + case PROP_GAPLESS: + g_value_set_boolean (value, self->gapless); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -399,7 +415,8 @@ gst_audio_buffer_split_output (GstAudioBufferSplit * self, gboolean force, static GstFlowReturn gst_audio_buffer_split_handle_discont (GstAudioBufferSplit * self, - GstBuffer * buffer, gint rate, gint bpf, guint samples_per_buffer) + GstBuffer * buffer, GstAudioFormat format, gint rate, gint bpf, + guint samples_per_buffer) { gboolean discont; GstFlowReturn ret = GST_FLOW_OK; @@ -414,18 +431,125 @@ gst_audio_buffer_split_handle_discont (GstAudioBufferSplit * self, GST_OBJECT_UNLOCK (self); if (discont) { - if (self->strict_buffer_size) { - gst_adapter_clear (self->adapter); - ret = GST_FLOW_OK; + guint avail = gst_adapter_available (self->adapter); + guint avail_samples = avail / bpf; + guint64 new_offset; + GstClockTime current_timestamp; + GstClockTime current_timestamp_end; + + /* Reset */ + self->drop_samples = 0; + + if (self->segment.rate < 0.0) { + current_timestamp = + self->resync_time - gst_util_uint64_scale (self->current_offset + + avail_samples, GST_SECOND, rate); + current_timestamp_end = + self->resync_time - gst_util_uint64_scale (self->current_offset, + GST_SECOND, rate); } else { - ret = - gst_audio_buffer_split_output (self, TRUE, rate, bpf, - samples_per_buffer); + current_timestamp = + self->resync_time + gst_util_uint64_scale (self->current_offset, + GST_SECOND, rate); + current_timestamp_end = + self->resync_time + gst_util_uint64_scale (self->current_offset + + avail_samples, GST_SECOND, rate); } - self->current_offset = 0; - self->accumulated_error = 0; - self->resync_time = GST_BUFFER_PTS (buffer); + if (self->gapless) { + if (self->current_offset == -1) { + /* We only set resync time on the very first buffer */ + self->current_offset = 0; + self->resync_time = GST_BUFFER_PTS (buffer); + } else { + GST_DEBUG_OBJECT (self, + "Got discont in gapless mode: Current timestamp %" GST_TIME_FORMAT + ", current end timestamp %" GST_TIME_FORMAT + ", timestamp after discont %" GST_TIME_FORMAT, + GST_TIME_ARGS (current_timestamp), + GST_TIME_ARGS (current_timestamp_end), + GST_TIME_ARGS (GST_BUFFER_PTS (buffer))); + + new_offset = + gst_util_uint64_scale (GST_BUFFER_PTS (buffer) - self->resync_time, + rate, GST_SECOND); + if (GST_BUFFER_PTS (buffer) < self->resync_time) { + guint64 drop_samples; + + new_offset = + gst_util_uint64_scale (self->resync_time - + GST_BUFFER_PTS (buffer), rate, GST_SECOND); + drop_samples = self->current_offset + avail_samples + new_offset; + + GST_DEBUG_OBJECT (self, + "Dropping %" G_GUINT64_FORMAT " samples (%" GST_TIME_FORMAT ")", + drop_samples, GST_TIME_ARGS (gst_util_uint64_scale (drop_samples, + GST_SECOND, rate))); + } else if (new_offset > self->current_offset + avail_samples) { + guint64 silence_samples = + new_offset - (self->current_offset + avail_samples); + const GstAudioFormatInfo *info = gst_audio_format_get_info (format); + + GST_DEBUG_OBJECT (self, + "Inserting %" G_GUINT64_FORMAT " samples of silence (%" + GST_TIME_FORMAT ")", silence_samples, + GST_TIME_ARGS (gst_util_uint64_scale (silence_samples, GST_SECOND, + rate))); + + /* Insert silence buffers to fill the gap in 1s chunks */ + while (silence_samples > 0) { + guint n_samples = MIN (silence_samples, rate); + GstBuffer *silence; + GstMapInfo map; + + silence = gst_buffer_new_and_alloc (n_samples * bpf); + GST_BUFFER_FLAG_SET (silence, GST_BUFFER_FLAG_GAP); + gst_buffer_map (silence, &map, GST_MAP_WRITE); + gst_audio_format_fill_silence (info, map.data, map.size); + gst_buffer_unmap (silence, &map); + + gst_adapter_push (self->adapter, silence); + ret = + gst_audio_buffer_split_output (self, FALSE, rate, bpf, + samples_per_buffer); + if (ret != GST_FLOW_OK) + return ret; + + silence_samples -= n_samples; + } + } else if (new_offset < self->current_offset + avail_samples) { + guint64 drop_samples = + self->current_offset + avail_samples - new_offset; + + GST_DEBUG_OBJECT (self, + "Dropping %" G_GUINT64_FORMAT " samples (%" GST_TIME_FORMAT ")", + drop_samples, GST_TIME_ARGS (gst_util_uint64_scale (drop_samples, + GST_SECOND, rate))); + self->drop_samples = drop_samples; + } + } + } else { + GST_DEBUG_OBJECT (self, + "Got discont: Current timestamp %" GST_TIME_FORMAT + ", current end timestamp %" GST_TIME_FORMAT + ", timestamp after discont %" GST_TIME_FORMAT, + GST_TIME_ARGS (current_timestamp), + GST_TIME_ARGS (current_timestamp_end), + GST_TIME_ARGS (GST_BUFFER_PTS (buffer))); + + if (self->strict_buffer_size) { + gst_adapter_clear (self->adapter); + ret = GST_FLOW_OK; + } else { + ret = + gst_audio_buffer_split_output (self, TRUE, rate, bpf, + samples_per_buffer); + } + + self->current_offset = 0; + self->accumulated_error = 0; + self->resync_time = GST_BUFFER_PTS (buffer); + } } return ret; @@ -438,6 +562,41 @@ gst_audio_buffer_split_clip_buffer (GstAudioBufferSplit * self, return gst_audio_buffer_clip (buffer, segment, rate, bpf); } +static GstBuffer * +gst_audio_buffer_split_clip_buffer_start_for_gapless (GstAudioBufferSplit * + self, GstBuffer * buffer, gint rate, gint bpf) +{ + guint nsamples; + + if (!self->gapless || self->drop_samples == 0) + return buffer; + + nsamples = gst_buffer_get_size (buffer) / bpf; + + GST_DEBUG_OBJECT (self, "Have to drop %lu samples, got %u samples", + self->drop_samples, nsamples); + + if (nsamples <= self->drop_samples) { + gst_buffer_unref (buffer); + self->drop_samples -= nsamples; + return NULL; + } + + if (self->segment.rate < 0.0) { + buffer = + gst_audio_buffer_truncate (buffer, bpf, 0, + nsamples - self->drop_samples); + self->drop_samples = 0; + return buffer; + } else { + buffer = gst_audio_buffer_truncate (buffer, bpf, self->drop_samples, -1); + self->drop_samples = 0; + return buffer; + } + + return buffer; +} + static GstFlowReturn gst_audio_buffer_split_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) @@ -468,13 +627,19 @@ gst_audio_buffer_split_sink_chain (GstPad * pad, GstObject * parent, return GST_FLOW_OK; ret = - gst_audio_buffer_split_handle_discont (self, buffer, rate, bpf, + gst_audio_buffer_split_handle_discont (self, buffer, format, rate, bpf, samples_per_buffer); if (ret != GST_FLOW_OK) { gst_buffer_unref (buffer); return ret; } + buffer = + gst_audio_buffer_split_clip_buffer_start_for_gapless (self, buffer, rate, + bpf); + if (!buffer) + return GST_FLOW_OK; + gst_adapter_push (self->adapter, buffer); return gst_audio_buffer_split_output (self, FALSE, rate, bpf, diff --git a/gst/audiobuffersplit/gstaudiobuffersplit.h b/gst/audiobuffersplit/gstaudiobuffersplit.h index ae24b8fff7..5d87870dac 100644 --- a/gst/audiobuffersplit/gstaudiobuffersplit.h +++ b/gst/audiobuffersplit/gstaudiobuffersplit.h @@ -55,12 +55,14 @@ struct _GstAudioBufferSplit { GstAudioStreamAlign *stream_align; GstClockTime resync_time; guint64 current_offset; /* offset from start time in samples */ + guint64 drop_samples; /* number of samples to drop in gapless mode */ guint samples_per_buffer; guint error_per_buffer; guint accumulated_error; gboolean strict_buffer_size; + gboolean gapless; }; struct _GstAudioBufferSplitClass {