gst/audioconvert/: Implement a channel mixer.
authorRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sun, 28 Nov 2004 16:09:13 +0000 (16:09 +0000)
committerRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sun, 28 Nov 2004 16:09:13 +0000 (16:09 +0000)
Original commit message from CVS:
* gst/audioconvert/Makefile.am:
* gst/audioconvert/gstaudioconvert.c: (gst_audio_convert_init),
(gst_audio_convert_link), (gst_audio_convert_change_state),
(gst_audio_convert_channels):
* gst/audioconvert/gstchannelmix.c:
(gst_audio_convert_unset_matrix),
(gst_audio_convert_fill_identical),
(gst_audio_convert_fill_compatible),
(gst_audio_convert_detect_pos), (gst_audio_convert_fill_one_other),
(gst_audio_convert_fill_others),
(gst_audio_convert_fill_normalize),
(gst_audio_convert_fill_matrix), (gst_audio_convert_setup_matrix),
(gst_audio_convert_passthrough), (gst_audio_convert_mix):
* gst/audioconvert/gstchannelmix.h:
Implement a channel mixer.

ChangeLog
gst/audioconvert/Makefile.am
gst/audioconvert/gstaudioconvert.c
gst/audioconvert/gstchannelmix.c [new file with mode: 0644]
gst/audioconvert/gstchannelmix.h [new file with mode: 0644]

index 0996af1..eb385bf 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2004-11-28  Ronald S. Bultje  <rbultje@ronald.bitfreak.net>
+
+       * gst/audioconvert/Makefile.am:
+       * gst/audioconvert/gstaudioconvert.c: (gst_audio_convert_init),
+       (gst_audio_convert_link), (gst_audio_convert_change_state),
+       (gst_audio_convert_channels):
+       * gst/audioconvert/gstchannelmix.c:
+       (gst_audio_convert_unset_matrix),
+       (gst_audio_convert_fill_identical),
+       (gst_audio_convert_fill_compatible),
+       (gst_audio_convert_detect_pos), (gst_audio_convert_fill_one_other),
+       (gst_audio_convert_fill_others),
+       (gst_audio_convert_fill_normalize),
+       (gst_audio_convert_fill_matrix), (gst_audio_convert_setup_matrix),
+       (gst_audio_convert_passthrough), (gst_audio_convert_mix):
+       * gst/audioconvert/gstchannelmix.h:
+         Implement a channel mixer.
+
 2004-11-28  Martin Soto  <martinsoto@users.sourceforge.net>
 
        * ext/alsa/gstalsasink.c (gst_alsa_sink_loop): 
index fff3c27..5595695 100644 (file)
@@ -1,7 +1,15 @@
-
 plugin_LTLIBRARIES = libgstaudioconvert.la
 
-libgstaudioconvert_la_SOURCES = gstaudioconvert.c bufferframesconvert.c plugin.c plugin.h
+libgstaudioconvert_la_SOURCES = \
+       gstaudioconvert.c \
+       gstchannelmix.c \
+       bufferframesconvert.c \
+       plugin.c
+
 libgstaudioconvert_la_CFLAGS = $(GST_CFLAGS)
 libgstaudioconvert_la_LIBADD =
 libgstaudioconvert_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+
+noinst_HEADERS = \
+       gstchannelmix.h \
+       plugin.h
index da07ce1..1ec9925 100644 (file)
 #include <gst/gst.h>
 #include <gst/audio/multichannel.h>
 #include <string.h>
+#include "gstchannelmix.h"
 #include "plugin.h"
 
-GST_DEBUG_CATEGORY_STATIC (audio_convert_debug);
-#define GST_CAT_DEFAULT (audio_convert_debug)
+GST_DEBUG_CATEGORY (audio_convert_debug);
 
 /*** DEFINITIONS **************************************************************/
 
-#define GST_TYPE_AUDIO_CONVERT          (gst_audio_convert_get_type())
-#define GST_AUDIO_CONVERT(obj)          (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_CONVERT,GstAudioConvert))
-#define GST_AUDIO_CONVERT_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIO_CONVERT,GstAudioConvert))
-#define GST_IS_AUDIO_CONVERT(obj)       (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_CONVERT))
-#define GST_IS_AUDIO_CONVERT_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIO_CONVERT))
-
-typedef struct _GstAudioConvert GstAudioConvert;
-typedef struct _GstAudioConvertCaps GstAudioConvertCaps;
-typedef struct _GstAudioConvertClass GstAudioConvertClass;
-
-/* this struct is a handy way of passing around all the caps info ... */
-struct _GstAudioConvertCaps
-{
-  /* general caps */
-  gboolean is_int;
-  gint endianness;
-  gint width;
-  gint rate;
-  gint channels;
-  GstAudioChannelPosition *pos;
-
-  /* int audio caps */
-  gboolean sign;
-  gint depth;
-
-  /* float audio caps */
-  gint buffer_frames;
-};
-
-struct _GstAudioConvert
-{
-  GstElement element;
-
-  /* pads */
-  GstPad *sink;
-  GstPad *src;
-
-  GstAudioConvertCaps srccaps;
-  GstAudioConvertCaps sinkcaps;
-
-  /* conversion functions */
-  GstBuffer *(*convert_internal) (GstAudioConvert * this, GstBuffer * buf);
-};
-
-struct _GstAudioConvertClass
-{
-  GstElementClass parent_class;
-};
-
 static GstElementDetails audio_convert_details = {
   "Audio Conversion",
   "Filter/Converter/Audio",
@@ -248,6 +199,7 @@ gst_audio_convert_init (GstAudioConvert * this)
   this->convert_internal = NULL;
   this->sinkcaps.pos = NULL;
   this->srccaps.pos = NULL;
+  this->matrix = NULL;
 }
 
 static void
@@ -434,6 +386,9 @@ gst_audio_convert_link (GstPad * pad, const GstCaps * caps)
   this = GST_AUDIO_CONVERT (GST_OBJECT_PARENT (pad));
   otherpad = (pad == this->src ? this->sink : this->src);
 
+  /* we'll need a new matrix after every new negotiation */
+  gst_audio_convert_unset_matrix (this);
+
   ac_caps.pos = NULL;
   if (!gst_audio_convert_parse_caps (caps, &ac_caps))
     return GST_PAD_LINK_REFUSED;
@@ -639,6 +594,7 @@ gst_audio_convert_change_state (GstElement * element)
   switch (GST_STATE_TRANSITION (element)) {
     case GST_STATE_PAUSED_TO_READY:
       this->convert_internal = NULL;
+      gst_audio_convert_unset_matrix (this);
       break;
     default:
       break;
@@ -895,138 +851,22 @@ static GstBuffer *
 gst_audio_convert_channels (GstAudioConvert * this, GstBuffer * buf)
 {
   GstBuffer *ret;
-  gint c, i, count, ci, co;
-  gint32 *src, *dest;
-
-  /* Conversions from one-channel to compatible two-channel configs */
-  struct
-  {
-    GstAudioChannelPosition pos1[2];
-    GstAudioChannelPosition pos2[1];
-  } conv[] = {
-    /* front: mono <-> stereo */
-    { {
-    GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
-            GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, {
-    GST_AUDIO_CHANNEL_POSITION_FRONT_MONO}},
-        /* front center: 2 <-> 1 */
-    { {
-    GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
-            GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER}, {
-    GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}},
-        /* rear: 2 <-> 1 */
-    { {
-    GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
-            GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
-    GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}}, { {
-    GST_AUDIO_CHANNEL_POSITION_INVALID}}
-  };
-  gboolean set[8] = { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE };
-
-  if (this->sinkcaps.channels == this->srccaps.channels) {
-    for (i = 0; i < this->sinkcaps.channels; i++) {
-      if (this->sinkcaps.pos[i] != this->srccaps.pos[i])
-        break;
-    }
-    if (i == this->sinkcaps.channels)
-      return buf;
-  }
-
-  count = GST_BUFFER_SIZE (buf) / 4 / this->sinkcaps.channels;
-  ret = gst_audio_convert_get_buffer (buf, count * 4 * this->srccaps.channels);
-
-  /* conversions from compatible (but not the same) channel schemes. This
-   * goes two ways: if the sink has both pos1[0,1] and src has pos2[0] or
-   * if the src has both pos1[0,1] and sink has pos2[0], then we do the
-   * conversion. We hereby assume that the existance of pos1[0,1] and
-   * pos2[0] are mututally exclusive. There are no checks for that,
-   * unfortunately. This shouldn't lead to issues (like crashes or so),
-   * though. */
-  for (c = 0; conv[c].pos1[0] != GST_AUDIO_CHANNEL_POSITION_INVALID; c++) {
-    gint pos1_0 = -1, pos1_1 = -1, pos2_0 = -1, n;
-
-    /* Try to go from the given 2 channels to the given 1 channel */
-    for (n = 0; n < this->sinkcaps.channels; n++) {
-      if (this->sinkcaps.pos[n] == conv[c].pos1[0])
-        pos1_0 = n;
-      else if (this->sinkcaps.pos[n] == conv[c].pos1[1])
-        pos1_1 = n;
-    }
-    for (n = 0; n < this->srccaps.channels; n++) {
-      if (this->srccaps.pos[n] == conv[c].pos2[0])
-        pos2_0 = n;
-    }
+  gint count;
 
-    if (pos1_0 != -1 && pos1_1 != -1 && pos2_0 != -1) {
-      src = (gint32 *) GST_BUFFER_DATA (buf);
-      dest = (gint32 *) GST_BUFFER_DATA (ret);
+  /* setup if not yet done */
+  if (!this->matrix)
+    gst_audio_convert_setup_matrix (this);
 
-      for (i = 0; i < count; i++) {
-        dest[pos2_0] = (src[pos1_0] >> 1) + (src[pos1_1] >> 1) +
-            ((src[pos1_0] & 1) & (src[pos1_1] & 1));
-        src += this->sinkcaps.channels;
-        dest += this->srccaps.channels;
-      }
-      set[pos2_0] = TRUE;
-    }
-
-    /* Try to go from the given 1 channel to the given 2 channels */
-    pos1_0 = -1;
-    pos1_1 = -1;
-    pos2_0 = -1;
-
-    for (n = 0; n < this->srccaps.channels; n++) {
-      if (this->srccaps.pos[n] == conv[c].pos1[0])
-        pos1_0 = n;
-      else if (this->srccaps.pos[n] == conv[c].pos1[1])
-        pos1_1 = n;
-    }
-    for (n = 0; n < this->sinkcaps.channels; n++) {
-      if (this->sinkcaps.pos[n] == conv[c].pos2[0])
-        pos2_0 = n;
-    }
-
-    if (pos1_0 != -1 && pos1_1 != -1 && pos2_0 != -1) {
-      src = (gint32 *) GST_BUFFER_DATA (buf);
-      dest = (gint32 *) GST_BUFFER_DATA (ret);
-
-      for (i = 0; i < count; i++) {
-        dest[pos1_0] = dest[pos1_1] = src[pos2_0];
-        src += this->sinkcaps.channels;
-        dest += this->srccaps.channels;
-      }
-      set[pos1_0] = set[pos1_1] = TRUE;
-    }
-  }
-
-  /* reset data pointers */
-  src = (gint32 *) GST_BUFFER_DATA (buf);
-  dest = (gint32 *) GST_BUFFER_DATA (ret);
-
-  /* Apart from the compatible channel assignments, we can also have
-   * same channel assignments. This is much simpler, we simply copy
-   * the value from source to dest! */
-  for (co = 0; co < this->srccaps.channels; co++) {
-    /* find a channel in input with same position */
-    for (ci = 0; ci < this->sinkcaps.channels; ci++) {
-      if (this->sinkcaps.pos[ci] == this->srccaps.pos[co]) {
-        for (i = 0; i < count; i++) {
-          dest[i * this->srccaps.channels + co] =
-              src[i * this->sinkcaps.channels + ci];
-        }
-        set[co] = TRUE;
-        break;
-      }
-    }
-
-    /* if not found, then silence */
-    if (ci == this->sinkcaps.channels && !set[co]) {
-      for (i = 0; i < count; i++) {
-        dest[i * this->srccaps.channels + co] = 0;
-      }
-    }
-  }
+  /* check for passthrough */
+  if (gst_audio_convert_passthrough (this))
+    return buf;
 
+  /* convert */
+  count = GST_BUFFER_SIZE (buf) / 4 / this->sinkcaps.channels;
+  ret = gst_audio_convert_get_buffer (buf, count * 4 * this->srccaps.channels);
+  gst_audio_convert_mix (this, (gint32 *) GST_BUFFER_DATA (buf),
+      (gint32 *) GST_BUFFER_DATA (ret), count);
   gst_buffer_unref (buf);
+
   return ret;
 }
diff --git a/gst/audioconvert/gstchannelmix.c b/gst/audioconvert/gstchannelmix.c
new file mode 100644 (file)
index 0000000..7d51127
--- /dev/null
@@ -0,0 +1,554 @@
+/* GStreamer
+ * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * gstchannelmix.c: setup of channel conversion matrices
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <gst/audio/multichannel.h>
+
+#include "gstchannelmix.h"
+
+/*
+ * Channel matrix functions.
+ */
+
+void
+gst_audio_convert_unset_matrix (GstAudioConvert * this)
+{
+  gint i;
+
+  /* don't access if nothing there */
+  if (!this->matrix)
+    return;
+
+  /* free */
+  for (i = 0; i < this->sinkcaps.channels; i++)
+    g_free (this->matrix[i]);
+  g_free (this->matrix);
+
+  this->matrix = NULL;
+}
+
+/*
+ * Detect and fill in identical channels. E.g.
+ * forward the left/right front channels in a
+ * 5.1 to 2.0 conversion.
+ */
+
+static void
+gst_audio_convert_fill_identical (GstAudioConvert * this)
+{
+  gint ci, co;
+
+  /* Apart from the compatible channel assignments, we can also have
+   * same channel assignments. This is much simpler, we simply copy
+   * the value from source to dest! */
+  for (co = 0; co < this->srccaps.channels; co++) {
+    /* find a channel in input with same position */
+    for (ci = 0; ci < this->sinkcaps.channels; ci++) {
+      if (this->sinkcaps.pos[ci] == this->srccaps.pos[co]) {
+        this->matrix[ci][co] = 1.0;
+      }
+    }
+  }
+}
+
+/*
+ * Detect and fill in compatible channels. E.g.
+ * forward left/right front to mono (or the other
+ * way around) when going from 2.0 to 1.0.
+ */
+
+static void
+gst_audio_convert_fill_compatible (GstAudioConvert * this)
+{
+  /* Conversions from one-channel to compatible two-channel configs */
+  struct
+  {
+    GstAudioChannelPosition pos1[2];
+    GstAudioChannelPosition pos2[1];
+  } conv[] = {
+    /* front: mono <-> stereo */
+    { {
+    GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+            GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, {
+    GST_AUDIO_CHANNEL_POSITION_FRONT_MONO}},
+        /* front center: 2 <-> 1 */
+    { {
+    GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+            GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER}, {
+    GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}},
+        /* rear: 2 <-> 1 */
+    { {
+    GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+            GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
+    GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}}, { {
+    GST_AUDIO_CHANNEL_POSITION_INVALID}}
+  };
+  gint c;
+
+  /* conversions from compatible (but not the same) channel schemes. This
+   * goes two ways: if the sink has both pos1[0,1] and src has pos2[0] or
+   * if the src has both pos1[0,1] and sink has pos2[0], then we do the
+   * conversion. We hereby assume that the existance of pos1[0,1] and
+   * pos2[0] are mututally exclusive. There are no checks for that,
+   * unfortunately. This shouldn't lead to issues (like crashes or so),
+   * though. */
+  for (c = 0; conv[c].pos1[0] != GST_AUDIO_CHANNEL_POSITION_INVALID; c++) {
+    gint pos1_0 = -1, pos1_1 = -1, pos2_0 = -1, n;
+
+    /* Try to go from the given 2 channels to the given 1 channel */
+    for (n = 0; n < this->sinkcaps.channels; n++) {
+      if (this->sinkcaps.pos[n] == conv[c].pos1[0])
+        pos1_0 = n;
+      else if (this->sinkcaps.pos[n] == conv[c].pos1[1])
+        pos1_1 = n;
+    }
+    for (n = 0; n < this->srccaps.channels; n++) {
+      if (this->srccaps.pos[n] == conv[c].pos2[0])
+        pos2_0 = n;
+    }
+
+    if (pos1_0 != -1 && pos1_1 != -1 && pos2_0 != -1) {
+      this->matrix[pos1_0][pos2_0] = -1.0;
+      this->matrix[pos1_1][pos2_0] = 1.0;
+    }
+
+    /* Try to go from the given 1 channel to the given 2 channels */
+    pos1_0 = -1;
+    pos1_1 = -1;
+    pos2_0 = -1;
+
+    for (n = 0; n < this->srccaps.channels; n++) {
+      if (this->srccaps.pos[n] == conv[c].pos1[0])
+        pos1_0 = n;
+      else if (this->srccaps.pos[n] == conv[c].pos1[1])
+        pos1_1 = n;
+    }
+    for (n = 0; n < this->sinkcaps.channels; n++) {
+      if (this->sinkcaps.pos[n] == conv[c].pos2[0])
+        pos2_0 = n;
+    }
+
+    if (pos1_0 != -1 && pos1_1 != -1 && pos2_0 != -1) {
+      this->matrix[pos2_0][pos1_0] = -1.0;
+      this->matrix[pos2_0][pos1_1] = 1.0;
+    }
+  }
+}
+
+/*
+ * Detect and fill in channels not handled by the
+ * above two, e.g. center to left/right front in
+ * 5.1 to 2.0 (or the other way around).
+ *
+ * Unfortunately, limited to static conversions
+ * for now.
+ */
+
+static void
+gst_audio_convert_detect_pos (GstAudioConvertCaps * caps,
+    gint * f, gboolean * has_f,
+    gint * c, gboolean * has_c, gint * r, gboolean * has_r,
+    gint * s, gboolean * has_s, gint * b, gboolean * has_b)
+{
+  gint n;
+
+  for (n = 0; n < caps->channels; n++) {
+    switch (caps->pos[n]) {
+      case GST_AUDIO_CHANNEL_POSITION_FRONT_MONO:
+      case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT:
+      case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT:
+        *has_f = TRUE;
+        if (f[0] == -1)
+          f[0] = n;
+        else
+          f[1] = n;
+        break;
+      case GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER:
+      case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
+      case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
+        *has_c = TRUE;
+        if (c[0] == -1)
+          c[0] = n;
+        else
+          c[1] = n;
+        break;
+      case GST_AUDIO_CHANNEL_POSITION_REAR_CENTER:
+      case GST_AUDIO_CHANNEL_POSITION_REAR_LEFT:
+      case GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT:
+        *has_r = TRUE;
+        if (r[0] == -1)
+          r[0] = n;
+        else
+          r[1] = n;
+        break;
+      case GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT:
+      case GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT:
+        *has_s = TRUE;
+        if (s[0] == -1)
+          s[0] = n;
+        else
+          s[1] = n;
+        break;
+      case GST_AUDIO_CHANNEL_POSITION_LFE:
+        *has_b = TRUE;
+        b[0] = n;
+        break;
+      default:
+        break;
+    }
+  }
+}
+
+static void
+gst_audio_convert_fill_one_other (gfloat ** matrix,
+    GstAudioConvertCaps * from_caps, gint * from_idx,
+    GstAudioChannelPosition from_pos_l,
+    GstAudioChannelPosition from_pos_r,
+    GstAudioChannelPosition from_pos_c,
+    GstAudioConvertCaps * to_caps, gint * to_idx,
+    GstAudioChannelPosition to_pos_l,
+    GstAudioChannelPosition to_pos_r,
+    GstAudioChannelPosition to_pos_c, gfloat ratio)
+{
+  gfloat in_r, out_r[2];
+
+  /*
+   * The idea is that we add up from the input (which means that if we
+   * have stereo input, we divide their sum by two) and put that in
+   * the matrix for their output ratio (given in $ratio).
+   * For left channels, we need to invert the signal sign (* -1).
+   */
+
+  if (from_caps->pos[from_idx[0]] == from_pos_c)
+    in_r = 1.0;
+  else
+    in_r = 0.5;
+
+  if (to_caps->pos[to_idx[0]] == to_pos_l)
+    out_r[0] = in_r * -ratio;
+  else
+    out_r[0] = in_r * ratio;
+
+  if (to_idx[1] != -1) {
+    if (to_caps->pos[to_idx[1]] == to_pos_l)
+      out_r[1] = in_r * -ratio;
+    else
+      out_r[1] = in_r * ratio;
+  }
+
+  matrix[from_idx[0]][to_idx[0]] = out_r[0];
+  if (to_idx[1] != -1)
+    matrix[from_idx[0]][to_idx[1]] = out_r[1];
+  if (from_idx[1] != -1) {
+    matrix[from_idx[1]][to_idx[0]] = out_r[0];
+    if (to_idx[1] != -1)
+      matrix[from_idx[1]][to_idx[1]] = out_r[1];
+  }
+}
+
+#define RATIO_FRONT_CENTER (1.0 / sqrt (2.0))
+#define RATIO_FRONT_REAR (1.0 / sqrt (2.0))
+#define RATIO_FRONT_BASS (1.0)
+#define RATIO_REAR_BASS (1.0 / sqrt (2.0))
+#define RATIO_CENTER_BASS (1.0 / sqrt (2.0))
+
+static void
+gst_audio_convert_fill_others (GstAudioConvert * this)
+{
+  gboolean in_has_front = FALSE, out_has_front = FALSE,
+      in_has_center = FALSE, out_has_center = FALSE,
+      in_has_rear = FALSE, out_has_rear = FALSE,
+      in_has_side = FALSE, out_has_side = FALSE,
+      in_has_bass = FALSE, out_has_bass = FALSE;
+  gint in_f[2] = { -1, -1 }, out_f[2] = {
+  -1, -1}, in_c[2] = {
+  -1, -1}, out_c[2] = {
+  -1, -1}, in_r[2] = {
+  -1, -1}, out_r[2] = {
+  -1, -1}, in_s[2] = {
+  -1, -1}, out_s[2] = {
+  -1, -1}, in_b[2] = {
+  -1, -1}, out_b[2] = {
+  -1, -1};
+
+  /* First see where (if at all) the various channels from/to
+   * which we want to convert are located in our matrix/array. */
+  gst_audio_convert_detect_pos (&this->sinkcaps,
+      in_f, &in_has_front,
+      in_c, &in_has_center, in_r, &in_has_rear,
+      in_s, &in_has_side, in_b, &in_has_bass);
+  gst_audio_convert_detect_pos (&this->srccaps,
+      out_f, &out_has_front,
+      out_c, &out_has_center, out_r, &out_has_rear,
+      out_s, &out_has_side, out_b, &out_has_bass);
+
+  /* center/front */
+  if (!in_has_center && in_has_front && out_has_center) {
+    gst_audio_convert_fill_one_other (this->matrix,
+        &this->sinkcaps, in_f,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_MONO,
+        &this->srccaps, out_c,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, RATIO_FRONT_CENTER);
+  } else if (in_has_center && !out_has_center && out_has_front) {
+    gst_audio_convert_fill_one_other (this->matrix,
+        &this->sinkcaps, in_c,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+        &this->srccaps, out_f,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_MONO, RATIO_FRONT_CENTER);
+  }
+
+  /* rear/front */
+  if (!in_has_rear && in_has_front && out_has_rear) {
+    gst_audio_convert_fill_one_other (this->matrix,
+        &this->sinkcaps, in_f,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_MONO,
+        &this->srccaps, out_r,
+        GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+        GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+        GST_AUDIO_CHANNEL_POSITION_REAR_CENTER, RATIO_FRONT_REAR);
+  } else if (in_has_center && !out_has_center && out_has_front) {
+    gst_audio_convert_fill_one_other (this->matrix,
+        &this->sinkcaps, in_r,
+        GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+        GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+        GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
+        &this->srccaps, out_f,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+        GST_AUDIO_CHANNEL_POSITION_FRONT_MONO, RATIO_FRONT_REAR);
+  }
+
+  /* bass/any */
+  if (in_has_bass && !out_has_bass) {
+    if (out_has_front) {
+      gst_audio_convert_fill_one_other (this->matrix,
+          &this->sinkcaps, in_b,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_LFE,
+          &this->srccaps, out_f,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_MONO, RATIO_FRONT_BASS);
+    }
+    if (out_has_center) {
+      gst_audio_convert_fill_one_other (this->matrix,
+          &this->sinkcaps, in_b,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_LFE,
+          &this->srccaps, out_c,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, RATIO_CENTER_BASS);
+    }
+    if (out_has_rear) {
+      gst_audio_convert_fill_one_other (this->matrix,
+          &this->sinkcaps, in_b,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_LFE,
+          &this->srccaps, out_r,
+          GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+          GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+          GST_AUDIO_CHANNEL_POSITION_REAR_CENTER, RATIO_REAR_BASS);
+    }
+  } else if (!in_has_bass && out_has_bass) {
+    if (in_has_front) {
+      gst_audio_convert_fill_one_other (this->matrix,
+          &this->sinkcaps, in_f,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_MONO,
+          &this->srccaps, out_b,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_LFE, RATIO_FRONT_BASS);
+    }
+    if (in_has_center) {
+      gst_audio_convert_fill_one_other (this->matrix,
+          &this->sinkcaps, in_c,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+          GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+          &this->srccaps, out_b,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_LFE, RATIO_CENTER_BASS);
+    }
+    if (in_has_rear) {
+      gst_audio_convert_fill_one_other (this->matrix,
+          &this->sinkcaps, in_r,
+          GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+          GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+          GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
+          &this->srccaps, out_b,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_INVALID,
+          GST_AUDIO_CHANNEL_POSITION_LFE, RATIO_REAR_BASS);
+    }
+  }
+
+  /* FIXME: side */
+}
+
+/*
+ * Normalize output values.
+ */
+
+static void
+gst_audio_convert_fill_normalize (GstAudioConvert * this)
+{
+  gfloat sum, top = 0;
+  gint i, j;
+
+  for (j = 0; j < this->srccaps.channels; j++) {
+    /* calculate sum */
+    sum = 0.0;
+    for (i = 0; i < this->sinkcaps.channels; i++) {
+      sum += this->matrix[i][j];
+    }
+    if (sum > top) {
+      top = sum;
+    }
+  }
+
+  /* normalize to this */
+  for (j = 0; j < this->srccaps.channels; j++) {
+    for (i = 0; i < this->sinkcaps.channels; i++) {
+      this->matrix[i][j] /= top;
+    }
+  }
+}
+
+/*
+ * Automagically generate conversion matrix.
+ */
+
+static void
+gst_audio_convert_fill_matrix (GstAudioConvert * this)
+{
+  gst_audio_convert_fill_identical (this);
+  gst_audio_convert_fill_compatible (this);
+  gst_audio_convert_fill_others (this);
+  gst_audio_convert_fill_normalize (this);
+}
+
+void
+gst_audio_convert_setup_matrix (GstAudioConvert * this)
+{
+  gint i, j;
+  GString *s;
+
+  /* don't lose memory */
+  gst_audio_convert_unset_matrix (this);
+
+  /* allocate */
+  this->matrix = g_new0 (gfloat *, this->sinkcaps.channels);
+  for (i = 0; i < this->sinkcaps.channels; i++) {
+    this->matrix[i] = g_new (gfloat, this->srccaps.channels);
+    for (j = 0; j < this->srccaps.channels; j++)
+      this->matrix[i][j] = 0.;
+  }
+
+  /* setup the matrix' internal values */
+  gst_audio_convert_fill_matrix (this);
+
+  /* debug */
+  s = g_string_new ("Matrix for");
+  g_string_append_printf (s, " %d -> %d: ",
+      this->sinkcaps.channels, this->srccaps.channels);
+  g_string_append (s, "{");
+  for (i = 0; i < this->sinkcaps.channels; i++) {
+    if (i != 0)
+      g_string_append (s, ",");
+    g_string_append (s, " {");
+    for (j = 0; j < this->srccaps.channels; j++) {
+      if (j != 0)
+        g_string_append (s, ",");
+      g_string_append_printf (s, " %f", this->matrix[i][j]);
+    }
+    g_string_append (s, " }");
+  }
+  g_string_append (s, " }");
+  GST_DEBUG (s->str);
+  g_string_free (s, TRUE);
+}
+
+gboolean
+gst_audio_convert_passthrough (GstAudioConvert * this)
+{
+  gint i;
+
+  /* only NxN matrices can be identities */
+  if (this->sinkcaps.channels != this->srccaps.channels)
+    return FALSE;
+
+  /* this assumes a normalized matrix */
+  for (i = 0; i < this->sinkcaps.channels; i++)
+    if (this->matrix[i][i] != 1.)
+      return FALSE;
+
+  return TRUE;
+}
+
+void
+gst_audio_convert_mix (GstAudioConvert * this,
+    gint32 * in_data, gint32 * out_data, gint samples)
+{
+  gint in, out, n;
+  gint64 res;
+
+  /* FIXME: use liboil here? */
+  for (out = 0; out < this->srccaps.channels; out++) {
+    for (n = 0; n < samples; n++) {
+      /* convert */
+      res = 0;
+      for (in = 0; in < this->sinkcaps.channels; in++) {
+        res += in_data[n * this->sinkcaps.channels + in] *
+            this->matrix[in][out];
+      }
+
+      /* clip (shouldn't we use doubles instead as intermediate format?) */
+      if (res < G_MININT32)
+        res = G_MININT32;
+      else if (res > G_MAXINT32)
+        res = G_MAXINT32;
+
+      /* store */
+      out_data[n * this->srccaps.channels + out] = res;
+    }
+  }
+}
diff --git a/gst/audioconvert/gstchannelmix.h b/gst/audioconvert/gstchannelmix.h
new file mode 100644 (file)
index 0000000..28d5082
--- /dev/null
@@ -0,0 +1,106 @@
+/* GStreamer
+ * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * gstchannelmix.h: setup of channel conversion matrices
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_CHANNEL_MIX_H__
+#define __GST_CHANNEL_MIX_H__
+
+#include <gst/gst.h>
+
+#define GST_TYPE_AUDIO_CONVERT          (gst_audio_convert_get_type())
+#define GST_AUDIO_CONVERT(obj)          (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_CONVERT,GstAudioConvert))
+#define GST_AUDIO_CONVERT_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIO_CONVERT,GstAudioConvert))
+#define GST_IS_AUDIO_CONVERT(obj)       (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_CONVERT))
+#define GST_IS_AUDIO_CONVERT_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIO_CONVERT))
+
+GST_DEBUG_CATEGORY_EXTERN (audio_convert_debug);
+#define GST_CAT_DEFAULT (audio_convert_debug)
+
+typedef struct _GstAudioConvert GstAudioConvert;
+typedef struct _GstAudioConvertCaps GstAudioConvertCaps;
+typedef struct _GstAudioConvertClass GstAudioConvertClass;
+
+/* this struct is a handy way of passing around all the caps info ... */
+struct _GstAudioConvertCaps
+{
+  /* general caps */
+  gboolean is_int;
+  gint endianness;
+  gint width;
+  gint rate;
+  gint channels;
+  GstAudioChannelPosition *pos;
+
+  /* int audio caps */
+  gboolean sign;
+  gint depth;
+
+  /* float audio caps */
+  gint buffer_frames;
+};
+
+struct _GstAudioConvert
+{
+  GstElement element;
+
+  /* pads */
+  GstPad *sink;
+  GstPad *src;
+
+  GstAudioConvertCaps srccaps;
+  GstAudioConvertCaps sinkcaps;
+
+  /* channel conversion matrix, m[in_channels][out_channels].
+   * If identity matrix, passthrough applies. */
+  gfloat **matrix;
+
+  /* conversion functions */
+  GstBuffer *(*convert_internal) (GstAudioConvert * this, GstBuffer * buf);
+};
+
+struct _GstAudioConvertClass
+{
+  GstElementClass parent_class;
+};
+
+/*
+ * Delete channel mixer matrix.
+ */
+void           gst_audio_convert_unset_matrix  (GstAudioConvert * this);
+
+/*
+ * Setup channel mixer matrix.
+ */
+void           gst_audio_convert_setup_matrix  (GstAudioConvert * this);
+
+/*
+ * Checks for passthrough (= identity matrix).
+ */
+gboolean       gst_audio_convert_passthrough   (GstAudioConvert * this);
+
+/*
+ * Do actual mixing.
+ */
+void           gst_audio_convert_mix           (GstAudioConvert * this,
+                                                gint32          * in_data,
+                                                gint32          * out_data,
+                                                gint              samples);
+
+#endif /* __GST_CHANNEL_MIX_H__ */