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