mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +00:00
fffc3ba770
If one side has a preference for a particular sample rate or set of sample rates, we should honour this in the caps we advertise and transform to and from, so that elements actually know about the other side's sample rate preference and can negotiate to it if supported. Also add unit test for this.
883 lines
28 KiB
C
883 lines
28 KiB
C
/* 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);
|