Multi-channel support with channel positions.
authorDave Robillard <dave@drobilla.net>
Wed, 12 Aug 2009 05:03:32 +0000 (01:03 -0400)
committerStefan Kost <ensonic@users.sf.net>
Thu, 17 Sep 2009 06:46:49 +0000 (09:46 +0300)
This queries port roles from the LV2 data and converts it into GStreamer
channel positions.  This should allow any type of multi-channel plugin
(including beyond stereo, e.g. surround) to work fine in GStreamer,
and with elements that require channel positions to be explicitly stated.

ext/ladspa/gstladspa.c
ext/lv2/gstlv2.c
ext/lv2/gstlv2.h
gst-libs/gst/signalprocessor/gstsignalprocessor.c
gst-libs/gst/signalprocessor/gstsignalprocessor.h

index 11da784..52adcc3 100644 (file)
@@ -82,6 +82,7 @@ gst_ladspa_base_init (gpointer g_class)
   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
   GstSignalProcessorClass *gsp_class = GST_SIGNAL_PROCESSOR_CLASS (g_class);
   GstElementDetails *details;
+  GstAudioChannelPosition mono_position = GST_AUDIO_CHANNEL_POSITION_FRONT_MONO;
   LADSPA_Descriptor *desc;
   guint j, audio_in_count, audio_out_count, control_in_count, control_out_count;
   gchar *klass_tags;
@@ -125,10 +126,10 @@ gst_ladspa_base_init (gpointer g_class)
 
       if (LADSPA_IS_PORT_INPUT (p))
         gst_signal_processor_class_add_pad_template (gsp_class, name,
-            GST_PAD_SINK, gsp_class->num_audio_in++, 1);
+            GST_PAD_SINK, gsp_class->num_audio_in++, 1, &mono_position);
       else
         gst_signal_processor_class_add_pad_template (gsp_class, name,
-            GST_PAD_SRC, gsp_class->num_audio_out++, 1);
+            GST_PAD_SRC, gsp_class->num_audio_out++, 1, &mono_position);
 
       g_free (name);
     } else if (LADSPA_IS_PORT_CONTROL (p)) {
index d1e4d17..ecf3fbe 100644 (file)
@@ -40,6 +40,7 @@
 #include <glib.h>
 #include <gst/audio/audio.h>
 #include <gst/controller/gstcontroller.h>
+#include <gst/audio/multichannel.h>
 
 #include "gstlv2.h"
 #include <slv2/slv2.h>
@@ -67,8 +68,21 @@ SLV2Value integer_prop;
 SLV2Value toggled_prop;
 SLV2Value in_place_broken_pred;
 SLV2Value in_group_pred;
+SLV2Value has_role_pred;
 SLV2Value lv2_symbol_pred;
 
+SLV2Value center_role;
+SLV2Value left_role;
+SLV2Value right_role;
+SLV2Value rear_center_role;
+SLV2Value rear_left_role;
+SLV2Value rear_right_role;
+SLV2Value lfe_role;
+SLV2Value center_left_role;
+SLV2Value center_right_role;
+SLV2Value side_left_role;
+SLV2Value side_right_role;
+
 static GstSignalProcessorClass *parent_class;
 
 static GstPlugin *gst_lv2_plugin;
@@ -76,6 +90,51 @@ static GstPlugin *gst_lv2_plugin;
 GST_DEBUG_CATEGORY_STATIC (lv2_debug);
 #define GST_CAT_DEFAULT lv2_debug
 
+/** Convert an LV2 port role to a Gst channel positon */
+static GstAudioChannelPosition
+gst_lv2_role_to_position (SLV2Value role)
+{
+  /* Front.  Mono and left/right are mututally exclusive */
+  if (slv2_value_equals (role, center_role)) {
+    /** WARNING: If the group has only a single port, this must be changed to
+     * GST_AUDIO_CHANNEL_POSITION_FRONT_MONO.  This can't be done by this
+     * function because this information isn't known at the time it is used.
+     */
+    return GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
+  } else if (slv2_value_equals (role, left_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
+  } else if (slv2_value_equals (role, right_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
+
+    /* Rear. Left/right and center are mututally exclusive */
+  } else if (slv2_value_equals (role, rear_center_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_REAR_CENTER;
+  } else if (slv2_value_equals (role, rear_left_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_REAR_LEFT;
+  } else if (slv2_value_equals (role, rear_right_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT;
+
+    /* Subwoofer/low-frequency-effects */
+  } else if (slv2_value_equals (role, lfe_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_LFE;
+
+    /* Center front speakers. Center and left/right_of_center
+     * are mutually exclusive */
+  } else if (slv2_value_equals (role, center_left_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+  } else if (slv2_value_equals (role, center_right_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+
+    /* sides */
+  } else if (slv2_value_equals (role, side_left_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT;
+  } else if (slv2_value_equals (role, side_right_role)) {
+    return GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT;
+  }
+
+  return GST_AUDIO_CHANNEL_POSITION_INVALID;
+}
+
 /** Find and return the group @a uri in @a groups, or NULL if not found */
 static GstLV2Group *
 gst_lv2_class_find_group (GArray * groups, SLV2Value uri)
@@ -87,6 +146,23 @@ gst_lv2_class_find_group (GArray * groups, SLV2Value uri)
   return NULL;
 }
 
+static GstAudioChannelPosition *
+gst_lv2_build_positions (GstLV2Group * group)
+{
+  int i;
+  GstAudioChannelPosition *positions = NULL;
+  if (group->has_roles) {
+    positions = malloc (group->ports->len * sizeof (GstAudioChannelPosition));
+    for (i = 0; i < group->ports->len; ++i)
+      positions[i] = g_array_index (group->ports, GstLV2Port, i).position;
+
+    // Fix up mono groups (see WARNING above)
+    if (group->ports->len == 1)
+      positions[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_MONO;
+  }
+  return positions;
+}
+
 static void
 gst_lv2_base_init (gpointer g_class)
 {
@@ -98,6 +174,9 @@ gst_lv2_base_init (gpointer g_class)
   SLV2Value val;
   SLV2Values values, sub_values;
   GstLV2Group *group = NULL;
+  GstAudioChannelPosition position = GST_AUDIO_CHANNEL_POSITION_INVALID;
+  GstAudioChannelPosition mono_position = GST_AUDIO_CHANNEL_POSITION_FRONT_MONO;
+  GstAudioChannelPosition *positions = NULL;
   guint j, in_pad_index = 0, out_pad_index = 0;
   gchar *klass_tags;
 
@@ -139,6 +218,7 @@ gst_lv2_base_init (gpointer g_class)
         g.uri = slv2_value_duplicate (group_uri);
         g.pad = is_input ? in_pad_index++ : out_pad_index++;
         g.ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
+        g.has_roles = TRUE;
         sub_values = slv2_plugin_get_value_for_subject (lv2plugin, group_uri,
             lv2_symbol_pred);
         if (slv2_values_size (sub_values) > 0)
@@ -151,6 +231,19 @@ gst_lv2_base_init (gpointer g_class)
         group = &g_array_index (groups, GstLV2Group, groups->len - 1);
       }
 
+      position = GST_AUDIO_CHANNEL_POSITION_INVALID;
+      sub_values = slv2_port_get_value (lv2plugin, port, has_role_pred);
+      if (slv2_values_size (sub_values) > 0) {
+        SLV2Value role = slv2_values_get_at (sub_values, 0);
+        position = gst_lv2_role_to_position (role);
+        slv2_values_free (sub_values);
+      }
+      if (position != GST_AUDIO_CHANNEL_POSITION_INVALID) {
+        desc.position = position;
+      } else {
+        group->has_roles = FALSE;
+      }
+
       g_array_append_val (group->ports, desc);
 
     } else {
@@ -184,17 +277,35 @@ gst_lv2_base_init (gpointer g_class)
   /* add input group pad templates */
   for (j = 0; j < gsp_class->num_group_in; ++j) {
     group = &g_array_index (klass->in_groups, GstLV2Group, j);
+    if (group->has_roles) {
+      positions = gst_lv2_build_positions (group);
+    }
+
     gst_signal_processor_class_add_pad_template (gsp_class,
         slv2_value_as_string (group->symbol),
-        GST_PAD_SINK, j, group->ports->len);
+        GST_PAD_SINK, j, group->ports->len, positions);
+
+    if (group->has_roles) {
+      free (positions);
+      positions = NULL;
+    }
   }
 
   /* add output group pad templates */
   for (j = 0; j < gsp_class->num_group_out; ++j) {
     group = &g_array_index (klass->out_groups, GstLV2Group, j);
+    if (group->has_roles) {
+      positions = gst_lv2_build_positions (group);
+    }
+
     gst_signal_processor_class_add_pad_template (gsp_class,
         slv2_value_as_string (group->symbol),
-        GST_PAD_SRC, j, group->ports->len);
+        GST_PAD_SRC, j, group->ports->len, positions);
+
+    if (group->has_roles) {
+      free (positions);
+      positions = NULL;
+    }
   }
 
   /* add non-grouped input port pad templates */
@@ -205,7 +316,7 @@ gst_lv2_base_init (gpointer g_class)
     const gchar *name =
         slv2_value_as_string (slv2_port_get_symbol (lv2plugin, port));
     gst_signal_processor_class_add_pad_template (gsp_class, name, GST_PAD_SINK,
-        j, 1);
+        j, 1, &mono_position);
   }
 
   /* add non-grouped output port pad templates */
@@ -216,7 +327,7 @@ gst_lv2_base_init (gpointer g_class)
     const gchar *name =
         slv2_value_as_string (slv2_port_get_symbol (lv2plugin, port));
     gst_signal_processor_class_add_pad_template (gsp_class, name, GST_PAD_SINK,
-        j, 1);
+        j, 1, &mono_position);
   }
 
   /* construct the element details struct */
@@ -651,8 +762,21 @@ plugin_init (GstPlugin * plugin)
   toggled_prop = slv2_value_new_uri (world, NS_LV2 "toggled");
   in_place_broken_pred = slv2_value_new_uri (world, NS_LV2 "inPlaceBroken");
   in_group_pred = slv2_value_new_uri (world, NS_PG "inGroup");
+  has_role_pred = slv2_value_new_uri (world, NS_PG "role");
   lv2_symbol_pred = slv2_value_new_string (world, NS_LV2 "symbol");
 
+  center_role = slv2_value_new_uri (world, NS_PG "centerChannel");
+  left_role = slv2_value_new_uri (world, NS_PG "leftChannel");
+  right_role = slv2_value_new_uri (world, NS_PG "rightChannel");
+  rear_center_role = slv2_value_new_uri (world, NS_PG "rearCenterChannel");
+  rear_left_role = slv2_value_new_uri (world, NS_PG "rearLeftChannel");
+  rear_right_role = slv2_value_new_uri (world, NS_PG "rearRightChannel");
+  lfe_role = slv2_value_new_uri (world, NS_PG "lfeChannel");
+  center_left_role = slv2_value_new_uri (world, NS_PG "centerLeftChannel");
+  center_right_role = slv2_value_new_uri (world, NS_PG "centerRightChannel");
+  side_left_role = slv2_value_new_uri (world, NS_PG "sideLeftChannel");
+  side_right_role = slv2_value_new_uri (world, NS_PG "sideRightChannel");
+
   parent_class = g_type_class_ref (GST_TYPE_SIGNAL_PROCESSOR);
 
   gst_lv2_plugin = plugin;
index fb384cc..85e4532 100644 (file)
@@ -64,11 +64,14 @@ struct _GstLV2Group {
   guint pad; /**< Gst pad index */
   SLV2Value symbol; /**< Gst pad name / LV2 group symbol */
   GArray *ports; /**< Array of GstLV2Port */
+  gboolean has_roles; /**< TRUE iff all ports have a known role */
 };
 
 struct _GstLV2Port {
   gint index; /**< LV2 port index (on LV2 plugin) */
   gint pad; /**< Gst pad index (iff not part of a group) */
+  SLV2Value role; /**< Channel position / port role */
+  GstAudioChannelPosition position; /**< Channel position */
 };
 
 struct _GstLV2Class {
index e502941..d080e4a 100644 (file)
@@ -90,11 +90,14 @@ gst_signal_processor_pad_template_get_type (void)
  * @name: pad name
  * @direction: pad direction (src/sink)
  * @index: index for the pad per direction (starting from 0)
+ * @channels: number of channels in this pad
+ * @positions: array of channel positions in order
  *
  */
 void
 gst_signal_processor_class_add_pad_template (GstSignalProcessorClass * klass,
-    const gchar * name, GstPadDirection direction, guint index, guint channels)
+    const gchar * name, GstPadDirection direction, guint index, guint channels,
+    const GstAudioChannelPosition * pos)
 {
   GstPadTemplate *new;
   GstCaps *caps;
@@ -107,6 +110,9 @@ gst_signal_processor_class_add_pad_template (GstSignalProcessorClass * klass,
       "endianness", G_TYPE_INT, G_BYTE_ORDER,
       "width", G_TYPE_INT, 32, "channels", G_TYPE_INT, channels, NULL);
 
+  if (pos)
+    gst_audio_set_caps_channel_positions_list (caps, pos, channels);
+
   new = g_object_new (gst_signal_processor_pad_template_get_type (),
       "name", name, "name-template", name,
       "direction", direction, "presence", GST_PAD_ALWAYS, "caps", caps, NULL);
index b01a2df..e2e6161 100644 (file)
@@ -25,6 +25,7 @@
 #define __GST_SIGNAL_PROCESSOR_H__
 
 #include <gst/gst.h>
+#include <gst/audio/multichannel.h>
 
 G_BEGIN_DECLS
 
@@ -131,7 +132,8 @@ struct _GstSignalProcessorClass {
 
 GType gst_signal_processor_get_type (void);
 void gst_signal_processor_class_add_pad_template (GstSignalProcessorClass *klass,
-    const gchar *name, GstPadDirection direction, guint index, guint channels);
+    const gchar *name, GstPadDirection direction, guint index, guint channels,
+    const GstAudioChannelPosition *pos);