mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 19:51:11 +00:00
audiobuffersplit: Add a gapless mode which inserts silence/drops samples on disconts
The output is always a continguous stream without any gaps.
This commit is contained in:
parent
2f761b89df
commit
f19edc8c83
2 changed files with 178 additions and 11 deletions
|
@ -46,6 +46,7 @@ enum
|
||||||
PROP_ALIGNMENT_THRESHOLD,
|
PROP_ALIGNMENT_THRESHOLD,
|
||||||
PROP_DISCONT_WAIT,
|
PROP_DISCONT_WAIT,
|
||||||
PROP_STRICT_BUFFER_SIZE,
|
PROP_STRICT_BUFFER_SIZE,
|
||||||
|
PROP_GAPLESS,
|
||||||
LAST_PROP
|
LAST_PROP
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ enum
|
||||||
#define DEFAULT_ALIGNMENT_THRESHOLD (40 * GST_MSECOND)
|
#define DEFAULT_ALIGNMENT_THRESHOLD (40 * GST_MSECOND)
|
||||||
#define DEFAULT_DISCONT_WAIT (1 * GST_SECOND)
|
#define DEFAULT_DISCONT_WAIT (1 * GST_SECOND)
|
||||||
#define DEFAULT_STRICT_BUFFER_SIZE (FALSE)
|
#define DEFAULT_STRICT_BUFFER_SIZE (FALSE)
|
||||||
|
#define DEFAULT_GAPLESS (FALSE)
|
||||||
|
|
||||||
#define parent_class gst_audio_buffer_split_parent_class
|
#define parent_class gst_audio_buffer_split_parent_class
|
||||||
G_DEFINE_TYPE (GstAudioBufferSplit, gst_audio_buffer_split, GST_TYPE_ELEMENT);
|
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 |
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
||||||
GST_PARAM_MUTABLE_READY));
|
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,
|
gst_element_class_set_static_metadata (gstelement_class,
|
||||||
"Audio Buffer Split", "Audio/Filter",
|
"Audio Buffer Split", "Audio/Filter",
|
||||||
"Splits raw audio buffers into equal sized chunks",
|
"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_n = DEFAULT_OUTPUT_BUFFER_DURATION_N;
|
||||||
self->output_buffer_duration_d = DEFAULT_OUTPUT_BUFFER_DURATION_D;
|
self->output_buffer_duration_d = DEFAULT_OUTPUT_BUFFER_DURATION_D;
|
||||||
self->strict_buffer_size = DEFAULT_STRICT_BUFFER_SIZE;
|
self->strict_buffer_size = DEFAULT_STRICT_BUFFER_SIZE;
|
||||||
|
self->gapless = DEFAULT_GAPLESS;
|
||||||
|
|
||||||
self->adapter = gst_adapter_new ();
|
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:
|
case PROP_STRICT_BUFFER_SIZE:
|
||||||
self->strict_buffer_size = g_value_get_boolean (value);
|
self->strict_buffer_size = g_value_get_boolean (value);
|
||||||
break;
|
break;
|
||||||
|
case PROP_GAPLESS:
|
||||||
|
self->gapless = g_value_get_boolean (value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -272,6 +285,9 @@ gst_audio_buffer_split_get_property (GObject * object, guint property_id,
|
||||||
case PROP_STRICT_BUFFER_SIZE:
|
case PROP_STRICT_BUFFER_SIZE:
|
||||||
g_value_set_boolean (value, self->strict_buffer_size);
|
g_value_set_boolean (value, self->strict_buffer_size);
|
||||||
break;
|
break;
|
||||||
|
case PROP_GAPLESS:
|
||||||
|
g_value_set_boolean (value, self->gapless);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -399,7 +415,8 @@ gst_audio_buffer_split_output (GstAudioBufferSplit * self, gboolean force,
|
||||||
|
|
||||||
static GstFlowReturn
|
static GstFlowReturn
|
||||||
gst_audio_buffer_split_handle_discont (GstAudioBufferSplit * self,
|
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;
|
gboolean discont;
|
||||||
GstFlowReturn ret = GST_FLOW_OK;
|
GstFlowReturn ret = GST_FLOW_OK;
|
||||||
|
@ -414,18 +431,125 @@ gst_audio_buffer_split_handle_discont (GstAudioBufferSplit * self,
|
||||||
GST_OBJECT_UNLOCK (self);
|
GST_OBJECT_UNLOCK (self);
|
||||||
|
|
||||||
if (discont) {
|
if (discont) {
|
||||||
if (self->strict_buffer_size) {
|
guint avail = gst_adapter_available (self->adapter);
|
||||||
gst_adapter_clear (self->adapter);
|
guint avail_samples = avail / bpf;
|
||||||
ret = GST_FLOW_OK;
|
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 {
|
} else {
|
||||||
ret =
|
current_timestamp =
|
||||||
gst_audio_buffer_split_output (self, TRUE, rate, bpf,
|
self->resync_time + gst_util_uint64_scale (self->current_offset,
|
||||||
samples_per_buffer);
|
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;
|
if (self->gapless) {
|
||||||
self->accumulated_error = 0;
|
if (self->current_offset == -1) {
|
||||||
self->resync_time = GST_BUFFER_PTS (buffer);
|
/* 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;
|
return ret;
|
||||||
|
@ -438,6 +562,41 @@ gst_audio_buffer_split_clip_buffer (GstAudioBufferSplit * self,
|
||||||
return gst_audio_buffer_clip (buffer, segment, rate, bpf);
|
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
|
static GstFlowReturn
|
||||||
gst_audio_buffer_split_sink_chain (GstPad * pad, GstObject * parent,
|
gst_audio_buffer_split_sink_chain (GstPad * pad, GstObject * parent,
|
||||||
GstBuffer * buffer)
|
GstBuffer * buffer)
|
||||||
|
@ -468,13 +627,19 @@ gst_audio_buffer_split_sink_chain (GstPad * pad, GstObject * parent,
|
||||||
return GST_FLOW_OK;
|
return GST_FLOW_OK;
|
||||||
|
|
||||||
ret =
|
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);
|
samples_per_buffer);
|
||||||
if (ret != GST_FLOW_OK) {
|
if (ret != GST_FLOW_OK) {
|
||||||
gst_buffer_unref (buffer);
|
gst_buffer_unref (buffer);
|
||||||
return ret;
|
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);
|
gst_adapter_push (self->adapter, buffer);
|
||||||
|
|
||||||
return gst_audio_buffer_split_output (self, FALSE, rate, bpf,
|
return gst_audio_buffer_split_output (self, FALSE, rate, bpf,
|
||||||
|
|
|
@ -55,12 +55,14 @@ struct _GstAudioBufferSplit {
|
||||||
GstAudioStreamAlign *stream_align;
|
GstAudioStreamAlign *stream_align;
|
||||||
GstClockTime resync_time;
|
GstClockTime resync_time;
|
||||||
guint64 current_offset; /* offset from start time in samples */
|
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 samples_per_buffer;
|
||||||
guint error_per_buffer;
|
guint error_per_buffer;
|
||||||
guint accumulated_error;
|
guint accumulated_error;
|
||||||
|
|
||||||
gboolean strict_buffer_size;
|
gboolean strict_buffer_size;
|
||||||
|
gboolean gapless;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _GstAudioBufferSplitClass {
|
struct _GstAudioBufferSplitClass {
|
||||||
|
|
Loading…
Reference in a new issue