| /* GStreamer |
| * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu> |
| * Copyright (C) 2003,2004 David A. Schleef <ds@schleef.org> |
| * |
| * 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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| /* Element-Checklist-Version: 5 */ |
| |
| /** |
| * SECTION:element-legacyresample |
| * |
| * legacyresample resamples raw audio buffers to different sample rates using |
| * a configurable windowing function to enhance quality. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * |[ |
| * gst-launch -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! audioconvert ! legacyresample ! audio/x-raw-int, rate=8000 ! alsasink |
| * ]| Decode an Ogg/Vorbis downsample to 8Khz and play sound through alsa. |
| * To create the Ogg/Vorbis file refer to the documentation of vorbisenc. |
| * </refsect2> |
| * |
| * Last reviewed on 2006-03-02 (0.10.4) |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include <math.h> |
| |
| /*#define DEBUG_ENABLED */ |
| #include "gstlegacyresample.h" |
| #include <gst/audio/audio.h> |
| #include <gst/base/gstbasetransform.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (legacyresample_debug); |
| #define GST_CAT_DEFAULT legacyresample_debug |
| |
| /* elementfactory information */ |
| static const GstElementDetails gst_legacyresample_details = |
| GST_ELEMENT_DETAILS ("Audio scaler", |
| "Filter/Converter/Audio", |
| "Resample audio", |
| "David Schleef <ds@schleef.org>"); |
| |
| #define DEFAULT_FILTERLEN 16 |
| |
| enum |
| { |
| PROP_0, |
| PROP_FILTERLEN |
| }; |
| |
| #define SUPPORTED_CAPS \ |
| GST_STATIC_CAPS ( \ |
| "audio/x-raw-int, " \ |
| "rate = (int) [ 1, MAX ], " \ |
| "channels = (int) [ 1, MAX ], " \ |
| "endianness = (int) BYTE_ORDER, " \ |
| "width = (int) 16, " \ |
| "depth = (int) 16, " \ |
| "signed = (boolean) true;" \ |
| "audio/x-raw-int, " \ |
| "rate = (int) [ 1, MAX ], " \ |
| "channels = (int) [ 1, MAX ], " \ |
| "endianness = (int) BYTE_ORDER, " \ |
| "width = (int) 32, " \ |
| "depth = (int) 32, " \ |
| "signed = (boolean) true;" \ |
| "audio/x-raw-float, " \ |
| "rate = (int) [ 1, MAX ], " \ |
| "channels = (int) [ 1, MAX ], " \ |
| "endianness = (int) BYTE_ORDER, " \ |
| "width = (int) 32; " \ |
| "audio/x-raw-float, " \ |
| "rate = (int) [ 1, MAX ], " \ |
| "channels = (int) [ 1, MAX ], " \ |
| "endianness = (int) BYTE_ORDER, " \ |
| "width = (int) 64" \ |
| ) |
| |
| static GstStaticPadTemplate gst_legacyresample_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, GST_PAD_ALWAYS, SUPPORTED_CAPS); |
| |
| static GstStaticPadTemplate gst_legacyresample_src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, GST_PAD_ALWAYS, SUPPORTED_CAPS); |
| |
| static void gst_legacyresample_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec); |
| static void gst_legacyresample_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec); |
| |
| /* vmethods */ |
| static gboolean legacyresample_get_unit_size (GstBaseTransform * base, |
| GstCaps * caps, guint * size); |
| static GstCaps *legacyresample_transform_caps (GstBaseTransform * base, |
| GstPadDirection direction, GstCaps * caps); |
| static void legacyresample_fixate_caps (GstBaseTransform * base, |
| GstPadDirection direction, GstCaps * caps, GstCaps * othercaps); |
| static gboolean legacyresample_transform_size (GstBaseTransform * trans, |
| GstPadDirection direction, GstCaps * incaps, guint insize, |
| GstCaps * outcaps, guint * outsize); |
| static gboolean legacyresample_set_caps (GstBaseTransform * base, |
| GstCaps * incaps, GstCaps * outcaps); |
| static GstFlowReturn legacyresample_pushthrough (GstLegacyresample * |
| legacyresample); |
| static GstFlowReturn legacyresample_transform (GstBaseTransform * base, |
| GstBuffer * inbuf, GstBuffer * outbuf); |
| static gboolean legacyresample_event (GstBaseTransform * base, |
| GstEvent * event); |
| static gboolean legacyresample_start (GstBaseTransform * base); |
| static gboolean legacyresample_stop (GstBaseTransform * base); |
| |
| static gboolean legacyresample_query (GstPad * pad, GstQuery * query); |
| static const GstQueryType *legacyresample_query_type (GstPad * pad); |
| |
| #define DEBUG_INIT(bla) \ |
| GST_DEBUG_CATEGORY_INIT (legacyresample_debug, "legacyresample", 0, "audio resampling element"); |
| |
| GST_BOILERPLATE_FULL (GstLegacyresample, gst_legacyresample, GstBaseTransform, |
| GST_TYPE_BASE_TRANSFORM, DEBUG_INIT); |
| |
| static void |
| gst_legacyresample_base_init (gpointer g_class) |
| { |
| GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); |
| |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&gst_legacyresample_src_template)); |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&gst_legacyresample_sink_template)); |
| |
| gst_element_class_set_details (gstelement_class, &gst_legacyresample_details); |
| } |
| |
| static void |
| gst_legacyresample_class_init (GstLegacyresampleClass * klass) |
| { |
| GObjectClass *gobject_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| |
| gobject_class->set_property = gst_legacyresample_set_property; |
| gobject_class->get_property = gst_legacyresample_get_property; |
| |
| g_object_class_install_property (gobject_class, PROP_FILTERLEN, |
| g_param_spec_int ("filter-length", "filter length", |
| "Length of the resample filter", 0, G_MAXINT, DEFAULT_FILTERLEN, |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); |
| |
| GST_BASE_TRANSFORM_CLASS (klass)->start = |
| GST_DEBUG_FUNCPTR (legacyresample_start); |
| GST_BASE_TRANSFORM_CLASS (klass)->stop = |
| GST_DEBUG_FUNCPTR (legacyresample_stop); |
| GST_BASE_TRANSFORM_CLASS (klass)->transform_size = |
| GST_DEBUG_FUNCPTR (legacyresample_transform_size); |
| GST_BASE_TRANSFORM_CLASS (klass)->get_unit_size = |
| GST_DEBUG_FUNCPTR (legacyresample_get_unit_size); |
| GST_BASE_TRANSFORM_CLASS (klass)->transform_caps = |
| GST_DEBUG_FUNCPTR (legacyresample_transform_caps); |
| GST_BASE_TRANSFORM_CLASS (klass)->fixate_caps = |
| GST_DEBUG_FUNCPTR (legacyresample_fixate_caps); |
| GST_BASE_TRANSFORM_CLASS (klass)->set_caps = |
| GST_DEBUG_FUNCPTR (legacyresample_set_caps); |
| GST_BASE_TRANSFORM_CLASS (klass)->transform = |
| GST_DEBUG_FUNCPTR (legacyresample_transform); |
| GST_BASE_TRANSFORM_CLASS (klass)->event = |
| GST_DEBUG_FUNCPTR (legacyresample_event); |
| |
| GST_BASE_TRANSFORM_CLASS (klass)->passthrough_on_same_caps = TRUE; |
| } |
| |
| static void |
| gst_legacyresample_init (GstLegacyresample * legacyresample, |
| GstLegacyresampleClass * klass) |
| { |
| GstBaseTransform *trans; |
| |
| trans = GST_BASE_TRANSFORM (legacyresample); |
| |
| /* buffer alloc passthrough is too impossible. FIXME, it |
| * is trivial in the passthrough case. */ |
| gst_pad_set_bufferalloc_function (trans->sinkpad, NULL); |
| |
| legacyresample->filter_length = DEFAULT_FILTERLEN; |
| |
| legacyresample->need_discont = FALSE; |
| |
| gst_pad_set_query_function (trans->srcpad, legacyresample_query); |
| gst_pad_set_query_type_function (trans->srcpad, legacyresample_query_type); |
| } |
| |
| /* vmethods */ |
| static gboolean |
| legacyresample_start (GstBaseTransform * base) |
| { |
| GstLegacyresample *legacyresample = GST_LEGACYRESAMPLE (base); |
| |
| legacyresample->resample = resample_new (); |
| legacyresample->ts_offset = -1; |
| legacyresample->offset = -1; |
| legacyresample->next_ts = -1; |
| |
| resample_set_filter_length (legacyresample->resample, |
| legacyresample->filter_length); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| legacyresample_stop (GstBaseTransform * base) |
| { |
| GstLegacyresample *legacyresample = GST_LEGACYRESAMPLE (base); |
| |
| if (legacyresample->resample) { |
| resample_free (legacyresample->resample); |
| legacyresample->resample = NULL; |
| } |
| |
| gst_caps_replace (&legacyresample->sinkcaps, NULL); |
| gst_caps_replace (&legacyresample->srccaps, NULL); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| legacyresample_get_unit_size (GstBaseTransform * base, GstCaps * caps, |
| guint * size) |
| { |
| gint width, channels; |
| GstStructure *structure; |
| gboolean ret; |
| |
| g_assert (size); |
| |
| /* this works for both float and int */ |
| structure = gst_caps_get_structure (caps, 0); |
| ret = gst_structure_get_int (structure, "width", &width); |
| ret &= gst_structure_get_int (structure, "channels", &channels); |
| g_return_val_if_fail (ret, FALSE); |
| |
| *size = width * channels / 8; |
| |
| return TRUE; |
| } |
| |
| static GstCaps * |
| legacyresample_transform_caps (GstBaseTransform * base, |
| GstPadDirection direction, GstCaps * caps) |
| { |
| const GValue *val; |
| GstStructure *s; |
| GstCaps *res; |
| |
| /* transform single caps into input_caps + input_caps with the rate |
| * field set to our supported range. This ensures that upstream knows |
| * about downstream's prefered rate(s) and can negotiate accordingly. */ |
| res = gst_caps_copy (caps); |
| |
| /* first, however, check if the caps contain a range for the rate field, in |
| * which case that side isn't going to care much about the exact sample rate |
| * chosen and we should just assume things will get fixated to something sane |
| * and we may just as well offer our full range instead of the range in the |
| * caps. If the rate is not an int range value, it's likely to express a |
| * real preference or limitation and we should maintain that structure as |
| * preference by putting it first into the transformed caps, and only add |
| * our full rate range as second option */ |
| s = gst_caps_get_structure (res, 0); |
| val = gst_structure_get_value (s, "rate"); |
| if (val == NULL || GST_VALUE_HOLDS_INT_RANGE (val)) { |
| /* overwrite existing range, or add field if it doesn't exist yet */ |
| gst_structure_set (s, "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); |
| } else { |
| /* append caps with full range to existing caps with non-range rate field */ |
| s = gst_structure_copy (s); |
| gst_structure_set (s, "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); |
| gst_caps_append_structure (res, s); |
| } |
| |
| return res; |
| } |
| |
| /* Fixate rate to the allowed rate that has the smallest difference */ |
| static void |
| legacyresample_fixate_caps (GstBaseTransform * base, |
| GstPadDirection direction, GstCaps * caps, GstCaps * othercaps) |
| { |
| GstStructure *s; |
| gint rate; |
| |
| s = gst_caps_get_structure (caps, 0); |
| if (!gst_structure_get_int (s, "rate", &rate)) |
| return; |
| |
| s = gst_caps_get_structure (othercaps, 0); |
| gst_structure_fixate_field_nearest_int (s, "rate", rate); |
| } |
| |
| static gboolean |
| resample_set_state_from_caps (ResampleState * state, GstCaps * incaps, |
| GstCaps * outcaps, gint * channels, gint * inrate, gint * outrate) |
| { |
| GstStructure *structure; |
| gboolean ret; |
| gint myinrate, myoutrate; |
| int mychannels; |
| gint width, depth; |
| ResampleFormat format; |
| |
| GST_DEBUG ("incaps %" GST_PTR_FORMAT ", outcaps %" |
| GST_PTR_FORMAT, incaps, outcaps); |
| |
| structure = gst_caps_get_structure (incaps, 0); |
| |
| /* get width */ |
| ret = gst_structure_get_int (structure, "width", &width); |
| if (!ret) |
| goto no_width; |
| |
| /* figure out the format */ |
| if (g_str_equal (gst_structure_get_name (structure), "audio/x-raw-float")) { |
| if (width == 32) |
| format = RESAMPLE_FORMAT_F32; |
| else if (width == 64) |
| format = RESAMPLE_FORMAT_F64; |
| else |
| goto wrong_depth; |
| } else { |
| /* for int, depth and width must be the same */ |
| ret = gst_structure_get_int (structure, "depth", &depth); |
| if (!ret || width != depth) |
| goto not_equal; |
| |
| if (width == 16) |
| format = RESAMPLE_FORMAT_S16; |
| else if (width == 32) |
| format = RESAMPLE_FORMAT_S32; |
| else |
| goto wrong_depth; |
| } |
| ret = gst_structure_get_int (structure, "rate", &myinrate); |
| ret &= gst_structure_get_int (structure, "channels", &mychannels); |
| if (!ret) |
| goto no_in_rate_channels; |
| |
| structure = gst_caps_get_structure (outcaps, 0); |
| ret = gst_structure_get_int (structure, "rate", &myoutrate); |
| if (!ret) |
| goto no_out_rate; |
| |
| if (channels) |
| *channels = mychannels; |
| if (inrate) |
| *inrate = myinrate; |
| if (outrate) |
| *outrate = myoutrate; |
| |
| resample_set_format (state, format); |
| resample_set_n_channels (state, mychannels); |
| resample_set_input_rate (state, myinrate); |
| resample_set_output_rate (state, myoutrate); |
| |
| return TRUE; |
| |
| /* ERRORS */ |
| no_width: |
| { |
| GST_DEBUG ("failed to get width from caps"); |
| return FALSE; |
| } |
| not_equal: |
| { |
| GST_DEBUG ("width %d and depth %d must be the same", width, depth); |
| return FALSE; |
| } |
| wrong_depth: |
| { |
| GST_DEBUG ("unknown depth %d found", depth); |
| return FALSE; |
| } |
| no_in_rate_channels: |
| { |
| GST_DEBUG ("could not get input rate and channels"); |
| return FALSE; |
| } |
| no_out_rate: |
| { |
| GST_DEBUG ("could not get output rate"); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| legacyresample_transform_size (GstBaseTransform * base, |
| GstPadDirection direction, GstCaps * caps, guint size, GstCaps * othercaps, |
| guint * othersize) |
| { |
| GstLegacyresample *legacyresample = GST_LEGACYRESAMPLE (base); |
| ResampleState *state; |
| GstCaps *srccaps, *sinkcaps; |
| gboolean use_internal = FALSE; /* whether we use the internal state */ |
| gboolean ret = TRUE; |
| |
| GST_LOG_OBJECT (base, "asked to transform size %d in direction %s", |
| size, direction == GST_PAD_SINK ? "SINK" : "SRC"); |
| if (direction == GST_PAD_SINK) { |
| sinkcaps = caps; |
| srccaps = othercaps; |
| } else { |
| sinkcaps = othercaps; |
| srccaps = caps; |
| } |
| |
| /* if the caps are the ones that _set_caps got called with; we can use |
| * our own state; otherwise we'll have to create a state */ |
| if (gst_caps_is_equal (sinkcaps, legacyresample->sinkcaps) && |
| gst_caps_is_equal (srccaps, legacyresample->srccaps)) { |
| use_internal = TRUE; |
| state = legacyresample->resample; |
| } else { |
| GST_DEBUG_OBJECT (legacyresample, |
| "caps are not the set caps, creating state"); |
| state = resample_new (); |
| resample_set_filter_length (state, legacyresample->filter_length); |
| resample_set_state_from_caps (state, sinkcaps, srccaps, NULL, NULL, NULL); |
| } |
| |
| if (direction == GST_PAD_SINK) { |
| /* asked to convert size of an incoming buffer */ |
| *othersize = resample_get_output_size_for_input (state, size); |
| } else { |
| /* asked to convert size of an outgoing buffer */ |
| *othersize = resample_get_input_size_for_output (state, size); |
| } |
| g_assert (*othersize % state->sample_size == 0); |
| |
| /* we make room for one extra sample, given that the resampling filter |
| * can output an extra one for non-integral i_rate/o_rate */ |
| GST_LOG_OBJECT (base, "transformed size %d to %d", size, *othersize); |
| |
| if (!use_internal) { |
| resample_free (state); |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| legacyresample_set_caps (GstBaseTransform * base, GstCaps * incaps, |
| GstCaps * outcaps) |
| { |
| gboolean ret; |
| gint inrate, outrate; |
| int channels; |
| GstLegacyresample *legacyresample = GST_LEGACYRESAMPLE (base); |
| |
| GST_DEBUG_OBJECT (base, "incaps %" GST_PTR_FORMAT ", outcaps %" |
| GST_PTR_FORMAT, incaps, outcaps); |
| |
| ret = resample_set_state_from_caps (legacyresample->resample, incaps, outcaps, |
| &channels, &inrate, &outrate); |
| |
| g_return_val_if_fail (ret, FALSE); |
| |
| legacyresample->channels = channels; |
| GST_DEBUG_OBJECT (legacyresample, "set channels to %d", channels); |
| legacyresample->i_rate = inrate; |
| GST_DEBUG_OBJECT (legacyresample, "set i_rate to %d", inrate); |
| legacyresample->o_rate = outrate; |
| GST_DEBUG_OBJECT (legacyresample, "set o_rate to %d", outrate); |
| |
| /* save caps so we can short-circuit in the size_transform if the caps |
| * are the same */ |
| gst_caps_replace (&legacyresample->sinkcaps, incaps); |
| gst_caps_replace (&legacyresample->srccaps, outcaps); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| legacyresample_event (GstBaseTransform * base, GstEvent * event) |
| { |
| GstLegacyresample *legacyresample; |
| |
| legacyresample = GST_LEGACYRESAMPLE (base); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_START: |
| break; |
| case GST_EVENT_FLUSH_STOP: |
| if (legacyresample->resample) |
| resample_input_flush (legacyresample->resample); |
| legacyresample->ts_offset = -1; |
| legacyresample->next_ts = -1; |
| legacyresample->offset = -1; |
| break; |
| case GST_EVENT_NEWSEGMENT: |
| resample_input_pushthrough (legacyresample->resample); |
| legacyresample_pushthrough (legacyresample); |
| legacyresample->ts_offset = -1; |
| legacyresample->next_ts = -1; |
| legacyresample->offset = -1; |
| break; |
| case GST_EVENT_EOS: |
| resample_input_eos (legacyresample->resample); |
| legacyresample_pushthrough (legacyresample); |
| break; |
| default: |
| break; |
| } |
| return parent_class->event (base, event); |
| } |
| |
| static GstFlowReturn |
| legacyresample_do_output (GstLegacyresample * legacyresample, |
| GstBuffer * outbuf) |
| { |
| int outsize; |
| int outsamples; |
| ResampleState *r; |
| |
| r = legacyresample->resample; |
| |
| outsize = resample_get_output_size (r); |
| GST_LOG_OBJECT (legacyresample, "legacyresample can give me %d bytes", |
| outsize); |
| |
| /* protect against mem corruption */ |
| if (outsize > GST_BUFFER_SIZE (outbuf)) { |
| GST_WARNING_OBJECT (legacyresample, |
| "overriding legacyresample's outsize %d with outbuffer's size %d", |
| outsize, GST_BUFFER_SIZE (outbuf)); |
| outsize = GST_BUFFER_SIZE (outbuf); |
| } |
| /* catch possibly wrong size differences */ |
| if (GST_BUFFER_SIZE (outbuf) - outsize > r->sample_size) { |
| GST_WARNING_OBJECT (legacyresample, |
| "legacyresample's outsize %d too far from outbuffer's size %d", |
| outsize, GST_BUFFER_SIZE (outbuf)); |
| } |
| |
| outsize = resample_get_output_data (r, GST_BUFFER_DATA (outbuf), outsize); |
| outsamples = outsize / r->sample_size; |
| GST_LOG_OBJECT (legacyresample, "resample gave me %d bytes or %d samples", |
| outsize, outsamples); |
| |
| GST_BUFFER_OFFSET (outbuf) = legacyresample->offset; |
| GST_BUFFER_TIMESTAMP (outbuf) = legacyresample->next_ts; |
| |
| if (legacyresample->ts_offset != -1) { |
| legacyresample->offset += outsamples; |
| legacyresample->ts_offset += outsamples; |
| legacyresample->next_ts = |
| gst_util_uint64_scale_int (legacyresample->ts_offset, GST_SECOND, |
| legacyresample->o_rate); |
| GST_BUFFER_OFFSET_END (outbuf) = legacyresample->offset; |
| |
| /* we calculate DURATION as the difference between "next" timestamp |
| * and current timestamp so we ensure a contiguous stream, instead of |
| * having rounding errors. */ |
| GST_BUFFER_DURATION (outbuf) = legacyresample->next_ts - |
| GST_BUFFER_TIMESTAMP (outbuf); |
| } else { |
| /* no valid offset know, we can still sortof calculate the duration though */ |
| GST_BUFFER_DURATION (outbuf) = |
| gst_util_uint64_scale_int (outsamples, GST_SECOND, |
| legacyresample->o_rate); |
| } |
| |
| /* check for possible mem corruption */ |
| if (outsize > GST_BUFFER_SIZE (outbuf)) { |
| /* this is an error that when it happens, would need fixing in the |
| * resample library; we told it we wanted only GST_BUFFER_SIZE (outbuf), |
| * and it gave us more ! */ |
| GST_WARNING_OBJECT (legacyresample, |
| "legacyresample, you memory corrupting bastard. " |
| "you gave me outsize %d while my buffer was size %d", |
| outsize, GST_BUFFER_SIZE (outbuf)); |
| return GST_FLOW_ERROR; |
| } |
| /* catch possibly wrong size differences */ |
| if (GST_BUFFER_SIZE (outbuf) - outsize > r->sample_size) { |
| GST_WARNING_OBJECT (legacyresample, |
| "legacyresample's written outsize %d too far from outbuffer's size %d", |
| outsize, GST_BUFFER_SIZE (outbuf)); |
| } |
| GST_BUFFER_SIZE (outbuf) = outsize; |
| |
| if (G_UNLIKELY (legacyresample->need_discont)) { |
| GST_DEBUG_OBJECT (legacyresample, |
| "marking this buffer with the DISCONT flag"); |
| GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); |
| legacyresample->need_discont = FALSE; |
| } |
| |
| GST_LOG_OBJECT (legacyresample, "transformed to buffer of %d bytes, ts %" |
| GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT ", offset %" |
| G_GINT64_FORMAT ", offset_end %" G_GINT64_FORMAT, |
| outsize, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), |
| GST_BUFFER_OFFSET (outbuf), GST_BUFFER_OFFSET_END (outbuf)); |
| |
| |
| return GST_FLOW_OK; |
| } |
| |
| static gboolean |
| legacyresample_check_discont (GstLegacyresample * legacyresample, |
| GstClockTime timestamp) |
| { |
| if (timestamp != GST_CLOCK_TIME_NONE && |
| legacyresample->prev_ts != GST_CLOCK_TIME_NONE && |
| legacyresample->prev_duration != GST_CLOCK_TIME_NONE && |
| timestamp != legacyresample->prev_ts + legacyresample->prev_duration) { |
| /* Potentially a discontinuous buffer. However, it turns out that many |
| * elements generate imperfect streams due to rounding errors, so we permit |
| * a small error (up to one sample) without triggering a filter |
| * flush/restart (if triggered incorrectly, this will be audible) */ |
| GstClockTimeDiff diff = timestamp - |
| (legacyresample->prev_ts + legacyresample->prev_duration); |
| |
| if (ABS (diff) > GST_SECOND / legacyresample->i_rate) { |
| GST_WARNING_OBJECT (legacyresample, |
| "encountered timestamp discontinuity of %" G_GINT64_FORMAT, diff); |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| static GstFlowReturn |
| legacyresample_transform (GstBaseTransform * base, GstBuffer * inbuf, |
| GstBuffer * outbuf) |
| { |
| GstLegacyresample *legacyresample; |
| ResampleState *r; |
| guchar *data, *datacopy; |
| gulong size; |
| GstClockTime timestamp; |
| |
| legacyresample = GST_LEGACYRESAMPLE (base); |
| r = legacyresample->resample; |
| |
| data = GST_BUFFER_DATA (inbuf); |
| size = GST_BUFFER_SIZE (inbuf); |
| timestamp = GST_BUFFER_TIMESTAMP (inbuf); |
| |
| GST_LOG_OBJECT (legacyresample, "transforming buffer of %ld bytes, ts %" |
| GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT ", offset %" |
| G_GINT64_FORMAT ", offset_end %" G_GINT64_FORMAT, |
| size, GST_TIME_ARGS (timestamp), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (inbuf)), |
| GST_BUFFER_OFFSET (inbuf), GST_BUFFER_OFFSET_END (inbuf)); |
| |
| /* check for timestamp discontinuities and flush/reset if needed */ |
| if (G_UNLIKELY (legacyresample_check_discont (legacyresample, timestamp))) { |
| /* Flush internal samples */ |
| legacyresample_pushthrough (legacyresample); |
| /* Inform downstream element about discontinuity */ |
| legacyresample->need_discont = TRUE; |
| /* We want to recalculate the offset */ |
| legacyresample->ts_offset = -1; |
| } |
| |
| if (legacyresample->ts_offset == -1) { |
| /* if we don't know the initial offset yet, calculate it based on the |
| * input timestamp. */ |
| if (GST_CLOCK_TIME_IS_VALID (timestamp)) { |
| GstClockTime stime; |
| |
| /* offset used to calculate the timestamps. We use the sample offset for |
| * this to make it more accurate. We want the first buffer to have the |
| * same timestamp as the incoming timestamp. */ |
| legacyresample->next_ts = timestamp; |
| legacyresample->ts_offset = |
| gst_util_uint64_scale_int (timestamp, r->o_rate, GST_SECOND); |
| /* offset used to set as the buffer offset, this offset is always |
| * relative to the stream time, note that timestamp is not... */ |
| stime = (timestamp - base->segment.start) + base->segment.time; |
| legacyresample->offset = |
| gst_util_uint64_scale_int (stime, r->o_rate, GST_SECOND); |
| } |
| } |
| legacyresample->prev_ts = timestamp; |
| legacyresample->prev_duration = GST_BUFFER_DURATION (inbuf); |
| |
| /* need to memdup, resample takes ownership. */ |
| datacopy = g_memdup (data, size); |
| resample_add_input_data (r, datacopy, size, g_free, datacopy); |
| |
| return legacyresample_do_output (legacyresample, outbuf); |
| } |
| |
| /* push remaining data in the buffers out */ |
| static GstFlowReturn |
| legacyresample_pushthrough (GstLegacyresample * legacyresample) |
| { |
| int outsize; |
| ResampleState *r; |
| GstBuffer *outbuf; |
| GstFlowReturn res = GST_FLOW_OK; |
| GstBaseTransform *trans; |
| |
| r = legacyresample->resample; |
| |
| outsize = resample_get_output_size (r); |
| if (outsize == 0) { |
| GST_DEBUG_OBJECT (legacyresample, "no internal buffers needing flush"); |
| goto done; |
| } |
| |
| trans = GST_BASE_TRANSFORM (legacyresample); |
| |
| res = gst_pad_alloc_buffer (trans->srcpad, GST_BUFFER_OFFSET_NONE, outsize, |
| GST_PAD_CAPS (trans->srcpad), &outbuf); |
| if (G_UNLIKELY (res != GST_FLOW_OK)) { |
| GST_WARNING_OBJECT (legacyresample, "failed allocating buffer of %d bytes", |
| outsize); |
| goto done; |
| } |
| |
| res = legacyresample_do_output (legacyresample, outbuf); |
| if (G_UNLIKELY (res != GST_FLOW_OK)) |
| goto done; |
| |
| res = gst_pad_push (trans->srcpad, outbuf); |
| |
| done: |
| return res; |
| } |
| |
| static gboolean |
| legacyresample_query (GstPad * pad, GstQuery * query) |
| { |
| GstLegacyresample *legacyresample = |
| GST_LEGACYRESAMPLE (gst_pad_get_parent (pad)); |
| GstBaseTransform *trans = GST_BASE_TRANSFORM (legacyresample); |
| gboolean res = TRUE; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_LATENCY: |
| { |
| GstClockTime min, max; |
| gboolean live; |
| guint64 latency; |
| GstPad *peer; |
| gint rate = legacyresample->i_rate; |
| gint resampler_latency = legacyresample->filter_length / 2; |
| |
| if (gst_base_transform_is_passthrough (trans)) |
| resampler_latency = 0; |
| |
| if ((peer = gst_pad_get_peer (trans->sinkpad))) { |
| if ((res = gst_pad_query (peer, query))) { |
| gst_query_parse_latency (query, &live, &min, &max); |
| |
| GST_DEBUG ("Peer latency: min %" |
| GST_TIME_FORMAT " max %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (min), GST_TIME_ARGS (max)); |
| |
| /* add our own latency */ |
| if (rate != 0 && resampler_latency != 0) |
| latency = |
| gst_util_uint64_scale (resampler_latency, GST_SECOND, rate); |
| else |
| latency = 0; |
| |
| GST_DEBUG ("Our latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (latency)); |
| |
| min += latency; |
| if (max != GST_CLOCK_TIME_NONE) |
| max += latency; |
| |
| GST_DEBUG ("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); |
| } |
| gst_object_unref (peer); |
| } |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, query); |
| break; |
| } |
| gst_object_unref (legacyresample); |
| return res; |
| } |
| |
| static const GstQueryType * |
| legacyresample_query_type (GstPad * pad) |
| { |
| static const GstQueryType types[] = { |
| GST_QUERY_LATENCY, |
| 0 |
| }; |
| |
| return types; |
| } |
| |
| static void |
| gst_legacyresample_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstLegacyresample *legacyresample; |
| |
| legacyresample = GST_LEGACYRESAMPLE (object); |
| |
| switch (prop_id) { |
| case PROP_FILTERLEN: |
| legacyresample->filter_length = g_value_get_int (value); |
| GST_DEBUG_OBJECT (GST_ELEMENT (legacyresample), "new filter length %d", |
| legacyresample->filter_length); |
| if (legacyresample->resample) { |
| resample_set_filter_length (legacyresample->resample, |
| legacyresample->filter_length); |
| gst_element_post_message (GST_ELEMENT (legacyresample), |
| gst_message_new_latency (GST_OBJECT (legacyresample))); |
| } |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_legacyresample_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstLegacyresample *legacyresample; |
| |
| legacyresample = GST_LEGACYRESAMPLE (object); |
| |
| switch (prop_id) { |
| case PROP_FILTERLEN: |
| g_value_set_int (value, legacyresample->filter_length); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| resample_init (); |
| |
| if (!gst_element_register (plugin, "legacyresample", GST_RANK_MARGINAL, |
| GST_TYPE_LEGACYRESAMPLE)) { |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| "legacyresample", |
| "Resamples audio", plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, |
| GST_PACKAGE_ORIGIN); |