1 /* GStreamer
2  * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) 2003,2004 David A. Schleef <ds@schleef.org>
4  * Copyright (C) 2007-2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:element-audioresample
24  * @title: audioresample
25  *
26  * audioresample resamples raw audio buffers to different sample rates using
27  * a configurable windowing function to enhance quality.
28  *
29  * By default, the resampler uses a reduced sinc table, with cubic interpolation filling in
30  * the gaps. This ensures that the table does not become too big. However, the interpolation
31  * increases the CPU usage considerably. As an alternative, a full sinc table can be used.
32  * Doing so can drastically reduce CPU usage (4x faster with 44.1 -> 48 kHz conversions for
33  * example), at the cost of increased memory consumption, plus the sinc table takes longer
34  * to initialize when the element is created. A third mode exists, which uses the full table
35  * unless said table would become too large, in which case the interpolated one is used instead.
36  *
37  * ## Example launch line
38  * |[
39  * gst-launch-1.0 -v uridecodebin uri=file:///path/to/audio.ogg ! audioconvert ! audioresample ! audio/x-raw, rate=8000 ! autoaudiosink
40  * ]|
41  *  Decode an audio file and downsample it to 8Khz and play sound.
42  * To create the Ogg/Vorbis file refer to the documentation of vorbisenc.
43  * This assumes there is an audio sink that will accept/handle 8kHz audio.
44  *
45  */
46 
47 /* TODO:
48  *  - Enable SSE/ARM optimizations and select at runtime
49  */
50 
51 #ifdef HAVE_CONFIG_H
52 #include "config.h"
53 #endif
54 
55 #include <string.h>
56 #include <math.h>
57 
58 #include "gstaudioresample.h"
59 #include <gst/gstutils.h>
60 #include <gst/audio/audio.h>
61 #include <gst/base/gstbasetransform.h>
62 
63 GST_DEBUG_CATEGORY (audio_resample_debug);
64 #define GST_CAT_DEFAULT audio_resample_debug
65 
66 #undef USE_SPEEX
67 
68 #define DEFAULT_QUALITY GST_AUDIO_RESAMPLER_QUALITY_DEFAULT
69 #define DEFAULT_RESAMPLE_METHOD GST_AUDIO_RESAMPLER_METHOD_KAISER
70 #define DEFAULT_SINC_FILTER_MODE GST_AUDIO_RESAMPLER_FILTER_MODE_AUTO
71 #define DEFAULT_SINC_FILTER_AUTO_THRESHOLD (1*1048576)
72 #define DEFAULT_SINC_FILTER_INTERPOLATION GST_AUDIO_RESAMPLER_FILTER_INTERPOLATION_CUBIC
73 
74 enum
75 {
76   PROP_0,
77   PROP_QUALITY,
78   PROP_RESAMPLE_METHOD,
79   PROP_SINC_FILTER_MODE,
80   PROP_SINC_FILTER_AUTO_THRESHOLD,
81   PROP_SINC_FILTER_INTERPOLATION
82 };
83 
84 #define SUPPORTED_CAPS \
85   GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL) \
86   ", layout = (string) { interleaved, non-interleaved }"
87 
88 static GstStaticPadTemplate gst_audio_resample_sink_template =
89 GST_STATIC_PAD_TEMPLATE ("sink",
90     GST_PAD_SINK,
91     GST_PAD_ALWAYS,
92     GST_STATIC_CAPS (SUPPORTED_CAPS));
93 
94 static GstStaticPadTemplate gst_audio_resample_src_template =
95 GST_STATIC_PAD_TEMPLATE ("src",
96     GST_PAD_SRC,
97     GST_PAD_ALWAYS,
98     GST_STATIC_CAPS (SUPPORTED_CAPS));
99 
100 static void gst_audio_resample_set_property (GObject * object,
101     guint prop_id, const GValue * value, GParamSpec * pspec);
102 static void gst_audio_resample_get_property (GObject * object,
103     guint prop_id, GValue * value, GParamSpec * pspec);
104 
105 /* vmethods */
106 static gboolean gst_audio_resample_get_unit_size (GstBaseTransform * base,
107     GstCaps * caps, gsize * size);
108 static GstCaps *gst_audio_resample_transform_caps (GstBaseTransform * base,
109     GstPadDirection direction, GstCaps * caps, GstCaps * filter);
110 static GstCaps *gst_audio_resample_fixate_caps (GstBaseTransform * base,
111     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
112 static gboolean gst_audio_resample_transform_size (GstBaseTransform * trans,
113     GstPadDirection direction, GstCaps * incaps, gsize insize,
114     GstCaps * outcaps, gsize * outsize);
115 static gboolean gst_audio_resample_set_caps (GstBaseTransform * base,
116     GstCaps * incaps, GstCaps * outcaps);
117 static GstFlowReturn gst_audio_resample_transform (GstBaseTransform * base,
118     GstBuffer * inbuf, GstBuffer * outbuf);
119 static gboolean gst_audio_resample_transform_meta (GstBaseTransform * trans,
120     GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf);
121 static GstFlowReturn gst_audio_resample_submit_input_buffer (GstBaseTransform *
122     base, gboolean is_discont, GstBuffer * input);
123 static gboolean gst_audio_resample_sink_event (GstBaseTransform * base,
124     GstEvent * event);
125 static gboolean gst_audio_resample_start (GstBaseTransform * base);
126 static gboolean gst_audio_resample_stop (GstBaseTransform * base);
127 static gboolean gst_audio_resample_query (GstPad * pad, GstObject * parent,
128     GstQuery * query);
129 
130 #define gst_audio_resample_parent_class parent_class
131 G_DEFINE_TYPE (GstAudioResample, gst_audio_resample, GST_TYPE_BASE_TRANSFORM);
132 
133 static void
gst_audio_resample_class_init(GstAudioResampleClass * klass)134 gst_audio_resample_class_init (GstAudioResampleClass * klass)
135 {
136   GObjectClass *gobject_class = (GObjectClass *) klass;
137   GstElementClass *gstelement_class = (GstElementClass *) klass;
138 
139   gobject_class->set_property = gst_audio_resample_set_property;
140   gobject_class->get_property = gst_audio_resample_get_property;
141 
142   g_object_class_install_property (gobject_class, PROP_QUALITY,
143       g_param_spec_int ("quality", "Quality", "Resample quality with 0 being "
144           "the lowest and 10 being the best",
145           GST_AUDIO_RESAMPLER_QUALITY_MIN, GST_AUDIO_RESAMPLER_QUALITY_MAX,
146           DEFAULT_QUALITY,
147           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
148 
149   g_object_class_install_property (gobject_class, PROP_RESAMPLE_METHOD,
150       g_param_spec_enum ("resample-method", "Resample method to use",
151           "What resample method to use",
152           GST_TYPE_AUDIO_RESAMPLER_METHOD,
153           DEFAULT_RESAMPLE_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154   g_object_class_install_property (gobject_class, PROP_SINC_FILTER_MODE,
155       g_param_spec_enum ("sinc-filter-mode", "Sinc filter table mode",
156           "What sinc filter table mode to use",
157           GST_TYPE_AUDIO_RESAMPLER_FILTER_MODE,
158           DEFAULT_SINC_FILTER_MODE,
159           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
160 
161   g_object_class_install_property (gobject_class,
162       PROP_SINC_FILTER_AUTO_THRESHOLD,
163       g_param_spec_uint ("sinc-filter-auto-threshold",
164           "Sinc filter auto mode threshold",
165           "Memory usage threshold to use if sinc filter mode is AUTO, given in bytes",
166           0, G_MAXUINT, DEFAULT_SINC_FILTER_AUTO_THRESHOLD,
167           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
168   g_object_class_install_property (gobject_class,
169       PROP_SINC_FILTER_INTERPOLATION,
170       g_param_spec_enum ("sinc-filter-interpolation",
171           "Sinc filter interpolation",
172           "How to interpolate the sinc filter table",
173           GST_TYPE_AUDIO_RESAMPLER_FILTER_INTERPOLATION,
174           DEFAULT_SINC_FILTER_INTERPOLATION,
175           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
176 
177   gst_element_class_add_static_pad_template (gstelement_class,
178       &gst_audio_resample_src_template);
179   gst_element_class_add_static_pad_template (gstelement_class,
180       &gst_audio_resample_sink_template);
181 
182   gst_element_class_set_static_metadata (gstelement_class, "Audio resampler",
183       "Filter/Converter/Audio", "Resamples audio",
184       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
185 
186   GST_BASE_TRANSFORM_CLASS (klass)->start =
187       GST_DEBUG_FUNCPTR (gst_audio_resample_start);
188   GST_BASE_TRANSFORM_CLASS (klass)->stop =
189       GST_DEBUG_FUNCPTR (gst_audio_resample_stop);
190   GST_BASE_TRANSFORM_CLASS (klass)->transform_size =
191       GST_DEBUG_FUNCPTR (gst_audio_resample_transform_size);
192   GST_BASE_TRANSFORM_CLASS (klass)->get_unit_size =
193       GST_DEBUG_FUNCPTR (gst_audio_resample_get_unit_size);
194   GST_BASE_TRANSFORM_CLASS (klass)->transform_caps =
195       GST_DEBUG_FUNCPTR (gst_audio_resample_transform_caps);
196   GST_BASE_TRANSFORM_CLASS (klass)->fixate_caps =
197       GST_DEBUG_FUNCPTR (gst_audio_resample_fixate_caps);
198   GST_BASE_TRANSFORM_CLASS (klass)->set_caps =
199       GST_DEBUG_FUNCPTR (gst_audio_resample_set_caps);
200   GST_BASE_TRANSFORM_CLASS (klass)->transform =
201       GST_DEBUG_FUNCPTR (gst_audio_resample_transform);
202   GST_BASE_TRANSFORM_CLASS (klass)->sink_event =
203       GST_DEBUG_FUNCPTR (gst_audio_resample_sink_event);
204   GST_BASE_TRANSFORM_CLASS (klass)->transform_meta =
205       GST_DEBUG_FUNCPTR (gst_audio_resample_transform_meta);
206   GST_BASE_TRANSFORM_CLASS (klass)->submit_input_buffer =
207       GST_DEBUG_FUNCPTR (gst_audio_resample_submit_input_buffer);
208 
209   GST_BASE_TRANSFORM_CLASS (klass)->passthrough_on_same_caps = TRUE;
210 }
211 
212 static void
gst_audio_resample_init(GstAudioResample * resample)213 gst_audio_resample_init (GstAudioResample * resample)
214 {
215   GstBaseTransform *trans = GST_BASE_TRANSFORM (resample);
216 
217   resample->method = DEFAULT_RESAMPLE_METHOD;
218   resample->quality = DEFAULT_QUALITY;
219   resample->sinc_filter_mode = DEFAULT_SINC_FILTER_MODE;
220   resample->sinc_filter_auto_threshold = DEFAULT_SINC_FILTER_AUTO_THRESHOLD;
221   resample->sinc_filter_interpolation = DEFAULT_SINC_FILTER_INTERPOLATION;
222 
223   gst_base_transform_set_gap_aware (trans, TRUE);
224   gst_pad_set_query_function (trans->srcpad, gst_audio_resample_query);
225 }
226 
227 /* vmethods */
228 static gboolean
gst_audio_resample_start(GstBaseTransform * base)229 gst_audio_resample_start (GstBaseTransform * base)
230 {
231   GstAudioResample *resample = GST_AUDIO_RESAMPLE (base);
232 
233   resample->need_discont = TRUE;
234 
235   resample->num_gap_samples = 0;
236   resample->num_nongap_samples = 0;
237   resample->t0 = GST_CLOCK_TIME_NONE;
238   resample->in_offset0 = GST_BUFFER_OFFSET_NONE;
239   resample->out_offset0 = GST_BUFFER_OFFSET_NONE;
240   resample->samples_in = 0;
241   resample->samples_out = 0;
242 
243   return TRUE;
244 }
245 
246 static gboolean
gst_audio_resample_stop(GstBaseTransform * base)247 gst_audio_resample_stop (GstBaseTransform * base)
248 {
249   GstAudioResample *resample = GST_AUDIO_RESAMPLE (base);
250 
251   if (resample->converter) {
252     gst_audio_converter_free (resample->converter);
253     resample->converter = NULL;
254   }
255   return TRUE;
256 }
257 
258 static gboolean
gst_audio_resample_get_unit_size(GstBaseTransform * base,GstCaps * caps,gsize * size)259 gst_audio_resample_get_unit_size (GstBaseTransform * base, GstCaps * caps,
260     gsize * size)
261 {
262   GstAudioInfo info;
263 
264   if (!gst_audio_info_from_caps (&info, caps))
265     goto invalid_caps;
266 
267   *size = GST_AUDIO_INFO_BPF (&info);
268 
269   return TRUE;
270 
271   /* ERRORS */
272 invalid_caps:
273   {
274     GST_ERROR_OBJECT (base, "invalid caps");
275     return FALSE;
276   }
277 }
278 
279 static GstCaps *
gst_audio_resample_transform_caps(GstBaseTransform * base,GstPadDirection direction,GstCaps * caps,GstCaps * filter)280 gst_audio_resample_transform_caps (GstBaseTransform * base,
281     GstPadDirection direction, GstCaps * caps, GstCaps * filter)
282 {
283   const GValue *val;
284   GstStructure *s;
285   GstCaps *res;
286   gint i, n;
287 
288   /* transform single caps into input_caps + input_caps with the rate
289    * field set to our supported range. This ensures that upstream knows
290    * about downstream's prefered rate(s) and can negotiate accordingly. */
291   res = gst_caps_new_empty ();
292   n = gst_caps_get_size (caps);
293   for (i = 0; i < n; i++) {
294     s = gst_caps_get_structure (caps, i);
295 
296     /* If this is already expressed by the existing caps
297      * skip this structure */
298     if (i > 0 && gst_caps_is_subset_structure (res, s))
299       continue;
300 
301     /* first, however, check if the caps contain a range for the rate field, in
302      * which case that side isn't going to care much about the exact sample rate
303      * chosen and we should just assume things will get fixated to something sane
304      * and we may just as well offer our full range instead of the range in the
305      * caps. If the rate is not an int range value, it's likely to express a
306      * real preference or limitation and we should maintain that structure as
307      * preference by putting it first into the transformed caps, and only add
308      * our full rate range as second option  */
309     s = gst_structure_copy (s);
310     val = gst_structure_get_value (s, "rate");
311     if (val == NULL || GST_VALUE_HOLDS_INT_RANGE (val)) {
312       /* overwrite existing range, or add field if it doesn't exist yet */
313       gst_structure_set (s, "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
314     } else {
315       /* append caps with full range to existing caps with non-range rate field */
316       gst_caps_append_structure (res, gst_structure_copy (s));
317       gst_structure_set (s, "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
318     }
319     gst_caps_append_structure (res, s);
320   }
321 
322   if (filter) {
323     GstCaps *intersection;
324 
325     intersection =
326         gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
327     gst_caps_unref (res);
328     res = intersection;
329   }
330 
331   return res;
332 }
333 
334 /* Fixate rate to the allowed rate that has the smallest difference */
335 static GstCaps *
gst_audio_resample_fixate_caps(GstBaseTransform * base,GstPadDirection direction,GstCaps * caps,GstCaps * othercaps)336 gst_audio_resample_fixate_caps (GstBaseTransform * base,
337     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
338 {
339   GstStructure *s;
340   gint rate;
341 
342   s = gst_caps_get_structure (caps, 0);
343   if (G_UNLIKELY (!gst_structure_get_int (s, "rate", &rate)))
344     return othercaps;
345 
346   othercaps = gst_caps_truncate (othercaps);
347   othercaps = gst_caps_make_writable (othercaps);
348   s = gst_caps_get_structure (othercaps, 0);
349   gst_structure_fixate_field_nearest_int (s, "rate", rate);
350 
351   return othercaps;
352 }
353 
354 static GstStructure *
make_options(GstAudioResample * resample,GstAudioInfo * in,GstAudioInfo * out)355 make_options (GstAudioResample * resample, GstAudioInfo * in,
356     GstAudioInfo * out)
357 {
358   GstStructure *options;
359 
360   options = gst_structure_new_empty ("resampler-options");
361   if (in != NULL && out != NULL)
362     gst_audio_resampler_options_set_quality (resample->method,
363         resample->quality, in->rate, out->rate, options);
364 
365   gst_structure_set (options,
366       GST_AUDIO_CONVERTER_OPT_RESAMPLER_METHOD, GST_TYPE_AUDIO_RESAMPLER_METHOD,
367       resample->method,
368       GST_AUDIO_RESAMPLER_OPT_FILTER_MODE, GST_TYPE_AUDIO_RESAMPLER_FILTER_MODE,
369       resample->sinc_filter_mode, GST_AUDIO_RESAMPLER_OPT_FILTER_MODE_THRESHOLD,
370       G_TYPE_UINT, resample->sinc_filter_auto_threshold,
371       GST_AUDIO_RESAMPLER_OPT_FILTER_INTERPOLATION,
372       GST_TYPE_AUDIO_RESAMPLER_FILTER_INTERPOLATION,
373       resample->sinc_filter_interpolation, NULL);
374 
375   return options;
376 }
377 
378 static gboolean
gst_audio_resample_update_state(GstAudioResample * resample,GstAudioInfo * in,GstAudioInfo * out)379 gst_audio_resample_update_state (GstAudioResample * resample, GstAudioInfo * in,
380     GstAudioInfo * out)
381 {
382   gboolean updated_latency = FALSE;
383   gsize old_latency = -1;
384   GstStructure *options;
385 
386   if (resample->converter == NULL && in == NULL && out == NULL)
387     return TRUE;
388 
389   options = make_options (resample, in, out);
390 
391   if (resample->converter)
392     old_latency = gst_audio_converter_get_max_latency (resample->converter);
393 
394   /* if channels and layout changed, destroy existing resampler */
395   if (in != NULL && (in->finfo != resample->in.finfo ||
396           in->channels != resample->in.channels ||
397           in->layout != resample->in.layout) && resample->converter) {
398     gst_audio_converter_free (resample->converter);
399     resample->converter = NULL;
400   }
401   if (resample->converter == NULL) {
402     resample->converter =
403         gst_audio_converter_new (GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE, in,
404         out, options);
405     if (resample->converter == NULL)
406       goto resampler_failed;
407   } else if (in && out) {
408     gboolean ret;
409 
410     ret =
411         gst_audio_converter_update_config (resample->converter, in->rate,
412         out->rate, options);
413     if (!ret)
414       goto update_failed;
415   } else {
416     gst_structure_free (options);
417   }
418   if (old_latency != -1)
419     updated_latency =
420         old_latency !=
421         gst_audio_converter_get_max_latency (resample->converter);
422 
423   if (updated_latency)
424     gst_element_post_message (GST_ELEMENT (resample),
425         gst_message_new_latency (GST_OBJECT (resample)));
426 
427   return TRUE;
428 
429   /* ERRORS */
430 resampler_failed:
431   {
432     GST_ERROR_OBJECT (resample, "failed to create resampler");
433     return FALSE;
434   }
435 update_failed:
436   {
437     GST_ERROR_OBJECT (resample, "failed to update resampler");
438     return FALSE;
439   }
440 }
441 
442 static void
gst_audio_resample_reset_state(GstAudioResample * resample)443 gst_audio_resample_reset_state (GstAudioResample * resample)
444 {
445   if (resample->converter)
446     gst_audio_converter_reset (resample->converter);
447 }
448 
449 static gboolean
gst_audio_resample_transform_size(GstBaseTransform * base,GstPadDirection direction,GstCaps * caps,gsize size,GstCaps * othercaps,gsize * othersize)450 gst_audio_resample_transform_size (GstBaseTransform * base,
451     GstPadDirection direction, GstCaps * caps, gsize size, GstCaps * othercaps,
452     gsize * othersize)
453 {
454   GstAudioResample *resample = GST_AUDIO_RESAMPLE (base);
455   gboolean ret = TRUE;
456   gint bpf;
457 
458   GST_LOG_OBJECT (base, "asked to transform size %" G_GSIZE_FORMAT
459       " in direction %s", size, direction == GST_PAD_SINK ? "SINK" : "SRC");
460 
461   /* Number of samples in either buffer is size / (width*channels) ->
462    * calculate the factor */
463   bpf = GST_AUDIO_INFO_BPF (&resample->in);
464 
465   /* Convert source buffer size to samples */
466   size /= bpf;
467 
468   if (direction == GST_PAD_SINK) {
469     /* asked to convert size of an incoming buffer */
470     *othersize = gst_audio_converter_get_out_frames (resample->converter, size);
471     *othersize *= bpf;
472   } else {
473     /* asked to convert size of an outgoing buffer */
474     *othersize = gst_audio_converter_get_in_frames (resample->converter, size);
475     *othersize *= bpf;
476   }
477 
478   GST_LOG_OBJECT (base,
479       "transformed size %" G_GSIZE_FORMAT " to %" G_GSIZE_FORMAT,
480       size * bpf, *othersize);
481 
482   return ret;
483 }
484 
485 static gboolean
gst_audio_resample_set_caps(GstBaseTransform * base,GstCaps * incaps,GstCaps * outcaps)486 gst_audio_resample_set_caps (GstBaseTransform * base, GstCaps * incaps,
487     GstCaps * outcaps)
488 {
489   GstAudioResample *resample = GST_AUDIO_RESAMPLE (base);
490   GstAudioInfo in, out;
491 
492   GST_LOG ("incaps %" GST_PTR_FORMAT ", outcaps %"
493       GST_PTR_FORMAT, incaps, outcaps);
494 
495   if (!gst_audio_info_from_caps (&in, incaps))
496     goto invalid_incaps;
497   if (!gst_audio_info_from_caps (&out, outcaps))
498     goto invalid_outcaps;
499 
500   /* FIXME do some checks */
501   gst_audio_resample_update_state (resample, &in, &out);
502 
503   resample->in = in;
504   resample->out = out;
505 
506   return TRUE;
507 
508   /* ERROR */
509 invalid_incaps:
510   {
511     GST_ERROR_OBJECT (base, "invalid incaps");
512     return FALSE;
513   }
514 invalid_outcaps:
515   {
516     GST_ERROR_OBJECT (base, "invalid outcaps");
517     return FALSE;
518   }
519 }
520 
521 /* Push history_len zeros into the filter, but discard the output. */
522 static void
gst_audio_resample_dump_drain(GstAudioResample * resample,guint history_len)523 gst_audio_resample_dump_drain (GstAudioResample * resample, guint history_len)
524 {
525   gsize out_len, outsize;
526   gpointer out[1];
527 
528   out_len =
529       gst_audio_converter_get_out_frames (resample->converter, history_len);
530   if (out_len == 0)
531     return;
532 
533   outsize = out_len * resample->out.bpf;
534 
535   out[0] = g_malloc (outsize);
536   gst_audio_converter_samples (resample->converter, 0, NULL, history_len,
537       out, out_len);
538   g_free (out[0]);
539 }
540 
541 static void
gst_audio_resample_push_drain(GstAudioResample * resample,guint history_len)542 gst_audio_resample_push_drain (GstAudioResample * resample, guint history_len)
543 {
544   GstBuffer *outbuf;
545   GstFlowReturn res;
546   gint outsize;
547   gsize out_len;
548   GstAudioBuffer abuf;
549 
550   g_assert (resample->converter != NULL);
551 
552   /* Don't drain samples if we were reset. */
553   if (!GST_CLOCK_TIME_IS_VALID (resample->t0))
554     return;
555 
556   out_len =
557       gst_audio_converter_get_out_frames (resample->converter, history_len);
558   if (out_len == 0)
559     return;
560 
561   outsize = out_len * resample->in.bpf;
562   outbuf = gst_buffer_new_and_alloc (outsize);
563 
564   if (GST_AUDIO_INFO_LAYOUT (&resample->out) ==
565       GST_AUDIO_LAYOUT_NON_INTERLEAVED) {
566     gst_buffer_add_audio_meta (outbuf, &resample->out, out_len, NULL);
567   }
568 
569   gst_audio_buffer_map (&abuf, &resample->out, outbuf, GST_MAP_WRITE);
570   gst_audio_converter_samples (resample->converter, 0, NULL, history_len,
571       abuf.planes, out_len);
572   gst_audio_buffer_unmap (&abuf);
573 
574   /* time */
575   if (GST_CLOCK_TIME_IS_VALID (resample->t0)) {
576     GST_BUFFER_TIMESTAMP (outbuf) = resample->t0 +
577         gst_util_uint64_scale_int_round (resample->samples_out, GST_SECOND,
578         resample->out.rate);
579     GST_BUFFER_DURATION (outbuf) = resample->t0 +
580         gst_util_uint64_scale_int_round (resample->samples_out + out_len,
581         GST_SECOND, resample->out.rate) - GST_BUFFER_TIMESTAMP (outbuf);
582   } else {
583     GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE;
584     GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
585   }
586   /* offset */
587   if (resample->out_offset0 != GST_BUFFER_OFFSET_NONE) {
588     GST_BUFFER_OFFSET (outbuf) = resample->out_offset0 + resample->samples_out;
589     GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + out_len;
590   } else {
591     GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET_NONE;
592     GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET_NONE;
593   }
594   /* move along */
595   resample->samples_out += out_len;
596   resample->samples_in += history_len;
597 
598   GST_LOG_OBJECT (resample,
599       "Pushing drain buffer of %u bytes with timestamp %" GST_TIME_FORMAT
600       " duration %" GST_TIME_FORMAT " offset %" G_GUINT64_FORMAT " offset_end %"
601       G_GUINT64_FORMAT, outsize,
602       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
603       GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), GST_BUFFER_OFFSET (outbuf),
604       GST_BUFFER_OFFSET_END (outbuf));
605 
606   res = gst_pad_push (GST_BASE_TRANSFORM_SRC_PAD (resample), outbuf);
607 
608   if (G_UNLIKELY (res != GST_FLOW_OK))
609     GST_WARNING_OBJECT (resample, "Failed to push drain: %s",
610         gst_flow_get_name (res));
611 
612   return;
613 }
614 
615 static gboolean
gst_audio_resample_sink_event(GstBaseTransform * base,GstEvent * event)616 gst_audio_resample_sink_event (GstBaseTransform * base, GstEvent * event)
617 {
618   GstAudioResample *resample = GST_AUDIO_RESAMPLE (base);
619 
620   switch (GST_EVENT_TYPE (event)) {
621     case GST_EVENT_FLUSH_STOP:
622       gst_audio_resample_reset_state (resample);
623       resample->num_gap_samples = 0;
624       resample->num_nongap_samples = 0;
625       resample->t0 = GST_CLOCK_TIME_NONE;
626       resample->in_offset0 = GST_BUFFER_OFFSET_NONE;
627       resample->out_offset0 = GST_BUFFER_OFFSET_NONE;
628       resample->samples_in = 0;
629       resample->samples_out = 0;
630       resample->need_discont = TRUE;
631       break;
632     case GST_EVENT_SEGMENT:
633       if (resample->converter) {
634         gsize latency =
635             gst_audio_converter_get_max_latency (resample->converter);
636         gst_audio_resample_push_drain (resample, latency);
637       }
638       gst_audio_resample_reset_state (resample);
639       resample->num_gap_samples = 0;
640       resample->num_nongap_samples = 0;
641       resample->t0 = GST_CLOCK_TIME_NONE;
642       resample->in_offset0 = GST_BUFFER_OFFSET_NONE;
643       resample->out_offset0 = GST_BUFFER_OFFSET_NONE;
644       resample->samples_in = 0;
645       resample->samples_out = 0;
646       resample->need_discont = TRUE;
647       break;
648     case GST_EVENT_EOS:
649       if (resample->converter) {
650         gsize latency =
651             gst_audio_converter_get_max_latency (resample->converter);
652         gst_audio_resample_push_drain (resample, latency);
653       }
654       gst_audio_resample_reset_state (resample);
655       break;
656     default:
657       break;
658   }
659 
660   return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (base, event);
661 }
662 
663 static gboolean
gst_audio_resample_check_discont(GstAudioResample * resample,GstBuffer * buf)664 gst_audio_resample_check_discont (GstAudioResample * resample, GstBuffer * buf)
665 {
666   guint64 offset;
667   guint64 delta;
668 
669   /* is the incoming buffer a discontinuity? */
670   if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buf)))
671     return TRUE;
672 
673   /* no valid timestamps or offsets to compare --> no discontinuity */
674   if (G_UNLIKELY (!(GST_BUFFER_TIMESTAMP_IS_VALID (buf) &&
675               GST_CLOCK_TIME_IS_VALID (resample->t0))))
676     return FALSE;
677 
678   /* convert the inbound timestamp to an offset. */
679   offset =
680       gst_util_uint64_scale_int_round (GST_BUFFER_TIMESTAMP (buf) -
681       resample->t0, resample->in.rate, GST_SECOND);
682 
683   /* many elements generate imperfect streams due to rounding errors, so we
684    * permit a small error (up to one sample) without triggering a filter
685    * flush/restart (if triggered incorrectly, this will be audible) */
686   /* allow even up to more samples, since sink is not so strict anyway,
687    * so give that one a chance to handle this as configured */
688   delta = ABS ((gint64) (offset - resample->samples_in));
689   if (delta <= (resample->in.rate >> 5))
690     return FALSE;
691 
692   GST_WARNING_OBJECT (resample,
693       "encountered timestamp discontinuity of %" G_GUINT64_FORMAT " samples = %"
694       GST_TIME_FORMAT, delta,
695       GST_TIME_ARGS (gst_util_uint64_scale_int_round (delta, GST_SECOND,
696               resample->in.rate)));
697   return TRUE;
698 }
699 
700 static GstFlowReturn
gst_audio_resample_process(GstAudioResample * resample,GstBuffer * inbuf,GstBuffer * outbuf)701 gst_audio_resample_process (GstAudioResample * resample, GstBuffer * inbuf,
702     GstBuffer * outbuf)
703 {
704   GstAudioBuffer srcabuf, dstabuf;
705   gsize outsize;
706   gsize in_len;
707   gsize out_len;
708   guint filt_len =
709       gst_audio_converter_get_max_latency (resample->converter) * 2;
710   gboolean inbuf_writable;
711 
712   inbuf_writable = gst_buffer_is_writable (inbuf)
713       && gst_buffer_n_memory (inbuf) == 1
714       && gst_memory_is_writable (gst_buffer_peek_memory (inbuf, 0));
715 
716   gst_audio_buffer_map (&srcabuf, &resample->in, inbuf,
717       inbuf_writable ? GST_MAP_READWRITE : GST_MAP_READ);
718 
719   in_len = srcabuf.n_samples;
720   out_len = gst_audio_converter_get_out_frames (resample->converter, in_len);
721 
722   /* ensure that the output buffer is not bigger than what we need */
723   gst_buffer_set_size (outbuf, out_len * resample->in.bpf);
724 
725   if (GST_AUDIO_INFO_LAYOUT (&resample->out) ==
726       GST_AUDIO_LAYOUT_NON_INTERLEAVED) {
727     gst_buffer_add_audio_meta (outbuf, &resample->out, out_len, NULL);
728   }
729 
730   gst_audio_buffer_map (&dstabuf, &resample->out, outbuf, GST_MAP_WRITE);
731 
732   if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP)) {
733     resample->num_nongap_samples = 0;
734     if (resample->num_gap_samples < filt_len) {
735       guint zeros_to_push;
736       if (in_len >= filt_len - resample->num_gap_samples)
737         zeros_to_push = filt_len - resample->num_gap_samples;
738       else
739         zeros_to_push = in_len;
740 
741       gst_audio_resample_push_drain (resample, zeros_to_push);
742       in_len -= zeros_to_push;
743       resample->num_gap_samples += zeros_to_push;
744     }
745 
746     {
747       guint num, den;
748       gint i;
749 
750       num = resample->in.rate;
751       den = resample->out.rate;
752 
753       if (resample->samples_in + in_len >= filt_len / 2)
754         out_len =
755             gst_util_uint64_scale_int_ceil (resample->samples_in + in_len -
756             filt_len / 2, den, num) - resample->samples_out;
757       else
758         out_len = 0;
759 
760       for (i = 0; i < dstabuf.n_planes; i++)
761         memset (dstabuf.planes[i], 0, GST_AUDIO_BUFFER_PLANE_SIZE (&dstabuf));
762 
763       GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP);
764       resample->num_gap_samples += in_len;
765     }
766   } else {                      /* not a gap */
767     if (resample->num_gap_samples > filt_len) {
768       /* push in enough zeros to restore the filter to the right offset */
769       guint num;
770 
771       num = resample->in.rate;
772 
773       gst_audio_resample_dump_drain (resample,
774           (resample->num_gap_samples - filt_len) % num);
775     }
776     resample->num_gap_samples = 0;
777     if (resample->num_nongap_samples < filt_len) {
778       resample->num_nongap_samples += in_len;
779       if (resample->num_nongap_samples > filt_len)
780         resample->num_nongap_samples = filt_len;
781     }
782     {
783       /* process */
784       GstAudioConverterFlags flags;
785 
786       flags = 0;
787       if (inbuf_writable)
788         flags |= GST_AUDIO_CONVERTER_FLAG_IN_WRITABLE;
789 
790       gst_audio_converter_samples (resample->converter, flags, srcabuf.planes,
791           in_len, dstabuf.planes, out_len);
792     }
793   }
794 
795   /* time */
796   if (GST_CLOCK_TIME_IS_VALID (resample->t0)) {
797     GST_BUFFER_TIMESTAMP (outbuf) = resample->t0 +
798         gst_util_uint64_scale_int_round (resample->samples_out, GST_SECOND,
799         resample->out.rate);
800     GST_BUFFER_DURATION (outbuf) = resample->t0 +
801         gst_util_uint64_scale_int_round (resample->samples_out + out_len,
802         GST_SECOND, resample->out.rate) - GST_BUFFER_TIMESTAMP (outbuf);
803   } else {
804     GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE;
805     GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
806   }
807   /* offset */
808   if (resample->out_offset0 != GST_BUFFER_OFFSET_NONE) {
809     GST_BUFFER_OFFSET (outbuf) = resample->out_offset0 + resample->samples_out;
810     GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + out_len;
811   } else {
812     GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET_NONE;
813     GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET_NONE;
814   }
815   /* move along */
816   resample->samples_out += out_len;
817   resample->samples_in += in_len;
818 
819   gst_audio_buffer_unmap (&srcabuf);
820   gst_audio_buffer_unmap (&dstabuf);
821 
822   outsize = out_len * resample->in.bpf;
823 
824   GST_LOG_OBJECT (resample,
825       "Converted to buffer of %" G_GSIZE_FORMAT
826       " samples (%" G_GSIZE_FORMAT " bytes) with timestamp %" GST_TIME_FORMAT
827       ", duration %" GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT
828       ", offset_end %" G_GUINT64_FORMAT, out_len, outsize,
829       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
830       GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)),
831       GST_BUFFER_OFFSET (outbuf), GST_BUFFER_OFFSET_END (outbuf));
832 
833   if (outsize == 0)
834     return GST_BASE_TRANSFORM_FLOW_DROPPED;
835   else
836     return GST_FLOW_OK;
837 }
838 
839 static GstFlowReturn
gst_audio_resample_transform(GstBaseTransform * base,GstBuffer * inbuf,GstBuffer * outbuf)840 gst_audio_resample_transform (GstBaseTransform * base, GstBuffer * inbuf,
841     GstBuffer * outbuf)
842 {
843   GstAudioResample *resample = GST_AUDIO_RESAMPLE (base);
844   GstFlowReturn ret;
845 
846   GST_LOG_OBJECT (resample, "transforming buffer of %" G_GSIZE_FORMAT " bytes,"
847       " ts %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT ", offset %"
848       G_GINT64_FORMAT ", offset_end %" G_GINT64_FORMAT,
849       gst_buffer_get_size (inbuf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)),
850       GST_TIME_ARGS (GST_BUFFER_DURATION (inbuf)),
851       GST_BUFFER_OFFSET (inbuf), GST_BUFFER_OFFSET_END (inbuf));
852 
853   /* check for timestamp discontinuities;  flush/reset if needed, and set
854    * flag to resync timestamp and offset counters and send event
855    * downstream */
856   if (G_UNLIKELY (gst_audio_resample_check_discont (resample, inbuf))) {
857     gst_audio_resample_reset_state (resample);
858     resample->need_discont = TRUE;
859   }
860 
861   /* handle discontinuity */
862   if (G_UNLIKELY (resample->need_discont)) {
863     resample->num_gap_samples = 0;
864     resample->num_nongap_samples = 0;
865     /* reset */
866     resample->samples_in = 0;
867     resample->samples_out = 0;
868     GST_DEBUG_OBJECT (resample, "found discontinuity; resyncing");
869     /* resync the timestamp and offset counters if possible */
870     if (GST_BUFFER_TIMESTAMP_IS_VALID (inbuf)) {
871       resample->t0 = GST_BUFFER_TIMESTAMP (inbuf);
872     } else {
873       GST_DEBUG_OBJECT (resample, "... but new timestamp is invalid");
874       resample->t0 = GST_CLOCK_TIME_NONE;
875     }
876     if (GST_BUFFER_OFFSET_IS_VALID (inbuf)) {
877       resample->in_offset0 = GST_BUFFER_OFFSET (inbuf);
878       resample->out_offset0 =
879           gst_util_uint64_scale_int_round (resample->in_offset0,
880           resample->out.rate, resample->in.rate);
881     } else {
882       GST_DEBUG_OBJECT (resample, "... but new offset is invalid");
883       resample->in_offset0 = GST_BUFFER_OFFSET_NONE;
884       resample->out_offset0 = GST_BUFFER_OFFSET_NONE;
885     }
886     /* set DISCONT flag on output buffer */
887     GST_DEBUG_OBJECT (resample, "marking this buffer with the DISCONT flag");
888     GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
889     resample->need_discont = FALSE;
890   }
891 
892   ret = gst_audio_resample_process (resample, inbuf, outbuf);
893   if (G_UNLIKELY (ret != GST_FLOW_OK))
894     return ret;
895 
896   GST_DEBUG_OBJECT (resample, "input = samples [%" G_GUINT64_FORMAT ", %"
897       G_GUINT64_FORMAT ") = [%" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT
898       ") ns;  output = samples [%" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT
899       ") = [%" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT ") ns",
900       GST_BUFFER_OFFSET (inbuf), GST_BUFFER_OFFSET_END (inbuf),
901       GST_BUFFER_TIMESTAMP (inbuf), GST_BUFFER_TIMESTAMP (inbuf) +
902       GST_BUFFER_DURATION (inbuf), GST_BUFFER_OFFSET (outbuf),
903       GST_BUFFER_OFFSET_END (outbuf), GST_BUFFER_TIMESTAMP (outbuf),
904       GST_BUFFER_TIMESTAMP (outbuf) + GST_BUFFER_DURATION (outbuf));
905 
906   return GST_FLOW_OK;
907 }
908 
909 static gboolean
gst_audio_resample_transform_meta(GstBaseTransform * trans,GstBuffer * outbuf,GstMeta * meta,GstBuffer * inbuf)910 gst_audio_resample_transform_meta (GstBaseTransform * trans, GstBuffer * outbuf,
911     GstMeta * meta, GstBuffer * inbuf)
912 {
913   const GstMetaInfo *info = meta->info;
914   const gchar *const *tags;
915 
916   tags = gst_meta_api_type_get_tags (info->api);
917 
918   if (!tags || (g_strv_length ((gchar **) tags) == 1
919           && gst_meta_api_type_has_tag (info->api,
920               g_quark_from_string (GST_META_TAG_AUDIO_STR))))
921     return TRUE;
922 
923   return FALSE;
924 }
925 
926 static GstFlowReturn
gst_audio_resample_submit_input_buffer(GstBaseTransform * base,gboolean is_discont,GstBuffer * input)927 gst_audio_resample_submit_input_buffer (GstBaseTransform * base,
928     gboolean is_discont, GstBuffer * input)
929 {
930   GstAudioResample *resample = GST_AUDIO_RESAMPLE (base);
931 
932   if (base->segment.format == GST_FORMAT_TIME) {
933     input =
934         gst_audio_buffer_clip (input, &base->segment, resample->in.rate,
935         resample->in.bpf);
936 
937     if (!input)
938       return GST_FLOW_OK;
939   }
940 
941   return GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer (base,
942       is_discont, input);
943 }
944 
945 static gboolean
gst_audio_resample_query(GstPad * pad,GstObject * parent,GstQuery * query)946 gst_audio_resample_query (GstPad * pad, GstObject * parent, GstQuery * query)
947 {
948   GstAudioResample *resample = GST_AUDIO_RESAMPLE (parent);
949   GstBaseTransform *trans;
950   gboolean res = TRUE;
951 
952   trans = GST_BASE_TRANSFORM (resample);
953 
954   switch (GST_QUERY_TYPE (query)) {
955     case GST_QUERY_LATENCY:
956     {
957       GstClockTime min, max;
958       gboolean live;
959       guint64 latency;
960       gint rate = resample->in.rate;
961       gint resampler_latency;
962 
963       if (resample->converter)
964         resampler_latency =
965             gst_audio_converter_get_max_latency (resample->converter);
966       else
967         resampler_latency = 0;
968 
969       if (gst_base_transform_is_passthrough (trans))
970         resampler_latency = 0;
971 
972       if ((res =
973               gst_pad_peer_query (GST_BASE_TRANSFORM_SINK_PAD (trans),
974                   query))) {
975         gst_query_parse_latency (query, &live, &min, &max);
976 
977         GST_DEBUG_OBJECT (resample, "Peer latency: min %"
978             GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
979             GST_TIME_ARGS (min), GST_TIME_ARGS (max));
980 
981         /* add our own latency */
982         if (rate != 0 && resampler_latency != 0)
983           latency = gst_util_uint64_scale_round (resampler_latency,
984               GST_SECOND, rate);
985         else
986           latency = 0;
987 
988         GST_DEBUG_OBJECT (resample, "Our latency: %" GST_TIME_FORMAT,
989             GST_TIME_ARGS (latency));
990 
991         min += latency;
992         if (GST_CLOCK_TIME_IS_VALID (max))
993           max += latency;
994 
995         GST_DEBUG_OBJECT (resample, "Calculated total latency : min %"
996             GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
997             GST_TIME_ARGS (min), GST_TIME_ARGS (max));
998 
999         gst_query_set_latency (query, live, min, max);
1000       }
1001       break;
1002     }
1003     default:
1004       res = gst_pad_query_default (pad, parent, query);
1005       break;
1006   }
1007   return res;
1008 }
1009 
1010 static void
gst_audio_resample_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1011 gst_audio_resample_set_property (GObject * object, guint prop_id,
1012     const GValue * value, GParamSpec * pspec)
1013 {
1014   GstAudioResample *resample;
1015 
1016   resample = GST_AUDIO_RESAMPLE (object);
1017 
1018   switch (prop_id) {
1019     case PROP_QUALITY:
1020       /* FIXME locking! */
1021       resample->quality = g_value_get_int (value);
1022       GST_DEBUG_OBJECT (resample, "new quality %d", resample->quality);
1023       gst_audio_resample_update_state (resample, NULL, NULL);
1024       break;
1025     case PROP_RESAMPLE_METHOD:
1026       resample->method = g_value_get_enum (value);
1027       gst_audio_resample_update_state (resample, NULL, NULL);
1028       break;
1029     case PROP_SINC_FILTER_MODE:
1030       /* FIXME locking! */
1031       resample->sinc_filter_mode = g_value_get_enum (value);
1032       gst_audio_resample_update_state (resample, NULL, NULL);
1033       break;
1034     case PROP_SINC_FILTER_AUTO_THRESHOLD:
1035       /* FIXME locking! */
1036       resample->sinc_filter_auto_threshold = g_value_get_uint (value);
1037       gst_audio_resample_update_state (resample, NULL, NULL);
1038       break;
1039     case PROP_SINC_FILTER_INTERPOLATION:
1040       /* FIXME locking! */
1041       resample->sinc_filter_interpolation = g_value_get_enum (value);
1042       gst_audio_resample_update_state (resample, NULL, NULL);
1043       break;
1044     default:
1045       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1046       break;
1047   }
1048 }
1049 
1050 static void
gst_audio_resample_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1051 gst_audio_resample_get_property (GObject * object, guint prop_id,
1052     GValue * value, GParamSpec * pspec)
1053 {
1054   GstAudioResample *resample;
1055 
1056   resample = GST_AUDIO_RESAMPLE (object);
1057 
1058   switch (prop_id) {
1059     case PROP_QUALITY:
1060       g_value_set_int (value, resample->quality);
1061       break;
1062     case PROP_RESAMPLE_METHOD:
1063       g_value_set_enum (value, resample->method);
1064       break;
1065     case PROP_SINC_FILTER_MODE:
1066       g_value_set_enum (value, resample->sinc_filter_mode);
1067       break;
1068     case PROP_SINC_FILTER_AUTO_THRESHOLD:
1069       g_value_set_uint (value, resample->sinc_filter_auto_threshold);
1070       break;
1071     case PROP_SINC_FILTER_INTERPOLATION:
1072       g_value_set_enum (value, resample->sinc_filter_interpolation);
1073       break;
1074     default:
1075       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1076       break;
1077   }
1078 }
1079 
1080 static gboolean
plugin_init(GstPlugin * plugin)1081 plugin_init (GstPlugin * plugin)
1082 {
1083   GST_DEBUG_CATEGORY_INIT (audio_resample_debug, "audioresample", 0,
1084       "audio resampling element");
1085 
1086   if (!gst_element_register (plugin, "audioresample", GST_RANK_PRIMARY,
1087           GST_TYPE_AUDIO_RESAMPLE)) {
1088     return FALSE;
1089   }
1090 
1091   return TRUE;
1092 }
1093 
1094 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1095     GST_VERSION_MINOR,
1096     audioresample,
1097     "Resamples audio", plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME,
1098     GST_PACKAGE_ORIGIN);
1099