summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Dröge <sebastian@centricular.com>2018-08-17 16:37:45 +0300
committerSebastian Dröge <sebastian@centricular.com>2018-08-17 16:40:16 +0300
commitf19edc8c835724d711094c7c230f542de1e4caf2 (patch)
treed0b22bf706eff1a7bf6573b1d00f011676710f74
parent2f761b89df4a705755d934b74053a7d71ad4f2ea (diff)
audiobuffersplit: Add a gapless mode which inserts silence/drops samples on disconts
The output is always a continguous stream without any gaps.
-rw-r--r--gst/audiobuffersplit/gstaudiobuffersplit.c187
-rw-r--r--gst/audiobuffersplit/gstaudiobuffersplit.h2
2 files changed, 178 insertions, 11 deletions
diff --git a/gst/audiobuffersplit/gstaudiobuffersplit.c b/gst/audiobuffersplit/gstaudiobuffersplit.c
index daf445b69..418400c12 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 ae24b8fff..5d87870da 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 {