72e09bf8c813e6155d27654541ea987f3feb2bb4
[platform/upstream/gst-plugins-good.git] / gst / audiofx / audiopanorama.c
1 /*
2  * GStreamer
3  * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net>
4  * Copyright (C) 2006 Sebastian Dröge <slomo@circular-chaos.org>
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-audiopanorama
24  *
25  * Stereo panorama effect with controllable pan position. One can choose between the default psychoacoustic panning method,
26  * which keeps the same perceived loudness, and a simple panning method that just controls the volume on one channel.
27  *
28  * <refsect2>
29  * <title>Example launch line</title>
30  * |[
31  * gst-launch-1.0 audiotestsrc wave=saw ! audiopanorama panorama=-1.00 ! alsasink
32  * gst-launch-1.0 filesrc location="melo1.ogg" ! oggdemux ! vorbisdec ! audioconvert ! audiopanorama panorama=-1.00 ! alsasink
33  * gst-launch-1.0 audiotestsrc wave=saw ! audioconvert ! audiopanorama panorama=-1.00 ! audioconvert ! alsasink
34  * gst-launch-1.0 audiotestsrc wave=saw ! audioconvert ! audiopanorama method=simple panorama=-0.50 ! audioconvert ! alsasink
35  * ]|
36  * </refsect2>
37  */
38
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42
43 #include <string.h>
44
45 #include <gst/gst.h>
46 #include <gst/base/gstbasetransform.h>
47
48 #include "audiopanorama.h"
49
50 #define GST_CAT_DEFAULT gst_audio_panorama_debug
51 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
52
53 /* Filter signals and args */
54 enum
55 {
56   PROP_0,
57   PROP_PANORAMA,
58   PROP_METHOD
59 };
60
61 #define GST_TYPE_AUDIO_PANORAMA_METHOD (gst_audio_panorama_method_get_type ())
62 static GType
63 gst_audio_panorama_method_get_type (void)
64 {
65   static GType gtype = 0;
66
67   if (gtype == 0) {
68     static const GEnumValue values[] = {
69       {METHOD_PSYCHOACOUSTIC, "Psychoacoustic Panning (default)",
70           "psychoacoustic"},
71       {METHOD_SIMPLE, "Simple Panning", "simple"},
72       {0, NULL, NULL}
73     };
74
75     gtype = g_enum_register_static ("GstAudioPanoramaMethod", values);
76   }
77   return gtype;
78 }
79
80 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
81     GST_PAD_SINK,
82     GST_PAD_ALWAYS,
83     GST_STATIC_CAPS ("audio/x-raw, "
84         "format = (string) { " GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (S16) "}, "
85         "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ], "
86         "layout = (string) interleaved")
87     );
88
89 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
90     GST_PAD_SRC,
91     GST_PAD_ALWAYS,
92     GST_STATIC_CAPS ("audio/x-raw, "
93         "format = (string) { " GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (S16) "}, "
94         "rate = (int) [ 1, MAX ], " "channels = (int) 2, "
95         "layout = (string) interleaved")
96     );
97
98 G_DEFINE_TYPE (GstAudioPanorama, gst_audio_panorama, GST_TYPE_BASE_TRANSFORM);
99
100 static void gst_audio_panorama_set_property (GObject * object, guint prop_id,
101     const GValue * value, GParamSpec * pspec);
102 static void gst_audio_panorama_get_property (GObject * object, guint prop_id,
103     GValue * value, GParamSpec * pspec);
104
105 static gboolean gst_audio_panorama_get_unit_size (GstBaseTransform * base,
106     GstCaps * caps, gsize * size);
107 static GstCaps *gst_audio_panorama_transform_caps (GstBaseTransform * base,
108     GstPadDirection direction, GstCaps * caps, GstCaps * filter);
109 static gboolean gst_audio_panorama_set_caps (GstBaseTransform * base,
110     GstCaps * incaps, GstCaps * outcaps);
111
112 static void gst_audio_panorama_transform_m2s_int (GstAudioPanorama * filter,
113     gint16 * idata, gint16 * odata, guint num_samples);
114 static void gst_audio_panorama_transform_s2s_int (GstAudioPanorama * filter,
115     gint16 * idata, gint16 * odata, guint num_samples);
116 static void gst_audio_panorama_transform_m2s_float (GstAudioPanorama * filter,
117     gfloat * idata, gfloat * odata, guint num_samples);
118 static void gst_audio_panorama_transform_s2s_float (GstAudioPanorama * filter,
119     gfloat * idata, gfloat * odata, guint num_samples);
120
121 static void gst_audio_panorama_transform_m2s_int_simple (GstAudioPanorama *
122     filter, gint16 * idata, gint16 * odata, guint num_samples);
123 static void gst_audio_panorama_transform_s2s_int_simple (GstAudioPanorama *
124     filter, gint16 * idata, gint16 * odata, guint num_samples);
125 static void gst_audio_panorama_transform_m2s_float_simple (GstAudioPanorama *
126     filter, gfloat * idata, gfloat * odata, guint num_samples);
127 static void gst_audio_panorama_transform_s2s_float_simple (GstAudioPanorama *
128     filter, gfloat * idata, gfloat * odata, guint num_samples);
129
130 static GstFlowReturn gst_audio_panorama_transform (GstBaseTransform * base,
131     GstBuffer * inbuf, GstBuffer * outbuf);
132
133
134 /* Table with processing functions: [channels][format][method] */
135 static GstAudioPanoramaProcessFunc panorama_process_functions[2][2][2] = {
136   {
137         {(GstAudioPanoramaProcessFunc) gst_audio_panorama_transform_m2s_int,
138               (GstAudioPanoramaProcessFunc)
139             gst_audio_panorama_transform_m2s_int_simple},
140         {(GstAudioPanoramaProcessFunc) gst_audio_panorama_transform_m2s_float,
141               (GstAudioPanoramaProcessFunc)
142             gst_audio_panorama_transform_m2s_float_simple}
143       },
144   {
145         {(GstAudioPanoramaProcessFunc) gst_audio_panorama_transform_s2s_int,
146               (GstAudioPanoramaProcessFunc)
147             gst_audio_panorama_transform_s2s_int_simple},
148         {(GstAudioPanoramaProcessFunc) gst_audio_panorama_transform_s2s_float,
149               (GstAudioPanoramaProcessFunc)
150             gst_audio_panorama_transform_s2s_float_simple}
151       }
152 };
153
154 /* GObject vmethod implementations */
155
156 static void
157 gst_audio_panorama_class_init (GstAudioPanoramaClass * klass)
158 {
159   GObjectClass *gobject_class;
160   GstElementClass *gstelement_class;
161
162   GST_DEBUG_CATEGORY_INIT (gst_audio_panorama_debug, "audiopanorama", 0,
163       "audiopanorama element");
164
165   gobject_class = (GObjectClass *) klass;
166   gstelement_class = (GstElementClass *) klass;
167
168   gobject_class->set_property = gst_audio_panorama_set_property;
169   gobject_class->get_property = gst_audio_panorama_get_property;
170
171   g_object_class_install_property (gobject_class, PROP_PANORAMA,
172       g_param_spec_float ("panorama", "Panorama",
173           "Position in stereo panorama (-1.0 left -> 1.0 right)", -1.0, 1.0,
174           0.0,
175           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
176   /**
177    * GstAudioPanorama:method
178    *
179    * Panning method: psychoacoustic mode keeps the same perceived loudness,
180    * while simple mode just controls the volume of one channel. It's merely
181    * a matter of taste which method should be chosen. 
182    *
183    * Since: 0.10.6
184    **/
185   g_object_class_install_property (gobject_class, PROP_METHOD,
186       g_param_spec_enum ("method", "Panning method",
187           "Psychoacoustic mode keeps same perceived loudness, "
188           "simple mode just controls volume of one channel.",
189           GST_TYPE_AUDIO_PANORAMA_METHOD, METHOD_PSYCHOACOUSTIC,
190           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
191
192   gst_element_class_set_static_metadata (gstelement_class, "Stereo positioning",
193       "Filter/Effect/Audio",
194       "Positions audio streams in the stereo panorama",
195       "Stefan Kost <ensonic@users.sf.net>");
196
197   gst_element_class_add_pad_template (gstelement_class,
198       gst_static_pad_template_get (&src_template));
199   gst_element_class_add_pad_template (gstelement_class,
200       gst_static_pad_template_get (&sink_template));
201
202   GST_BASE_TRANSFORM_CLASS (klass)->get_unit_size =
203       GST_DEBUG_FUNCPTR (gst_audio_panorama_get_unit_size);
204   GST_BASE_TRANSFORM_CLASS (klass)->transform_caps =
205       GST_DEBUG_FUNCPTR (gst_audio_panorama_transform_caps);
206   GST_BASE_TRANSFORM_CLASS (klass)->set_caps =
207       GST_DEBUG_FUNCPTR (gst_audio_panorama_set_caps);
208   GST_BASE_TRANSFORM_CLASS (klass)->transform =
209       GST_DEBUG_FUNCPTR (gst_audio_panorama_transform);
210 }
211
212 static void
213 gst_audio_panorama_init (GstAudioPanorama * filter)
214 {
215
216   filter->panorama = 0;
217   filter->method = METHOD_PSYCHOACOUSTIC;
218   gst_audio_info_init (&filter->info);
219   filter->process = NULL;
220
221   gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (filter), TRUE);
222 }
223
224 static gboolean
225 gst_audio_panorama_set_process_function (GstAudioPanorama * filter,
226     GstAudioInfo * info)
227 {
228   gint channel_index, format_index, method_index;
229   const GstAudioFormatInfo *finfo = info->finfo;
230
231   /* set processing function */
232   channel_index = GST_AUDIO_INFO_CHANNELS (info) - 1;
233   if (channel_index > 1 || channel_index < 0) {
234     filter->process = NULL;
235     return FALSE;
236   }
237
238   format_index = GST_AUDIO_FORMAT_INFO_IS_FLOAT (finfo) ? 1 : 0;
239   method_index = filter->method;
240
241   filter->process =
242       panorama_process_functions[channel_index][format_index][method_index];
243   return TRUE;
244 }
245
246 static void
247 gst_audio_panorama_set_property (GObject * object, guint prop_id,
248     const GValue * value, GParamSpec * pspec)
249 {
250   GstAudioPanorama *filter = GST_AUDIO_PANORAMA (object);
251
252   switch (prop_id) {
253     case PROP_PANORAMA:
254       filter->panorama = g_value_get_float (value);
255       break;
256     case PROP_METHOD:
257       filter->method = g_value_get_enum (value);
258       gst_audio_panorama_set_process_function (filter, &filter->info);
259       break;
260     default:
261       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
262       break;
263   }
264 }
265
266 static void
267 gst_audio_panorama_get_property (GObject * object, guint prop_id,
268     GValue * value, GParamSpec * pspec)
269 {
270   GstAudioPanorama *filter = GST_AUDIO_PANORAMA (object);
271
272   switch (prop_id) {
273     case PROP_PANORAMA:
274       g_value_set_float (value, filter->panorama);
275       break;
276     case PROP_METHOD:
277       g_value_set_enum (value, filter->method);
278       break;
279     default:
280       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
281       break;
282   }
283 }
284
285 /* GstBaseTransform vmethod implementations */
286
287 static gboolean
288 gst_audio_panorama_get_unit_size (GstBaseTransform * base, GstCaps * caps,
289     gsize * size)
290 {
291   GstAudioInfo info;
292
293   g_assert (size);
294
295   if (!gst_audio_info_from_caps (&info, caps))
296     return FALSE;
297
298   *size = GST_AUDIO_INFO_BPF (&info);
299
300   return TRUE;
301 }
302
303 static GstCaps *
304 gst_audio_panorama_transform_caps (GstBaseTransform * base,
305     GstPadDirection direction, GstCaps * caps, GstCaps * filter)
306 {
307   GstCaps *res;
308   GstStructure *structure;
309   gint i;
310
311   /* replace the channel property with our range. */
312   res = gst_caps_copy (caps);
313   for (i = 0; i < gst_caps_get_size (res); i++) {
314     structure = gst_caps_get_structure (res, i);
315     if (direction == GST_PAD_SRC) {
316       GST_INFO_OBJECT (base, "[%d] allow 1-2 channels", i);
317       gst_structure_set (structure, "channels", GST_TYPE_INT_RANGE, 1, 2, NULL);
318     } else {
319       GST_INFO_OBJECT (base, "[%d] allow 2 channels", i);
320       gst_structure_set (structure, "channels", G_TYPE_INT, 2, NULL);
321     }
322     gst_structure_remove_field (structure, "channel-mask");
323   }
324   GST_DEBUG_OBJECT (base, "transformed %" GST_PTR_FORMAT, res);
325
326   if (filter) {
327     GstCaps *intersection;
328
329     GST_DEBUG_OBJECT (base, "Using filter caps %" GST_PTR_FORMAT, filter);
330     intersection =
331         gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
332     gst_caps_unref (res);
333     res = intersection;
334     GST_DEBUG_OBJECT (base, "Intersection %" GST_PTR_FORMAT, res);
335   }
336
337   return res;
338 }
339
340 static gboolean
341 gst_audio_panorama_set_caps (GstBaseTransform * base, GstCaps * incaps,
342     GstCaps * outcaps)
343 {
344   GstAudioPanorama *filter = GST_AUDIO_PANORAMA (base);
345   GstAudioInfo info;
346
347   /*GST_INFO ("incaps are %" GST_PTR_FORMAT, incaps); */
348   if (!gst_audio_info_from_caps (&info, incaps))
349     goto no_format;
350
351   GST_DEBUG ("try to process %d input with %d channels",
352       GST_AUDIO_INFO_FORMAT (&info), GST_AUDIO_INFO_CHANNELS (&info));
353
354   if (!gst_audio_panorama_set_process_function (filter, &info))
355     goto no_format;
356
357   filter->info = info;
358
359   return TRUE;
360
361 no_format:
362   {
363     GST_DEBUG ("invalid caps");
364     return FALSE;
365   }
366 }
367
368 /* psychoacoustic processing functions */
369 static void
370 gst_audio_panorama_transform_m2s_int (GstAudioPanorama * filter, gint16 * idata,
371     gint16 * odata, guint num_samples)
372 {
373   guint i;
374   gdouble val;
375   glong lval, rval;
376   gdouble rpan, lpan;
377
378   /* pan:  -1.0  0.0  1.0
379    * lpan:  1.0  0.5  0.0  
380    * rpan:  0.0  0.5  1.0
381    *
382    * FIXME: we should use -3db (1/sqtr(2)) for 50:50
383    */
384   rpan = (gdouble) (filter->panorama + 1.0) / 2.0;
385   lpan = 1.0 - rpan;
386
387   for (i = 0; i < num_samples; i++) {
388     val = (gdouble) * idata++;
389
390     lval = (glong) (val * lpan);
391     rval = (glong) (val * rpan);
392
393     *odata++ = (gint16) CLAMP (lval, G_MININT16, G_MAXINT16);
394     *odata++ = (gint16) CLAMP (rval, G_MININT16, G_MAXINT16);
395   }
396 }
397
398 static void
399 gst_audio_panorama_transform_s2s_int (GstAudioPanorama * filter, gint16 * idata,
400     gint16 * odata, guint num_samples)
401 {
402   guint i;
403   glong lval, rval;
404   gdouble lival, rival;
405   gdouble lrpan, llpan, rrpan, rlpan;
406
407   /* pan:  -1.0  0.0  1.0
408    * llpan: 1.0  1.0  0.0
409    * lrpan: 1.0  0.0  0.0
410    * rrpan: 0.0  1.0  1.0
411    * rlpan: 0.0  0.0  1.0
412    */
413   if (filter->panorama > 0) {
414     rlpan = (gdouble) filter->panorama;
415     llpan = 1.0 - rlpan;
416     lrpan = 0.0;
417     rrpan = 1.0;
418   } else {
419     rrpan = (gdouble) (1.0 + filter->panorama);
420     lrpan = 1.0 - rrpan;
421     rlpan = 0.0;
422     llpan = 1.0;
423   }
424
425   for (i = 0; i < num_samples; i++) {
426     lival = (gdouble) * idata++;
427     rival = (gdouble) * idata++;
428
429     lval = lival * llpan + rival * lrpan;
430     rval = lival * rlpan + rival * rrpan;
431
432     *odata++ = (gint16) CLAMP (lval, G_MININT16, G_MAXINT16);
433     *odata++ = (gint16) CLAMP (rval, G_MININT16, G_MAXINT16);
434   }
435 }
436
437 static void
438 gst_audio_panorama_transform_m2s_float (GstAudioPanorama * filter,
439     gfloat * idata, gfloat * odata, guint num_samples)
440 {
441   guint i;
442   gfloat val;
443   gdouble rpan, lpan;
444
445   /* pan:  -1.0  0.0  1.0
446    * lpan:  1.0  0.5  0.0  
447    * rpan:  0.0  0.5  1.0
448    *
449    * FIXME: we should use -3db (1/sqtr(2)) for 50:50
450    */
451   rpan = (gdouble) (filter->panorama + 1.0) / 2.0;
452   lpan = 1.0 - rpan;
453
454   for (i = 0; i < num_samples; i++) {
455     val = *idata++;
456
457     *odata++ = val * lpan;
458     *odata++ = val * rpan;
459   }
460 }
461
462 static void
463 gst_audio_panorama_transform_s2s_float (GstAudioPanorama * filter,
464     gfloat * idata, gfloat * odata, guint num_samples)
465 {
466   guint i;
467   gfloat lival, rival;
468   gdouble lrpan, llpan, rrpan, rlpan;
469
470   /* pan:  -1.0  0.0  1.0
471    * llpan: 1.0  1.0  0.0
472    * lrpan: 1.0  0.0  0.0
473    * rrpan: 0.0  1.0  1.0
474    * rlpan: 0.0  0.0  1.0
475    */
476   if (filter->panorama > 0) {
477     rlpan = (gdouble) filter->panorama;
478     llpan = 1.0 - rlpan;
479     lrpan = 0.0;
480     rrpan = 1.0;
481   } else {
482     rrpan = (gdouble) (1.0 + filter->panorama);
483     lrpan = 1.0 - rrpan;
484     rlpan = 0.0;
485     llpan = 1.0;
486   }
487
488   for (i = 0; i < num_samples; i++) {
489     lival = *idata++;
490     rival = *idata++;
491
492     *odata++ = lival * llpan + rival * lrpan;
493     *odata++ = lival * rlpan + rival * rrpan;
494   }
495 }
496
497 /* simple processing functions */
498 static void
499 gst_audio_panorama_transform_m2s_int_simple (GstAudioPanorama * filter,
500     gint16 * idata, gint16 * odata, guint num_samples)
501 {
502   guint i;
503   gdouble pan;
504   glong lval, rval;
505
506   if (filter->panorama > 0.0) {
507     pan = 1.0 - filter->panorama;
508     for (i = 0; i < num_samples; i++) {
509       rval = *idata++;
510       lval = (glong) ((gdouble) rval * pan);
511
512       *odata++ = (gint16) CLAMP (lval, G_MININT16, G_MAXINT16);
513       *odata++ = (gint16) rval;
514     }
515   } else {
516     pan = 1.0 + filter->panorama;
517     for (i = 0; i < num_samples; i++) {
518       lval = *idata++;
519       rval = (glong) ((gdouble) lval * pan);
520
521       *odata++ = (gint16) lval;
522       *odata++ = (gint16) CLAMP (rval, G_MININT16, G_MAXINT16);
523     }
524   }
525 }
526
527 static void
528 gst_audio_panorama_transform_s2s_int_simple (GstAudioPanorama * filter,
529     gint16 * idata, gint16 * odata, guint num_samples)
530 {
531   guint i;
532   glong lval, rval;
533   gdouble lival, rival, pan;
534
535   if (filter->panorama > 0.0) {
536     pan = 1.0 - filter->panorama;
537     for (i = 0; i < num_samples; i++) {
538       lival = (gdouble) * idata++;
539       rival = (gdouble) * idata++;
540
541       lval = (glong) (lival * pan);
542       rval = (glong) rival;
543
544       *odata++ = (gint16) CLAMP (lval, G_MININT16, G_MAXINT16);
545       *odata++ = (gint16) rval;
546     }
547   } else {
548     pan = 1.0 + filter->panorama;
549     for (i = 0; i < num_samples; i++) {
550       lival = (gdouble) * idata++;
551       rival = (gdouble) * idata++;
552
553       lval = (glong) lival;
554       rval = (glong) (rival * pan);
555
556       *odata++ = (gint16) lval;
557       *odata++ = (gint16) CLAMP (rval, G_MININT16, G_MAXINT16);
558     }
559   }
560 }
561
562 static void
563 gst_audio_panorama_transform_m2s_float_simple (GstAudioPanorama * filter,
564     gfloat * idata, gfloat * odata, guint num_samples)
565 {
566   guint i;
567   gfloat val, pan;
568
569   if (filter->panorama > 0.0) {
570     pan = 1.0 - filter->panorama;
571     for (i = 0; i < num_samples; i++) {
572       val = *idata++;
573
574       *odata++ = val * pan;
575       *odata++ = val;
576     }
577   } else {
578     pan = 1.0 + filter->panorama;
579     for (i = 0; i < num_samples; i++) {
580       val = *idata++;
581
582       *odata++ = val;
583       *odata++ = val * pan;
584     }
585   }
586 }
587
588 static void
589 gst_audio_panorama_transform_s2s_float_simple (GstAudioPanorama * filter,
590     gfloat * idata, gfloat * odata, guint num_samples)
591 {
592   guint i;
593   gfloat lival, rival, pan;
594
595   if (filter->panorama > 0.0) {
596     pan = 1.0 - filter->panorama;
597     for (i = 0; i < num_samples; i++) {
598       lival = *idata++;
599       rival = *idata++;
600
601       *odata++ = lival * pan;
602       *odata++ = rival;
603     }
604   } else {
605     pan = 1.0 + filter->panorama;
606     for (i = 0; i < num_samples; i++) {
607       lival = *idata++;
608       rival = *idata++;
609
610       *odata++ = lival;
611       *odata++ = rival * pan;
612     }
613   }
614 }
615
616 /* this function does the actual processing
617  */
618 static GstFlowReturn
619 gst_audio_panorama_transform (GstBaseTransform * base, GstBuffer * inbuf,
620     GstBuffer * outbuf)
621 {
622   GstAudioPanorama *filter = GST_AUDIO_PANORAMA (base);
623   GstClockTime timestamp, stream_time;
624   GstMapInfo inmap, outmap;
625
626   timestamp = GST_BUFFER_TIMESTAMP (inbuf);
627   stream_time =
628       gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp);
629
630   GST_DEBUG_OBJECT (filter, "sync to %" GST_TIME_FORMAT,
631       GST_TIME_ARGS (timestamp));
632
633   if (GST_CLOCK_TIME_IS_VALID (stream_time))
634     gst_object_sync_values (GST_OBJECT (filter), stream_time);
635
636   gst_buffer_map (inbuf, &inmap, GST_MAP_READ);
637   gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE);
638
639   if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP))) {
640     GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP);
641     memset (outmap.data, 0, outmap.size);
642   } else {
643     /* output always stereo, input mono or stereo,
644      * and info describes input format */
645     guint num_samples = outmap.size / (2 * GST_AUDIO_INFO_BPS (&filter->info));
646
647     filter->process (filter, inmap.data, outmap.data, num_samples);
648   }
649
650   gst_buffer_unmap (inbuf, &inmap);
651   gst_buffer_unmap (outbuf, &outmap);
652
653   return GST_FLOW_OK;
654 }