gstreamer/gst/audiobuffersplit/gstaudiobuffersplit.c
Sebastian Dröge 20756e3387 audiobuffersplit: Keep incoming and outgoing segments separate
We might have to drain already queued input based on the old segment
before forwarding the new segment event. The new segment is only
forwarded after a discont as otherwise we might cause unnecessary
timestamp jumps as we output buffers timestamped based on sample counts.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1254>
2020-05-11 07:25:39 +00:00

874 lines
29 KiB
C

/*
* GStreamer
* Copyright (C) 2016 Sebastian Dröge <sebastian@centricular.com>
*
* 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 "gstaudiobuffersplit.h"
#define GST_CAT_DEFAULT gst_audio_buffer_split_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw")
);
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw")
);
enum
{
PROP_0,
PROP_OUTPUT_BUFFER_DURATION,
PROP_ALIGNMENT_THRESHOLD,
PROP_DISCONT_WAIT,
PROP_STRICT_BUFFER_SIZE,
PROP_GAPLESS,
PROP_MAX_SILENCE_TIME,
LAST_PROP
};
#define DEFAULT_OUTPUT_BUFFER_DURATION_N (1)
#define DEFAULT_OUTPUT_BUFFER_DURATION_D (50)
#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 DEFAULT_MAX_SILENCE_TIME (0)
#define parent_class gst_audio_buffer_split_parent_class
G_DEFINE_TYPE (GstAudioBufferSplit, gst_audio_buffer_split, GST_TYPE_ELEMENT);
static GstFlowReturn gst_audio_buffer_split_sink_chain (GstPad * pad,
GstObject * parent, GstBuffer * buffer);
static gboolean gst_audio_buffer_split_sink_event (GstPad * pad,
GstObject * parent, GstEvent * event);
static gboolean gst_audio_buffer_split_src_query (GstPad * pad,
GstObject * parent, GstQuery * query);
static void gst_audio_buffer_split_finalize (GObject * object);
static void gst_audio_buffer_split_get_property (GObject * object,
guint property_id, GValue * value, GParamSpec * pspec);
static void gst_audio_buffer_split_set_property (GObject * object,
guint property_id, const GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_audio_buffer_split_change_state (GstElement *
element, GstStateChange transition);
static void
gst_audio_buffer_split_class_init (GstAudioBufferSplitClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
gobject_class->set_property = gst_audio_buffer_split_set_property;
gobject_class->get_property = gst_audio_buffer_split_get_property;
gobject_class->finalize = gst_audio_buffer_split_finalize;
g_object_class_install_property (gobject_class, PROP_OUTPUT_BUFFER_DURATION,
gst_param_spec_fraction ("output-buffer-duration",
"Output Buffer Duration", "Output block size in seconds", 1, G_MAXINT,
G_MAXINT, 1, DEFAULT_OUTPUT_BUFFER_DURATION_N,
DEFAULT_OUTPUT_BUFFER_DURATION_D,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
GST_PARAM_MUTABLE_READY));
g_object_class_install_property (gobject_class, PROP_ALIGNMENT_THRESHOLD,
g_param_spec_uint64 ("alignment-threshold", "Alignment Threshold",
"Timestamp alignment threshold in nanoseconds", 0,
G_MAXUINT64 - 1, DEFAULT_ALIGNMENT_THRESHOLD,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
GST_PARAM_MUTABLE_READY));
g_object_class_install_property (gobject_class, PROP_DISCONT_WAIT,
g_param_spec_uint64 ("discont-wait", "Discont Wait",
"Window of time in nanoseconds to wait before "
"creating a discontinuity", 0,
G_MAXUINT64 - 1, DEFAULT_DISCONT_WAIT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
GST_PARAM_MUTABLE_READY));
g_object_class_install_property (gobject_class, PROP_STRICT_BUFFER_SIZE,
g_param_spec_boolean ("strict-buffer-size", "Strict buffer size",
"Discard the last samples at EOS or discont if they are too "
"small to fill a buffer", DEFAULT_STRICT_BUFFER_SIZE,
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));
g_object_class_install_property (gobject_class, PROP_MAX_SILENCE_TIME,
g_param_spec_uint64 ("max-silence-time",
"Maximum time of silence to insert",
"Do not insert silence in gapless mode if the gap exceeds this "
"period (in ns) (0 = disabled)",
0, G_MAXUINT64, DEFAULT_MAX_SILENCE_TIME,
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",
"Sebastian Dröge <sebastian@centricular.com>");
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&src_template));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&sink_template));
gstelement_class->change_state = gst_audio_buffer_split_change_state;
}
static void
gst_audio_buffer_split_init (GstAudioBufferSplit * self)
{
self->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
gst_pad_set_chain_function (self->sinkpad,
GST_DEBUG_FUNCPTR (gst_audio_buffer_split_sink_chain));
gst_pad_set_event_function (self->sinkpad,
GST_DEBUG_FUNCPTR (gst_audio_buffer_split_sink_event));
GST_PAD_SET_PROXY_CAPS (self->sinkpad);
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
self->srcpad = gst_pad_new_from_static_template (&src_template, "src");
gst_pad_set_query_function (self->srcpad,
GST_DEBUG_FUNCPTR (gst_audio_buffer_split_src_query));
GST_PAD_SET_PROXY_CAPS (self->srcpad);
gst_pad_use_fixed_caps (self->srcpad);
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
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 ();
self->stream_align =
gst_audio_stream_align_new (48000, DEFAULT_ALIGNMENT_THRESHOLD,
DEFAULT_DISCONT_WAIT);
}
static void
gst_audio_buffer_split_finalize (GObject * object)
{
GstAudioBufferSplit *self = GST_AUDIO_BUFFER_SPLIT (object);
if (self->adapter) {
gst_object_unref (self->adapter);
self->adapter = NULL;
}
if (self->stream_align) {
gst_audio_stream_align_free (self->stream_align);
self->stream_align = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_audio_buffer_split_update_samples_per_buffer (GstAudioBufferSplit * self)
{
gboolean ret = TRUE;
GST_OBJECT_LOCK (self);
/* For a later time */
if (!self->info.finfo
|| GST_AUDIO_INFO_FORMAT (&self->info) == GST_AUDIO_FORMAT_UNKNOWN) {
self->samples_per_buffer = 0;
goto out;
}
self->samples_per_buffer =
(((guint64) GST_AUDIO_INFO_RATE (&self->info)) *
self->output_buffer_duration_n) / self->output_buffer_duration_d;
if (self->samples_per_buffer == 0) {
ret = FALSE;
goto out;
}
self->error_per_buffer =
(((guint64) GST_AUDIO_INFO_RATE (&self->info)) *
self->output_buffer_duration_n) % self->output_buffer_duration_d;
self->accumulated_error = 0;
GST_DEBUG_OBJECT (self, "Buffer duration: %u/%u",
self->output_buffer_duration_n, self->output_buffer_duration_d);
GST_DEBUG_OBJECT (self, "Samples per buffer: %u (error: %u/%u)",
self->samples_per_buffer, self->error_per_buffer,
self->output_buffer_duration_d);
out:
GST_OBJECT_UNLOCK (self);
return ret;
}
static void
gst_audio_buffer_split_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
GstAudioBufferSplit *self = GST_AUDIO_BUFFER_SPLIT (object);
switch (property_id) {
case PROP_OUTPUT_BUFFER_DURATION:
self->output_buffer_duration_n = gst_value_get_fraction_numerator (value);
self->output_buffer_duration_d =
gst_value_get_fraction_denominator (value);
gst_audio_buffer_split_update_samples_per_buffer (self);
break;
case PROP_ALIGNMENT_THRESHOLD:
GST_OBJECT_LOCK (self);
gst_audio_stream_align_set_alignment_threshold (self->stream_align,
g_value_get_uint64 (value));
GST_OBJECT_UNLOCK (self);
break;
case PROP_DISCONT_WAIT:
GST_OBJECT_LOCK (self);
gst_audio_stream_align_set_discont_wait (self->stream_align,
g_value_get_uint64 (value));
GST_OBJECT_UNLOCK (self);
break;
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;
case PROP_MAX_SILENCE_TIME:
self->max_silence_time = g_value_get_uint64 (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gst_audio_buffer_split_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
GstAudioBufferSplit *self = GST_AUDIO_BUFFER_SPLIT (object);
switch (property_id) {
case PROP_OUTPUT_BUFFER_DURATION:
gst_value_set_fraction (value, self->output_buffer_duration_n,
self->output_buffer_duration_d);
break;
case PROP_ALIGNMENT_THRESHOLD:
GST_OBJECT_LOCK (self);
g_value_set_uint64 (value,
gst_audio_stream_align_get_alignment_threshold (self->stream_align));
GST_OBJECT_UNLOCK (self);
break;
case PROP_DISCONT_WAIT:
GST_OBJECT_LOCK (self);
g_value_set_uint64 (value,
gst_audio_stream_align_get_discont_wait (self->stream_align));
GST_OBJECT_UNLOCK (self);
break;
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;
case PROP_MAX_SILENCE_TIME:
g_value_set_uint64 (value, self->max_silence_time);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static GstStateChangeReturn
gst_audio_buffer_split_change_state (GstElement * element,
GstStateChange transition)
{
GstAudioBufferSplit *self = GST_AUDIO_BUFFER_SPLIT (element);
GstStateChangeReturn state_ret;
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
gst_audio_info_init (&self->info);
gst_segment_init (&self->in_segment, GST_FORMAT_TIME);
gst_segment_init (&self->out_segment, GST_FORMAT_UNDEFINED);
self->segment_pending = FALSE;
GST_OBJECT_LOCK (self);
gst_audio_stream_align_mark_discont (self->stream_align);
GST_OBJECT_UNLOCK (self);
self->current_offset = -1;
self->accumulated_error = 0;
self->samples_per_buffer = 0;
break;
default:
break;
}
state_ret =
GST_ELEMENT_CLASS (gst_audio_buffer_split_parent_class)->change_state
(element, transition);
if (state_ret == GST_STATE_CHANGE_FAILURE)
return state_ret;
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_adapter_clear (self->adapter);
GST_OBJECT_LOCK (self);
gst_audio_stream_align_mark_discont (self->stream_align);
GST_OBJECT_UNLOCK (self);
break;
default:
break;
}
return state_ret;
}
static GstFlowReturn
gst_audio_buffer_split_output (GstAudioBufferSplit * self, gboolean force,
gint rate, gint bpf, guint samples_per_buffer)
{
gint size, avail;
GstFlowReturn ret = GST_FLOW_OK;
GstClockTime resync_time;
resync_time = self->resync_time;
size = samples_per_buffer * bpf;
/* If we accumulated enough error for one sample, include one
* more sample in this buffer. Accumulated error is updated below */
if (self->error_per_buffer + self->accumulated_error >=
self->output_buffer_duration_d)
size += bpf;
while ((avail = gst_adapter_available (self->adapter)) >= size || (force
&& avail > 0)) {
GstBuffer *buffer;
GstClockTime resync_time_diff;
size = MIN (size, avail);
buffer = gst_adapter_take_buffer (self->adapter, size);
/* After a reset we have to set the discont flag */
if (self->current_offset == 0)
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
resync_time_diff =
gst_util_uint64_scale (self->current_offset, GST_SECOND, rate);
if (self->out_segment.rate < 0.0) {
if (resync_time > resync_time_diff)
GST_BUFFER_TIMESTAMP (buffer) = resync_time - resync_time_diff;
else
GST_BUFFER_TIMESTAMP (buffer) = 0;
GST_BUFFER_DURATION (buffer) =
gst_util_uint64_scale (size / bpf, GST_SECOND, rate);
self->current_offset += size / bpf;
} else {
GST_BUFFER_TIMESTAMP (buffer) = resync_time + resync_time_diff;
self->current_offset += size / bpf;
resync_time_diff =
gst_util_uint64_scale (self->current_offset, GST_SECOND, rate);
GST_BUFFER_DURATION (buffer) =
resync_time_diff - (GST_BUFFER_TIMESTAMP (buffer) - resync_time);
}
GST_BUFFER_OFFSET (buffer) = GST_BUFFER_OFFSET_NONE;
GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE;
self->accumulated_error =
(self->accumulated_error +
self->error_per_buffer) % self->output_buffer_duration_d;
GST_LOG_OBJECT (self,
"Outputting buffer at timestamp %" GST_TIME_FORMAT " with duration %"
GST_TIME_FORMAT " (%u samples)",
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), size / bpf);
ret = gst_pad_push (self->srcpad, buffer);
if (ret != GST_FLOW_OK)
break;
/* Update the size based on the accumulated error we have now after
* taking out a buffer. Same code as above */
size = samples_per_buffer * bpf;
if (self->error_per_buffer + self->accumulated_error >=
self->output_buffer_duration_d)
size += bpf;
}
return ret;
}
static GstFlowReturn
gst_audio_buffer_split_handle_discont (GstAudioBufferSplit * self,
GstBuffer * buffer, GstAudioFormat format, gint rate, gint bpf,
guint samples_per_buffer)
{
gboolean discont;
GstFlowReturn 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;
GST_OBJECT_LOCK (self);
discont =
gst_audio_stream_align_process (self->stream_align,
self->in_segment.rate < 0 ? FALSE : GST_BUFFER_IS_DISCONT (buffer)
|| GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_RESYNC),
GST_BUFFER_PTS (buffer), gst_buffer_get_size (buffer) / bpf, NULL, NULL,
NULL);
GST_OBJECT_UNLOCK (self);
if (!discont)
return ret;
/* Reset */
self->drop_samples = 0;
if (self->in_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 {
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);
}
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);
discont = FALSE;
} 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)));
discont = FALSE;
} 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);
GstClockTime silence_time =
gst_util_uint64_scale (silence_samples, GST_SECOND, rate);
if (silence_time > self->max_silence_time) {
GST_DEBUG_OBJECT (self,
"Not inserting %" G_GUINT64_FORMAT " samples of silence (%"
GST_TIME_FORMAT " exceeds maximum %" GST_TIME_FORMAT ")",
silence_samples, GST_TIME_ARGS (silence_time),
GST_TIME_ARGS (self->max_silence_time));
} else {
GST_DEBUG_OBJECT (self,
"Inserting %" G_GUINT64_FORMAT " samples of silence (%"
GST_TIME_FORMAT ")", silence_samples,
GST_TIME_ARGS (silence_time));
/* 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;
}
discont = FALSE;
}
} 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;
discont = FALSE;
}
}
}
if (discont) {
/* We might end up in here also in gapless mode, if the above code decided
* that no silence is to be inserted, because e.g. the gap is too big */
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);
if (self->segment_pending) {
GstEvent *event;
self->out_segment = self->in_segment;
GST_DEBUG_OBJECT (self, "Updating output segment %" GST_SEGMENT_FORMAT,
&self->out_segment);
event = gst_event_new_segment (&self->out_segment);
gst_event_set_seqnum (event, self->segment_seqnum);
gst_pad_push_event (self->srcpad, event);
self->segment_pending = FALSE;
}
}
return ret;
}
static GstBuffer *
gst_audio_buffer_split_clip_buffer (GstAudioBufferSplit * self,
GstBuffer * buffer, const GstSegment * segment, gint rate, gint 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 %" G_GUINT64_FORMAT
" 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->out_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)
{
GstAudioBufferSplit *self = GST_AUDIO_BUFFER_SPLIT (parent);
GstFlowReturn ret;
GstAudioFormat format;
gint rate, bpf, samples_per_buffer;
GST_OBJECT_LOCK (self);
format =
self->info.
finfo ? GST_AUDIO_INFO_FORMAT (&self->info) : GST_AUDIO_FORMAT_UNKNOWN;
rate = GST_AUDIO_INFO_RATE (&self->info);
bpf = GST_AUDIO_INFO_BPF (&self->info);
samples_per_buffer = self->samples_per_buffer;
GST_OBJECT_UNLOCK (self);
if (format == GST_AUDIO_FORMAT_UNKNOWN || samples_per_buffer == 0) {
gst_buffer_unref (buffer);
return GST_FLOW_NOT_NEGOTIATED;
}
buffer =
gst_audio_buffer_split_clip_buffer (self, buffer, &self->in_segment, rate,
bpf);
if (!buffer)
return GST_FLOW_OK;
ret =
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,
samples_per_buffer);
}
static gboolean
gst_audio_buffer_split_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
GstAudioBufferSplit *self = GST_AUDIO_BUFFER_SPLIT (parent);
gboolean ret = FALSE;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:{
GstCaps *caps;
GstAudioInfo info;
gst_event_parse_caps (event, &caps);
ret = gst_audio_info_from_caps (&info, caps);
if (ret) {
GST_DEBUG_OBJECT (self, "Got caps %" GST_PTR_FORMAT, caps);
if (!gst_audio_info_is_equal (&info, &self->info)) {
if (self->strict_buffer_size) {
gst_adapter_clear (self->adapter);
} else {
GstAudioFormat format;
gint rate, bpf, samples_per_buffer;
GST_OBJECT_LOCK (self);
format =
self->info.finfo ? GST_AUDIO_INFO_FORMAT (&self->info) :
GST_AUDIO_FORMAT_UNKNOWN;
rate = GST_AUDIO_INFO_RATE (&self->info);
bpf = GST_AUDIO_INFO_BPF (&self->info);
samples_per_buffer = self->samples_per_buffer;
GST_OBJECT_UNLOCK (self);
if (format != GST_AUDIO_FORMAT_UNKNOWN && samples_per_buffer != 0)
gst_audio_buffer_split_output (self, TRUE, rate, bpf,
samples_per_buffer);
}
}
self->info = info;
GST_OBJECT_LOCK (self);
gst_audio_stream_align_set_rate (self->stream_align, self->info.rate);
GST_OBJECT_UNLOCK (self);
ret = gst_audio_buffer_split_update_samples_per_buffer (self);
} else {
ret = FALSE;
}
if (ret)
ret = gst_pad_event_default (pad, parent, event);
else
gst_event_unref (event);
break;
}
case GST_EVENT_FLUSH_STOP:
gst_segment_init (&self->in_segment, GST_FORMAT_TIME);
gst_segment_init (&self->out_segment, GST_FORMAT_UNDEFINED);
self->segment_pending = FALSE;
GST_OBJECT_LOCK (self);
gst_audio_stream_align_mark_discont (self->stream_align);
GST_OBJECT_UNLOCK (self);
self->current_offset = -1;
self->accumulated_error = 0;
gst_adapter_clear (self->adapter);
ret = gst_pad_event_default (pad, parent, event);
break;
case GST_EVENT_SEGMENT:
gst_event_copy_segment (event, &self->in_segment);
if (self->in_segment.format != GST_FORMAT_TIME) {
gst_event_unref (event);
ret = FALSE;
} else {
GST_DEBUG_OBJECT (self,
"Received new input segment %" GST_SEGMENT_FORMAT,
&self->in_segment);
self->segment_pending = TRUE;
self->segment_seqnum = gst_event_get_seqnum (event);
gst_event_unref (event);
ret = TRUE;
}
break;
case GST_EVENT_EOS:
if (self->strict_buffer_size) {
gst_adapter_clear (self->adapter);
} else {
GstAudioFormat format;
gint rate, bpf, samples_per_buffer;
GST_OBJECT_LOCK (self);
format =
self->info.finfo ? GST_AUDIO_INFO_FORMAT (&self->info) :
GST_AUDIO_FORMAT_UNKNOWN;
rate = GST_AUDIO_INFO_RATE (&self->info);
bpf = GST_AUDIO_INFO_BPF (&self->info);
samples_per_buffer = self->samples_per_buffer;
GST_OBJECT_UNLOCK (self);
if (format != GST_AUDIO_FORMAT_UNKNOWN && samples_per_buffer != 0)
gst_audio_buffer_split_output (self, TRUE, rate, bpf,
samples_per_buffer);
}
ret = gst_pad_event_default (pad, parent, event);
break;
default:
ret = gst_pad_event_default (pad, parent, event);
break;
}
return ret;
}
static gboolean
gst_audio_buffer_split_src_query (GstPad * pad,
GstObject * parent, GstQuery * query)
{
GstAudioBufferSplit *self = GST_AUDIO_BUFFER_SPLIT (parent);
gboolean ret = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_LATENCY:{
if ((ret = gst_pad_peer_query (self->sinkpad, query))) {
GstClockTime latency;
GstClockTime min, max;
gboolean live;
gst_query_parse_latency (query, &live, &min, &max);
GST_DEBUG_OBJECT (self, "Peer latency: min %"
GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
GST_TIME_ARGS (min), GST_TIME_ARGS (max));
latency =
gst_util_uint64_scale (GST_SECOND, self->output_buffer_duration_n,
self->output_buffer_duration_d);
GST_DEBUG_OBJECT (self, "Our latency: min %" GST_TIME_FORMAT
", max %" GST_TIME_FORMAT,
GST_TIME_ARGS (latency), GST_TIME_ARGS (latency));
min += latency;
if (max != GST_CLOCK_TIME_NONE)
max += latency;
GST_DEBUG_OBJECT (self, "Calculated total latency : min %"
GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
GST_TIME_ARGS (min), GST_TIME_ARGS (max));
gst_query_set_latency (query, live, min, max);
}
break;
}
default:
ret = gst_pad_query_default (pad, parent, query);
break;
}
return ret;
}
static gboolean
plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (gst_audio_buffer_split_debug, "audiobuffersplit",
0, "Audio buffer splitter");
gst_element_register (plugin, "audiobuffersplit", GST_RANK_NONE,
GST_TYPE_AUDIO_BUFFER_SPLIT);
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
audiobuffersplit,
"Audio buffer splitter",
plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN)