/* GStreamer
 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
 *                    2000 Wim Taymans <wtay@chello.be>
 *                    2005 Wim Taymans <wim@fluendo.com>
 *                    2007 Andy Wingo <wingo at pobox.com>
 *                    2008 Sebastian Dröge <slomo@circular-chaos.org>
 *                    2014 Collabora
 *                        Olivier Crete <olivier.crete@collabora.com>
 *
 * gstaudiointerleave.c: audiointerleave element, N in, one out,
 * samples are added
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:element-audiointerleave
 *
 *
 */

/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
 * with newer GLib versions (>= 2.31.0) */
#define GLIB_DISABLE_DEPRECATION_WARNINGS

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstaudiointerleave.h"
#include <gst/audio/audio.h>

#include <string.h>

#define GST_CAT_DEFAULT gst_audio_interleave_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);

enum
{
  PROP_PAD_0,
  PROP_PAD_CHANNEL
};

G_DEFINE_TYPE (GstAudioInterleavePad, gst_audio_interleave_pad,
    GST_TYPE_AUDIO_AGGREGATOR_PAD);

static void
gst_audio_interleave_pad_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstAudioInterleavePad *pad = GST_AUDIO_INTERLEAVE_PAD (object);

  switch (prop_id) {
    case PROP_PAD_CHANNEL:
      g_value_set_uint (value, pad->channel);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}


static void
gst_audio_interleave_pad_class_init (GstAudioInterleavePadClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;

  gobject_class->get_property = gst_audio_interleave_pad_get_property;

  g_object_class_install_property (gobject_class,
      PROP_PAD_CHANNEL,
      g_param_spec_uint ("channel",
          "Channel number",
          "Number of the channel of this pad in the output", 0, G_MAXUINT, 0,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

static void
gst_audio_interleave_pad_init (GstAudioInterleavePad * pad)
{
}

enum
{
  PROP_0,
  PROP_CHANNEL_POSITIONS,
  PROP_CHANNEL_POSITIONS_FROM_INPUT
};

/* elementfactory information */

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define CAPS \
  GST_AUDIO_CAPS_MAKE ("{ S32LE, U32LE, S16LE, U16LE, S8, U8, F32LE, F64LE }") \
  ", layout = (string) { interleaved, non-interleaved }"
#else
#define CAPS \
  GST_AUDIO_CAPS_MAKE ("{ S32BE, U32BE, S16BE, U16BE, S8, U8, F32BE, F64BE }") \
  ", layout = (string) { interleaved, non-interleaved }"
#endif

static GstStaticPadTemplate gst_audio_interleave_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink_%u",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS ("audio/x-raw, "
        "rate = (int) [ 1, MAX ], "
        "channels = (int) 1, "
        "format = (string) " GST_AUDIO_FORMATS_ALL ", "
        "layout = (string) {non-interleaved, interleaved}")
    );

static GstStaticPadTemplate gst_audio_interleave_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw, "
        "rate = (int) [ 1, MAX ], "
        "channels = (int) [ 1, MAX ], "
        "format = (string) " GST_AUDIO_FORMATS_ALL ", "
        "layout = (string) interleaved")
    );

static void gst_audio_interleave_child_proxy_init (gpointer g_iface,
    gpointer iface_data);

#define gst_audio_interleave_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstAudioInterleave, gst_audio_interleave,
    GST_TYPE_AUDIO_AGGREGATOR, G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
        gst_audio_interleave_child_proxy_init));

static void gst_audio_interleave_finalize (GObject * object);
static void gst_audio_interleave_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_audio_interleave_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

static gboolean gst_audio_interleave_setcaps (GstAudioInterleave * self,
    GstPad * pad, GstCaps * caps);
static GstPad *gst_audio_interleave_request_new_pad (GstElement * element,
    GstPadTemplate * temp, const gchar * req_name, const GstCaps * caps);
static void gst_audio_interleave_release_pad (GstElement * element,
    GstPad * pad);

static gboolean gst_audio_interleave_stop (GstAggregator * agg);

static gboolean
gst_audio_interleave_aggregate_one_buffer (GstAudioAggregator * aagg,
    GstAudioAggregatorPad * aaggpad, GstBuffer * inbuf, guint in_offset,
    GstBuffer * outbuf, guint out_offset, guint num_samples);


static void
__remove_channels (GstCaps * caps)
{
  GstStructure *s;
  gint i, size;

  size = gst_caps_get_size (caps);
  for (i = 0; i < size; i++) {
    s = gst_caps_get_structure (caps, i);
    gst_structure_remove_field (s, "channel-mask");
    gst_structure_remove_field (s, "channels");
  }
}

static void
__set_channels (GstCaps * caps, gint channels)
{
  GstStructure *s;
  gint i, size;

  size = gst_caps_get_size (caps);
  for (i = 0; i < size; i++) {
    s = gst_caps_get_structure (caps, i);
    if (channels > 0)
      gst_structure_set (s, "channels", G_TYPE_INT, channels, NULL);
    else
      gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
  }
}

/* we can only accept caps that we and downstream can handle.
 * if we have filtercaps set, use those to constrain the target caps.
 */
static GstCaps *
gst_audio_interleave_sink_getcaps (GstAggregator * agg, GstPad * pad,
    GstCaps * filter)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (agg);
  GstCaps *result = NULL, *peercaps, *sinkcaps;

  GST_OBJECT_LOCK (self);
  /* If we already have caps on one of the sink pads return them */
  if (self->sinkcaps)
    result = gst_caps_copy (self->sinkcaps);
  GST_OBJECT_UNLOCK (self);

  if (result == NULL) {
    /* get the downstream possible caps */
    peercaps = gst_pad_peer_query_caps (agg->srcpad, NULL);

    /* get the allowed caps on this sinkpad */
    sinkcaps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
    __remove_channels (sinkcaps);
    if (peercaps) {
      peercaps = gst_caps_make_writable (peercaps);
      __remove_channels (peercaps);
      /* if the peer has caps, intersect */
      GST_DEBUG_OBJECT (pad, "intersecting peer and template caps");
      result = gst_caps_intersect (peercaps, sinkcaps);
      gst_caps_unref (peercaps);
      gst_caps_unref (sinkcaps);
    } else {
      /* the peer has no caps (or there is no peer), just use the allowed caps
       * of this sinkpad. */
      GST_DEBUG_OBJECT (pad, "no peer caps, using sinkcaps");
      result = sinkcaps;
    }
    __set_channels (result, 1);
  }

  if (filter != NULL) {
    GstCaps *caps = result;

    GST_LOG_OBJECT (pad, "intersecting filter caps %" GST_PTR_FORMAT " with "
        "preliminary result %" GST_PTR_FORMAT, filter, caps);

    result = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
    gst_caps_unref (caps);
  }

  GST_DEBUG_OBJECT (pad, "Returning caps %" GST_PTR_FORMAT, result);

  return result;
}

static gboolean
gst_audio_interleave_sink_query (GstAggregator * agg, GstAggregatorPad * aggpad,
    GstQuery * query)
{
  gboolean res = FALSE;

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_CAPS:
    {
      GstCaps *filter, *caps;

      gst_query_parse_caps (query, &filter);
      caps = gst_audio_interleave_sink_getcaps (agg, GST_PAD (aggpad), filter);
      gst_query_set_caps_result (query, caps);
      gst_caps_unref (caps);
      res = TRUE;
      break;
    }
    default:
      res =
          GST_AGGREGATOR_CLASS (parent_class)->sink_query (agg, aggpad, query);
      break;
  }

  return res;
}

static gint
compare_positions (gconstpointer a, gconstpointer b, gpointer user_data)
{
  const gint i = *(const gint *) a;
  const gint j = *(const gint *) b;
  const gint *pos = (const gint *) user_data;

  if (pos[i] < pos[j])
    return -1;
  else if (pos[i] > pos[j])
    return 1;
  else
    return 0;
}

static gboolean
gst_audio_interleave_channel_positions_to_mask (GValueArray * positions,
    gint default_ordering_map[64], guint64 * mask)
{
  gint i;
  guint channels;
  GstAudioChannelPosition *pos;
  gboolean ret;

  channels = positions->n_values;
  pos = g_new (GstAudioChannelPosition, channels);

  for (i = 0; i < channels; i++) {
    GValue *val;

    val = g_value_array_get_nth (positions, i);
    pos[i] = g_value_get_enum (val);
  }

  /* sort the default ordering map according to the position order */
  for (i = 0; i < channels; i++) {
    default_ordering_map[i] = i;
  }
  g_qsort_with_data (default_ordering_map, channels,
      sizeof (*default_ordering_map), compare_positions, pos);

  ret = gst_audio_channel_positions_to_mask (pos, channels, FALSE, mask);
  g_free (pos);

  return ret;
}


/* Must be called with the object lock held */

static guint64
gst_audio_interleave_get_channel_mask (GstAudioInterleave * self)
{
  guint64 channel_mask = 0;

  if (self->channel_positions != NULL &&
      self->channels == self->channel_positions->n_values) {
    if (!gst_audio_interleave_channel_positions_to_mask
        (self->channel_positions, self->default_channels_ordering_map,
            &channel_mask)) {
      GST_WARNING_OBJECT (self, "Invalid channel positions, using NONE");
      channel_mask = 0;
    }
  } else {
    GST_WARNING_OBJECT (self, "Using NONE channel positions");
  }

  return channel_mask;
}


#define MAKE_FUNC(type) \
static void interleave_##type (guint##type *out, guint##type *in, \
    guint stride, guint nframes) \
{ \
  gint i; \
  \
  for (i = 0; i < nframes; i++) { \
    *out = in[i]; \
    out += stride; \
  } \
}

MAKE_FUNC (8);
MAKE_FUNC (16);
MAKE_FUNC (32);
MAKE_FUNC (64);

static void
interleave_24 (guint8 * out, guint8 * in, guint stride, guint nframes)
{
  gint i;

  for (i = 0; i < nframes; i++) {
    memcpy (out, in, 3);
    out += stride * 3;
    in += 3;
  }
}

static void
gst_audio_interleave_set_process_function (GstAudioInterleave * self,
    GstAudioInfo * info)
{
  switch (GST_AUDIO_INFO_WIDTH (info)) {
    case 8:
      self->func = (GstInterleaveFunc) interleave_8;
      break;
    case 16:
      self->func = (GstInterleaveFunc) interleave_16;
      break;
    case 24:
      self->func = (GstInterleaveFunc) interleave_24;
      break;
    case 32:
      self->func = (GstInterleaveFunc) interleave_32;
      break;
    case 64:
      self->func = (GstInterleaveFunc) interleave_64;
      break;
    default:
      g_assert_not_reached ();
      break;
  }
}


/* the first caps we receive on any of the sinkpads will define the caps for all
 * the other sinkpads because we can only mix streams with the same caps.
 */
static gboolean
gst_audio_interleave_setcaps (GstAudioInterleave * self, GstPad * pad,
    GstCaps * caps)
{
  GstAudioAggregator *aagg = GST_AUDIO_AGGREGATOR (self);
  GstAudioInfo info;
  GValue *val;
  guint channel;
  gboolean new = FALSE;

  if (!gst_audio_info_from_caps (&info, caps))
    goto invalid_format;

  GST_OBJECT_LOCK (self);
  if (self->sinkcaps && !gst_caps_is_subset (caps, self->sinkcaps))
    goto cannot_change_caps;

  if (!self->sinkcaps) {
    GstCaps *sinkcaps = gst_caps_copy (caps);
    GstStructure *s = gst_caps_get_structure (sinkcaps, 0);

    gst_structure_remove_field (s, "channel-mask");

    GST_DEBUG_OBJECT (self, "setting sinkcaps %" GST_PTR_FORMAT, sinkcaps);

    gst_caps_replace (&self->sinkcaps, sinkcaps);

    gst_caps_unref (sinkcaps);
    new = TRUE;
    self->new_caps = TRUE;
  }

  if (self->channel_positions_from_input
      && GST_AUDIO_INFO_CHANNELS (&info) == 1) {
    channel = GST_AUDIO_INTERLEAVE_PAD (pad)->channel;
    val = g_value_array_get_nth (self->input_channel_positions, channel);
    g_value_set_enum (val, GST_AUDIO_INFO_POSITION (&info, 0));
  }
  GST_OBJECT_UNLOCK (self);

  gst_audio_aggregator_set_sink_caps (aagg, GST_AUDIO_AGGREGATOR_PAD (pad),
      caps);

  if (!new)
    return TRUE;

  GST_INFO_OBJECT (pad, "handle caps change to %" GST_PTR_FORMAT, caps);

  return TRUE;

  /* ERRORS */
invalid_format:
  {
    GST_WARNING_OBJECT (self, "invalid format set as caps: %" GST_PTR_FORMAT,
        caps);
    return FALSE;
  }
cannot_change_caps:
  {
    GST_OBJECT_UNLOCK (self);
    GST_WARNING_OBJECT (self, "caps of %" GST_PTR_FORMAT " already set, can't "
        "change", self->sinkcaps);
    return FALSE;
  }
}

static gboolean
gst_audio_interleave_sink_event (GstAggregator * agg, GstAggregatorPad * aggpad,
    GstEvent * event)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (agg);
  gboolean res = TRUE;

  GST_DEBUG_OBJECT (aggpad, "Got %s event on sink pad",
      GST_EVENT_TYPE_NAME (event));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
    {
      GstCaps *caps;

      gst_event_parse_caps (event, &caps);
      res = gst_audio_interleave_setcaps (self, GST_PAD_CAST (aggpad), caps);
      gst_event_unref (event);
      event = NULL;
      break;
    }
    default:
      break;
  }

  if (event != NULL)
    return GST_AGGREGATOR_CLASS (parent_class)->sink_event (agg, aggpad, event);

  return res;
}

static GstFlowReturn
gst_audio_interleave_aggregate (GstAggregator * aggregator, gboolean timeout)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (aggregator);
  GstAudioAggregator *aagg = GST_AUDIO_AGGREGATOR (aggregator);

  GST_OBJECT_LOCK (aggregator);
  if (self->new_caps) {
    GstCaps *srccaps;
    GstStructure *s;
    gboolean ret;

    if (self->sinkcaps == NULL || self->channels == 0) {
      /* In this case, let the base class handle it */
      goto not_negotiated;
    }

    srccaps = gst_caps_copy (self->sinkcaps);
    s = gst_caps_get_structure (srccaps, 0);

    gst_structure_set (s, "channels", G_TYPE_INT, self->channels, "layout",
        G_TYPE_STRING, "interleaved", "channel-mask", GST_TYPE_BITMASK,
        gst_audio_interleave_get_channel_mask (self), NULL);


    GST_OBJECT_UNLOCK (aggregator);
    ret = gst_audio_aggregator_set_src_caps (aagg, srccaps);
    gst_caps_unref (srccaps);

    if (!ret)
      goto src_did_not_accept;

    GST_OBJECT_LOCK (aggregator);

    gst_audio_interleave_set_process_function (self, &aagg->info);

    self->new_caps = FALSE;
  }

not_negotiated:
  GST_OBJECT_UNLOCK (aggregator);

  return GST_AGGREGATOR_CLASS (parent_class)->aggregate (aggregator, timeout);

src_did_not_accept:
  GST_WARNING_OBJECT (self, "src did not accept setcaps()");
  return GST_FLOW_NOT_NEGOTIATED;;
}

static void
gst_audio_interleave_class_init (GstAudioInterleaveClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstElementClass *gstelement_class = (GstElementClass *) klass;
  GstAggregatorClass *agg_class = (GstAggregatorClass *) klass;
  GstAudioAggregatorClass *aagg_class = (GstAudioAggregatorClass *) klass;

  GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "audiointerleave", 0,
      "audio interleaving element");

  gobject_class->set_property = gst_audio_interleave_set_property;
  gobject_class->get_property = gst_audio_interleave_get_property;
  gobject_class->finalize = gst_audio_interleave_finalize;

  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_audio_interleave_src_template);
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_audio_interleave_sink_template);
  gst_element_class_set_static_metadata (gstelement_class, "AudioInterleave",
      "Generic/Audio", "Mixes multiple audio streams",
      "Olivier Crete <olivier.crete@collabora.com>");

  gstelement_class->request_new_pad =
      GST_DEBUG_FUNCPTR (gst_audio_interleave_request_new_pad);
  gstelement_class->release_pad =
      GST_DEBUG_FUNCPTR (gst_audio_interleave_release_pad);


  agg_class->sinkpads_type = GST_TYPE_AUDIO_INTERLEAVE_PAD;

  agg_class->sink_query = GST_DEBUG_FUNCPTR (gst_audio_interleave_sink_query);
  agg_class->sink_event = GST_DEBUG_FUNCPTR (gst_audio_interleave_sink_event);
  agg_class->stop = gst_audio_interleave_stop;
  agg_class->aggregate = gst_audio_interleave_aggregate;

  aagg_class->aggregate_one_buffer = gst_audio_interleave_aggregate_one_buffer;


  /**
   * GstInterleave:channel-positions
   * 
   * Channel positions: This property controls the channel positions
   * that are used on the src caps. The number of elements should be
   * the same as the number of sink pads and the array should contain
   * a valid list of channel positions. The n-th element of the array
   * is the position of the n-th sink pad.
   *
   * These channel positions will only be used if they're valid and the
   * number of elements is the same as the number of channels. If this
   * is not given a NONE layout will be used.
   *
   */
  g_object_class_install_property (gobject_class, PROP_CHANNEL_POSITIONS,
      g_param_spec_value_array ("channel-positions", "Channel positions",
          "Channel positions used on the output",
          g_param_spec_enum ("channel-position", "Channel position",
              "Channel position of the n-th input",
              GST_TYPE_AUDIO_CHANNEL_POSITION,
              GST_AUDIO_CHANNEL_POSITION_NONE,
              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS),
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstInterleave:channel-positions-from-input
   * 
   * Channel positions from input: If this property is set to %TRUE the channel
   * positions will be taken from the input caps if valid channel positions for
   * the output can be constructed from them. If this is set to %TRUE setting the
   * channel-positions property overwrites this property again.
   *
   */
  g_object_class_install_property (gobject_class,
      PROP_CHANNEL_POSITIONS_FROM_INPUT,
      g_param_spec_boolean ("channel-positions-from-input",
          "Channel positions from input",
          "Take channel positions from the input", TRUE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gst_audio_interleave_init (GstAudioInterleave * self)
{
  self->input_channel_positions = g_value_array_new (0);
  self->channel_positions_from_input = TRUE;
  self->channel_positions = self->input_channel_positions;
}

static void
gst_audio_interleave_finalize (GObject * object)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (object);

  if (self->channel_positions
      && self->channel_positions != self->input_channel_positions) {
    g_value_array_free (self->channel_positions);
    self->channel_positions = NULL;
  }

  if (self->input_channel_positions) {
    g_value_array_free (self->input_channel_positions);
    self->input_channel_positions = NULL;
  }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_audio_interleave_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (object);

  switch (prop_id) {
    case PROP_CHANNEL_POSITIONS:
      g_return_if_fail (
          ((GValueArray *) g_value_get_boxed (value))->n_values > 0);

      if (self->channel_positions &&
          self->channel_positions != self->input_channel_positions)
        g_value_array_free (self->channel_positions);

      self->channel_positions = g_value_dup_boxed (value);
      self->channel_positions_from_input = FALSE;
      break;
    case PROP_CHANNEL_POSITIONS_FROM_INPUT:
      self->channel_positions_from_input = g_value_get_boolean (value);

      if (self->channel_positions_from_input) {
        if (self->channel_positions &&
            self->channel_positions != self->input_channel_positions)
          g_value_array_free (self->channel_positions);
        self->channel_positions = self->input_channel_positions;
      }
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_audio_interleave_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (object);

  switch (prop_id) {
    case PROP_CHANNEL_POSITIONS:
      g_value_set_boxed (value, self->channel_positions);
      break;
    case PROP_CHANNEL_POSITIONS_FROM_INPUT:
      g_value_set_boolean (value, self->channel_positions_from_input);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static gboolean
gst_audio_interleave_stop (GstAggregator * agg)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (agg);

  if (!GST_AGGREGATOR_CLASS (parent_class)->stop (agg))
    return FALSE;

  self->new_caps = FALSE;
  gst_caps_replace (&self->sinkcaps, NULL);

  return TRUE;
}

static GstPad *
gst_audio_interleave_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (element);
  GstAudioInterleavePad *newpad;
  gchar *pad_name;
  gint channel, padnumber;
  GValue val = { 0, };

  /* FIXME: We ignore req_name, this is evil! */

  padnumber = g_atomic_int_add (&self->padcounter, 1);
  channel = g_atomic_int_add (&self->channels, 1);
  if (!self->channel_positions_from_input)
    channel = padnumber;

  pad_name = g_strdup_printf ("sink_%u", padnumber);
  newpad = (GstAudioInterleavePad *)
      GST_ELEMENT_CLASS (parent_class)->request_new_pad (element,
      templ, pad_name, caps);
  g_free (pad_name);
  if (newpad == NULL)
    goto could_not_create;

  newpad->channel = channel;
  gst_pad_use_fixed_caps (GST_PAD (newpad));

  gst_child_proxy_child_added (GST_CHILD_PROXY (element), G_OBJECT (newpad),
      GST_OBJECT_NAME (newpad));


  g_value_init (&val, GST_TYPE_AUDIO_CHANNEL_POSITION);
  g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_NONE);
  self->input_channel_positions =
      g_value_array_append (self->input_channel_positions, &val);
  g_value_unset (&val);

  /* Update the src caps if we already have them */
  GST_OBJECT_LOCK (self);
  self->new_caps = TRUE;
  GST_OBJECT_UNLOCK (self);

  return GST_PAD_CAST (newpad);

could_not_create:
  {
    GST_DEBUG_OBJECT (element, "could not create/add  pad");
    return NULL;
  }
}

static void
gst_audio_interleave_release_pad (GstElement * element, GstPad * pad)
{
  GstAudioInterleave *self;
  gint position;
  GList *l;

  self = GST_AUDIO_INTERLEAVE (element);

  /* Take lock to make sure we're not changing this when processing buffers */
  GST_OBJECT_LOCK (self);

  g_atomic_int_add (&self->channels, -1);

  position = GST_AUDIO_INTERLEAVE_PAD (pad)->channel;
  g_value_array_remove (self->input_channel_positions, position);

  /* Update channel numbers */
  /* Taken above, GST_OBJECT_LOCK (self); */
  for (l = GST_ELEMENT_CAST (self)->sinkpads; l != NULL; l = l->next) {
    GstAudioInterleavePad *ipad = GST_AUDIO_INTERLEAVE_PAD (l->data);

    if (GST_AUDIO_INTERLEAVE_PAD (pad)->channel < ipad->channel)
      ipad->channel--;
  }

  self->new_caps = TRUE;
  GST_OBJECT_UNLOCK (self);


  GST_DEBUG_OBJECT (self, "release pad %s:%s", GST_DEBUG_PAD_NAME (pad));

  gst_child_proxy_child_removed (GST_CHILD_PROXY (self), G_OBJECT (pad),
      GST_OBJECT_NAME (pad));

  GST_ELEMENT_CLASS (parent_class)->release_pad (element, pad);
}


/* Called with object lock and pad object lock held */
static gboolean
gst_audio_interleave_aggregate_one_buffer (GstAudioAggregator * aagg,
    GstAudioAggregatorPad * aaggpad, GstBuffer * inbuf, guint in_offset,
    GstBuffer * outbuf, guint out_offset, guint num_frames)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (aagg);
  GstAudioInterleavePad *pad = GST_AUDIO_INTERLEAVE_PAD (aaggpad);
  GstMapInfo inmap;
  GstMapInfo outmap;
  gint out_width, in_bpf, out_bpf, out_channels;
  guint8 *outdata;

  out_width = GST_AUDIO_INFO_WIDTH (&aagg->info) / 8;
  in_bpf = GST_AUDIO_INFO_BPF (&aaggpad->info);
  out_bpf = GST_AUDIO_INFO_BPF (&aagg->info);
  out_channels = GST_AUDIO_INFO_CHANNELS (&aagg->info);

  gst_buffer_map (outbuf, &outmap, GST_MAP_READWRITE);
  gst_buffer_map (inbuf, &inmap, GST_MAP_READ);
  GST_LOG_OBJECT (pad, "interleaves %u frames on channel %d/%d at offset %u"
      " from offset %u", num_frames, pad->channel, out_channels,
      out_offset * out_bpf, in_offset * in_bpf);

  outdata = outmap.data + (out_offset * out_bpf) +
      (out_width * self->default_channels_ordering_map[pad->channel]);


  self->func (outdata, inmap.data + (in_offset * in_bpf), out_channels,
      num_frames);


  gst_buffer_unmap (inbuf, &inmap);
  gst_buffer_unmap (outbuf, &outmap);

  return TRUE;
}


/* GstChildProxy implementation */
static GObject *
gst_audio_interleave_child_proxy_get_child_by_index (GstChildProxy *
    child_proxy, guint index)
{
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (child_proxy);
  GObject *obj = NULL;

  GST_OBJECT_LOCK (self);
  obj = g_list_nth_data (GST_ELEMENT_CAST (self)->sinkpads, index);
  if (obj)
    gst_object_ref (obj);
  GST_OBJECT_UNLOCK (self);

  return obj;
}

static guint
gst_audio_interleave_child_proxy_get_children_count (GstChildProxy *
    child_proxy)
{
  guint count = 0;
  GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (child_proxy);

  GST_OBJECT_LOCK (self);
  count = GST_ELEMENT_CAST (self)->numsinkpads;
  GST_OBJECT_UNLOCK (self);
  GST_INFO_OBJECT (self, "Children Count: %d", count);

  return count;
}

static void
gst_audio_interleave_child_proxy_init (gpointer g_iface, gpointer iface_data)
{
  GstChildProxyInterface *iface = g_iface;

  GST_INFO ("intializing child proxy interface");
  iface->get_child_by_index =
      gst_audio_interleave_child_proxy_get_child_by_index;
  iface->get_children_count =
      gst_audio_interleave_child_proxy_get_children_count;
}
