gst_plugins_base/gst/audioconvert/gstaudioconvert.c
changeset 16 8e837d1bf446
parent 0 0e761a78d257
child 30 7e817e7e631c
--- a/gst_plugins_base/gst/audioconvert/gstaudioconvert.c	Wed Mar 24 17:58:42 2010 -0500
+++ b/gst_plugins_base/gst/audioconvert/gstaudioconvert.c	Wed Mar 24 18:04:17 2010 -0500
@@ -24,31 +24,20 @@
 /**
  * SECTION:element-audioconvert
  *
- * <refsect2>
- * <para>
  * Audioconvert converts raw audio buffers between various possible formats.
  * It supports integer to float conversion, width/depth conversion,
- * signedness and endianness conversion.
- * </para>
- * <para>
- * Some format conversion are not carried out in an optimal way right now.
- * E.g. converting from double to float would cause a loss of precision.
- * </para>
+ * signedness and endianness conversion and channel transformations.
+ *
+ * <refsect2>
  * <title>Example launch line</title>
- * <para>
- * <programlisting>
+ * |[
  * gst-launch -v -m audiotestsrc ! audioconvert ! audio/x-raw-int,channels=2,width=8,depth=8 ! level ! fakesink silent=TRUE
- * </programlisting>
- * This pipeline converts audio to 8-bit.  The level element shows that
+ * ]| This pipeline converts audio to 8-bit.  The level element shows that
  * the output levels still match the one for a sine wave.
- * </para>
- * <para>
- * <programlisting>
+ * |[
  * gst-launch -v -m audiotestsrc ! audioconvert ! vorbisenc ! fakesink silent=TRUE
- * </programlisting>
- * The vorbis encoder takes float audio data instead of the integer data
+ * ]| The vorbis encoder takes float audio data instead of the integer data
  * generated by audiotestsrc.
- * </para>
  * </refsect2>
  *
  * Last reviewed on 2006-03-02 (0.10.4)
@@ -107,7 +96,8 @@
     const GValue * value, GParamSpec * pspec);
 static void gst_audio_convert_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec);
-
+static gboolean structure_has_fixed_channel_positions (GstStructure * s,
+    gboolean * unpositioned_layout);
 
 /* AudioConvert signals and args */
 enum
@@ -135,37 +125,37 @@
 GST_STATIC_CAPS ( \
   "audio/x-raw-float, " \
     "rate = (int) [ 1, MAX ], " \
-    "channels = (int) [ 1, 8 ], " \
+    "channels = (int) [ 1, MAX ], " \
     "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, " \
     "width = (int) 64;" \
   "audio/x-raw-float, " \
     "rate = (int) [ 1, MAX ], " \
-    "channels = (int) [ 1, 8 ], " \
+    "channels = (int) [ 1, MAX ], " \
     "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, " \
     "width = (int) 32;" \
   "audio/x-raw-int, " \
     "rate = (int) [ 1, MAX ], " \
-    "channels = (int) [ 1, 8 ], " \
+    "channels = (int) [ 1, MAX ], " \
     "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, " \
     "width = (int) 32, " \
     "depth = (int) [ 1, 32 ], " \
     "signed = (boolean) { true, false }; " \
   "audio/x-raw-int, "   \
     "rate = (int) [ 1, MAX ], " \
-    "channels = (int) [ 1, 8 ], "       \
+    "channels = (int) [ 1, MAX ], "       \
     "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, "        \
     "width = (int) 24, "        \
     "depth = (int) [ 1, 24 ], " "signed = (boolean) { true, false }; "  \
   "audio/x-raw-int, " \
     "rate = (int) [ 1, MAX ], " \
-    "channels = (int) [ 1, 8 ], " \
+    "channels = (int) [ 1, MAX ], " \
     "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, " \
     "width = (int) 16, " \
     "depth = (int) [ 1, 16 ], " \
     "signed = (boolean) { true, false }; " \
   "audio/x-raw-int, " \
     "rate = (int) [ 1, MAX ], " \
-    "channels = (int) [ 1, 8 ], " \
+    "channels = (int) [ 1, MAX ], " \
     "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, " \
     "width = (int) 8, " \
     "depth = (int) [ 1, 8 ], " \
@@ -263,13 +253,14 @@
   g_object_class_install_property (gobject_class, ARG_DITHERING,
       g_param_spec_enum ("dithering", "Dithering",
           "Selects between different dithering methods.",
-          GST_TYPE_AUDIO_CONVERT_DITHERING, DITHER_TPDF, G_PARAM_READWRITE));
+          GST_TYPE_AUDIO_CONVERT_DITHERING, DITHER_TPDF,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class, ARG_NOISE_SHAPING,
       g_param_spec_enum ("noise-shaping", "Noise shaping",
           "Selects between different noise shaping methods.",
           GST_TYPE_AUDIO_CONVERT_NOISE_SHAPING, NOISE_SHAPING_NONE,
-          G_PARAM_READWRITE));
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   basetransform_class->get_unit_size =
       GST_DEBUG_FUNCPTR (gst_audio_convert_get_unit_size);
@@ -293,6 +284,8 @@
   this->dither = DITHER_TPDF;
   this->ns = NOISE_SHAPING_NONE;
   memset (&this->ctx, 0, sizeof (AudioConvertCtx));
+
+  gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (this), TRUE);
 }
 
 static void
@@ -330,6 +323,10 @@
     goto no_values;
   if (!(fmt->pos = gst_audio_get_channel_positions (structure)))
     goto no_values;
+
+  fmt->unpositioned_layout = FALSE;
+  structure_has_fixed_channel_positions (structure, &fmt->unpositioned_layout);
+
   if (!gst_structure_get_int (structure, "width", &fmt->width))
     goto no_values;
   if (!gst_structure_get_int (structure, "rate", &fmt->rate))
@@ -529,6 +526,40 @@
   }
 }
 
+static gboolean
+structure_has_fixed_channel_positions (GstStructure * s,
+    gboolean * unpositioned_layout)
+{
+  GstAudioChannelPosition *pos;
+  const GValue *val;
+  gint channels = 0;
+
+  if (!gst_structure_get_int (s, "channels", &channels))
+    return FALSE;               /* probably a range */
+
+  val = gst_structure_get_value (s, "channel-positions");
+  if ((val == NULL || !gst_value_is_fixed (val)) && channels <= 8) {
+    GST_LOG ("no or unfixed channel-positions in %" GST_PTR_FORMAT, s);
+    return FALSE;
+  } else if (val == NULL || !gst_value_is_fixed (val)) {
+    GST_LOG ("implicit undefined channel-positions");
+    *unpositioned_layout = TRUE;
+    return TRUE;
+  }
+
+  pos = gst_audio_get_channel_positions (s);
+  if (pos && pos[0] == GST_AUDIO_CHANNEL_POSITION_NONE) {
+    GST_LOG ("fixed undefined channel-positions in %" GST_PTR_FORMAT, s);
+    *unpositioned_layout = TRUE;
+  } else {
+    GST_LOG ("fixed defined channel-positions in %" GST_PTR_FORMAT, s);
+    *unpositioned_layout = FALSE;
+  }
+  g_free (pos);
+
+  return TRUE;
+}
+
 /* Audioconvert can perform all conversions on audio except for resampling. 
  * However, there are some conversions we _prefer_ not to do. For example, it's
  * better to convert format (float<->int, endianness, etc) than the number of
@@ -546,8 +577,8 @@
 {
   GstCaps *ret;
   GstStructure *s, *structure;
-  gboolean isfloat;
-  gint width, depth, channels;
+  gboolean isfloat, allow_mixing;
+  gint width, depth, channels = 0;
   const gchar *fields_used[] = {
     "width", "depth", "rate", "channels", "endianness", "signed"
   };
@@ -603,11 +634,28 @@
     }
   }
 
+  allow_mixing = TRUE;
   if (gst_structure_get_int (structure, "channels", &channels)) {
-    if (channels == 8)
-      gst_structure_set (s, "channels", G_TYPE_INT, 8, NULL);
+    gboolean unpositioned;
+
+    /* we don't support mixing for channels without channel positions */
+    if (structure_has_fixed_channel_positions (structure, &unpositioned))
+      allow_mixing = (unpositioned == FALSE);
+  }
+
+  if (!allow_mixing) {
+    gst_structure_set (s, "channels", G_TYPE_INT, channels, NULL);
+    if (gst_structure_has_field (structure, "channel-positions"))
+      gst_structure_set_value (s, "channel-positions",
+          gst_structure_get_value (structure, "channel-positions"));
+  } else {
+    if (channels == 0)
+      gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, 1, 11, NULL);
+    else if (channels == 11)
+      gst_structure_set (s, "channels", G_TYPE_INT, 11, NULL);
     else
-      gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, channels, 8, NULL);
+      gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, channels, 11, NULL);
+    gst_structure_remove_field (s, "channel-positions");
   }
   gst_caps_append_structure (ret, s);
 
@@ -636,7 +684,16 @@
    * it's very bad to drop channels entirely.
    */
   s = gst_structure_copy (s);
-  gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, 1, 8, NULL);
+  if (allow_mixing) {
+    gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, 1, 11, NULL);
+    gst_structure_remove_field (s, "channel-positions");
+  } else {
+    /* allow_mixing can only be FALSE if we got a fixed number of channels */
+    gst_structure_set (s, "channels", G_TYPE_INT, channels, NULL);
+    if (gst_structure_has_field (structure, "channel-positions"))
+      gst_structure_set_value (s, "channel-positions",
+          gst_structure_get_value (structure, "channel-positions"));
+  }
   gst_caps_append_structure (ret, s);
 
   /* Same, plus a float<->int conversion */
@@ -751,7 +808,7 @@
 gst_audio_convert_fixate_channels (GstBaseTransform * base, GstStructure * ins,
     GstStructure * outs)
 {
-  const GValue *out_layout;
+  const GValue *in_layout, *out_layout;
   gint in_chans, out_chans;
 
   if (!gst_structure_get_int (ins, "channels", &in_chans))
@@ -776,19 +833,18 @@
   /* check if the output has a channel layout (or a list of layouts) */
   out_layout = gst_structure_get_value (outs, "channel-positions");
 
+  /* get the channel layout of the input if any */
+  in_layout = gst_structure_get_value (ins, "channel-positions");
+
   if (out_layout == NULL) {
-    if (out_chans <= 2)
+    if (out_chans <= 2 && (in_chans != out_chans || in_layout == NULL))
       return;                   /* nothing to do, default layout will be assumed */
     GST_WARNING_OBJECT (base, "downstream caps contain no channel layout");
   }
 
-  if (in_chans == out_chans) {
-    const GValue *in_layout;
+  if (in_chans == out_chans && in_layout != NULL) {
     GValue res = { 0, };
 
-    in_layout = gst_structure_get_value (ins, "channel-positions");
-    g_return_if_fail (in_layout != NULL);
-
     /* same number of channels and no output layout: just use input layout */
     if (out_layout == NULL) {
       gst_structure_set_value (outs, "channel-positions", in_layout);
@@ -846,7 +902,7 @@
    * and try to add/remove channels from the input layout, or pick a default
    * layout based on LFE-presence in input layout, but let's save that for
    * another day) */
-  if (out_chans > 0 && out_chans < G_N_ELEMENTS (default_positions[0])) {
+  if (out_chans > 0 && out_chans <= G_N_ELEMENTS (default_positions[0])) {
     GST_DEBUG_OBJECT (base, "using default channel layout as fallback");
     gst_audio_set_channel_positions (outs, default_positions[out_chans - 1]);
   }
@@ -946,6 +1002,79 @@
   return GST_FLOW_OK;
 }
 
+static void
+gst_audio_convert_create_silence_buffer (GstAudioConvert * this, gpointer dst,
+    gint size)
+{
+  if (this->ctx.out.is_int && !this->ctx.out.sign) {
+    gint i;
+
+    switch (this->ctx.out.width) {
+      case 8:{
+        guint8 zero = 0x80 >> (8 - this->ctx.out.depth);
+
+        memset (dst, zero, size);
+        break;
+      }
+      case 16:{
+        guint16 *data = (guint16 *) dst;
+        guint16 zero = 0x8000 >> (16 - this->ctx.out.depth);
+
+        if (this->ctx.out.endianness == G_LITTLE_ENDIAN)
+          zero = GUINT16_TO_LE (zero);
+        else
+          zero = GUINT16_TO_BE (zero);
+
+        size /= 2;
+
+        for (i = 0; i < size; i++)
+          data[i] = zero;
+        break;
+      }
+      case 24:{
+        guint32 zero = 0x800000 >> (24 - this->ctx.out.depth);
+        guint8 *data = (guint8 *) dst;
+
+        if (this->ctx.out.endianness == G_LITTLE_ENDIAN) {
+          for (i = 0; i < size; i += 3) {
+            data[i] = zero & 0xff;
+            data[i + 1] = (zero >> 8) & 0xff;
+            data[i + 2] = (zero >> 16) & 0xff;
+          }
+        } else {
+          for (i = 0; i < size; i += 3) {
+            data[i + 2] = zero & 0xff;
+            data[i + 1] = (zero >> 8) & 0xff;
+            data[i] = (zero >> 16) & 0xff;
+          }
+        }
+        break;
+      }
+      case 32:{
+        guint32 *data = (guint32 *) dst;
+        guint32 zero = (0x80000000 >> (32 - this->ctx.out.depth));
+
+        if (this->ctx.out.endianness == G_LITTLE_ENDIAN)
+          zero = GUINT32_TO_LE (zero);
+        else
+          zero = GUINT32_TO_BE (zero);
+
+        size /= 4;
+
+        for (i = 0; i < size; i++)
+          data[i] = zero;
+        break;
+      }
+      default:
+        memset (dst, 0, size);
+        g_return_if_reached ();
+        break;
+    }
+  } else {
+    memset (dst, 0, size);
+  }
+}
+
 static GstFlowReturn
 gst_audio_convert_transform (GstBaseTransform * base, GstBuffer * inbuf,
     GstBuffer * outbuf)
@@ -978,9 +1107,14 @@
   dst = GST_BUFFER_DATA (outbuf);
 
   /* and convert the samples */
-  if (!(res = audio_convert_convert (&this->ctx, src, dst,
-              samples, gst_buffer_is_writable (inbuf))))
-    goto convert_error;
+  if (!GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP)) {
+    if (!(res = audio_convert_convert (&this->ctx, src, dst,
+                samples, gst_buffer_is_writable (inbuf))))
+      goto convert_error;
+  } else {
+    /* Create silence buffer */
+    gst_audio_convert_create_silence_buffer (this, dst, outsize);
+  }
 
   GST_BUFFER_SIZE (outbuf) = outsize;