deinterleave.c 28 KB
Newer Older
1 2 3 4 5
/* 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>
6
 *                    2008 Sebastian Dröge <slomo@circular-chaos.org>
7
 *
8
 * deinterleave.c: deinterleave samples
9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * 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
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
22 23
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
24 25
 */

26 27 28 29 30 31 32
/* TODO: 
 *       - handle changes in number of channels
 *       - handle changes in channel positions
 *       - better capsnego by using a buffer alloc function
 *         and passing downstream caps changes upstream there
 */

33 34
/**
 * SECTION:element-deinterleave
35
 * @see_also: interleave
36 37
 *
 * Splits one interleaved multichannel audio stream into many mono audio streams.
38
 * 
39 40 41 42
 * This element handles all raw audio formats and supports changing the input caps as long as
 * all downstream elements can handle the new caps and the number of channels and the channel
 * positions stay the same. This restriction will be removed in later versions by adding or
 * removing some source pads as required.
43
 * 
44 45
 * In most cases a queue and an audioconvert element should be added after each source pad
 * before further processing of the audio data.
46 47
 * 
 * <refsect2>
48
 * <title>Example launch line</title>
49
 * |[
50
 * gst-launch-1.0 filesrc location=/path/to/file.mp3 ! decodebin ! audioconvert ! "audio/x-raw,channels=2 ! deinterleave name=d  d.src_0 ! queue ! audioconvert ! vorbisenc ! oggmux ! filesink location=channel1.ogg  d.src_1 ! queue ! audioconvert ! vorbisenc ! oggmux ! filesink location=channel2.ogg
51 52 53
 * ]| Decodes an MP3 file and encodes the left and right channel into separate
 * Ogg Vorbis files.
 * |[
54
 * gst-launch-1.0 filesrc location=file.mp3 ! decodebin ! audioconvert ! "audio/x-raw,channels=2" ! deinterleave name=d  interleave name=i ! audioconvert ! wavenc ! filesink location=test.wav    d.src_0 ! queue ! audioconvert ! i.sink_1    d.src_1 ! queue ! audioconvert ! i.sink_0
55 56 57
 * ]| Decodes and deinterleaves a Stereo MP3 file into separate channels and
 * then interleaves the channels again to a WAV file with the channel with the
 * channels exchanged.
58 59 60
 * </refsect2>
 */

61 62 63 64 65
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <gst/gst.h>
66
#include <string.h>
67
#include "deinterleave.h"
68 69 70 71

GST_DEBUG_CATEGORY_STATIC (gst_deinterleave_debug);
#define GST_CAT_DEFAULT gst_deinterleave_debug

Wim Taymans's avatar
Wim Taymans committed
72
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
73 74
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
Philippe Normand's avatar
Philippe Normand committed
75 76
    GST_STATIC_CAPS ("audio/x-raw, "
        "format = (string) " GST_AUDIO_FORMATS_ALL ", "
77
        "rate = (int) [ 1, MAX ], "
Philippe Normand's avatar
Philippe Normand committed
78
        "channels = (int) 1, layout = (string) {non-interleaved, interleaved}"));
79

80 81 82
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
Philippe Normand's avatar
Philippe Normand committed
83 84
    GST_STATIC_CAPS ("audio/x-raw, "
        "format = (string) " GST_AUDIO_FORMATS_ALL ", "
85
        "rate = (int) [ 1, MAX ], "
Philippe Normand's avatar
Philippe Normand committed
86
        "channels = (int) [ 1, MAX ], layout = (string) interleaved"));
87

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
#define MAKE_FUNC(type) \
static void deinterleave_##type (guint##type *out, guint##type *in, \
    guint stride, guint nframes) \
{ \
  gint i; \
  \
  for (i = 0; i < nframes; i++) { \
    out[i] = *in; \
    in += stride; \
  } \
}

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

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

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

Philippe Normand's avatar
Philippe Normand committed
117 118
#define gst_deinterleave_parent_class parent_class
G_DEFINE_TYPE (GstDeinterleave, gst_deinterleave, GST_TYPE_ELEMENT);
119

120 121 122 123 124 125
enum
{
  PROP_0,
  PROP_KEEP_POSITIONS
};

Philippe Normand's avatar
Philippe Normand committed
126 127
static GstFlowReturn gst_deinterleave_chain (GstPad * pad, GstObject * parent,
    GstBuffer * buffer);
128

Philippe Normand's avatar
Philippe Normand committed
129 130
static gboolean gst_deinterleave_sink_setcaps (GstDeinterleave * self,
    GstCaps * caps);
131

Philippe Normand's avatar
Philippe Normand committed
132 133
static GstStateChangeReturn
gst_deinterleave_change_state (GstElement * element, GstStateChange transition);
134

Philippe Normand's avatar
Philippe Normand committed
135 136
static gboolean gst_deinterleave_sink_event (GstPad * pad, GstObject * parent,
    GstEvent * event);
137 138
static gboolean gst_deinterleave_sink_query (GstPad * pad, GstObject * parent,
    GstQuery * query);
139

Philippe Normand's avatar
Philippe Normand committed
140 141
static gboolean gst_deinterleave_src_query (GstPad * pad, GstObject * parent,
    GstQuery * query);
142

143 144 145 146 147
static void gst_deinterleave_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_deinterleave_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec);

148

149 150 151 152 153
static void
gst_deinterleave_finalize (GObject * obj)
{
  GstDeinterleave *self = GST_DEINTERLEAVE (obj);

154 155 156 157 158 159
  if (self->pending_events) {
    g_list_foreach (self->pending_events, (GFunc) gst_mini_object_unref, NULL);
    g_list_free (self->pending_events);
    self->pending_events = NULL;
  }

160 161 162
  G_OBJECT_CLASS (parent_class)->finalize (obj);
}

163
static void
Philippe Normand's avatar
Philippe Normand committed
164
gst_deinterleave_class_init (GstDeinterleaveClass * klass)
165
{
Philippe Normand's avatar
Philippe Normand committed
166 167 168 169 170
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);

  GST_DEBUG_CATEGORY_INIT (gst_deinterleave_debug, "deinterleave", 0,
      "deinterleave element");
171

172 173
  gst_element_class_set_static_metadata (gstelement_class,
      "Audio deinterleaver", "Filter/Converter/Audio",
174
      "Splits one interleaved multichannel audio stream into many mono audio streams",
175
      "Andy Wingo <wingo at pobox.com>, " "Iain <iain@prettypeople.org>, "
176
      "Sebastian Dröge <slomo@circular-chaos.org>");
177

178
  gst_element_class_add_pad_template (gstelement_class,
179
      gst_static_pad_template_get (&sink_template));
180
  gst_element_class_add_pad_template (gstelement_class,
181
      gst_static_pad_template_get (&src_template));
182

Philippe Normand's avatar
Philippe Normand committed
183
  gstelement_class->change_state = gst_deinterleave_change_state;
184 185

  gobject_class->finalize = gst_deinterleave_finalize;
186 187 188
  gobject_class->set_property = gst_deinterleave_set_property;
  gobject_class->get_property = gst_deinterleave_get_property;

189 190 191 192 193 194 195 196 197
  /**
   * GstDeinterleave:keep-positions
   * 
   * Keep positions: When enable the caps on the output buffers will
   * contain the original channel positions. This can be used to correctly
   * interleave the output again later but can also lead to unwanted effects
   * if the output should be handled as Mono.
   *
   */
198 199 200 201
  g_object_class_install_property (gobject_class, PROP_KEEP_POSITIONS,
      g_param_spec_boolean ("keep-positions", "Keep positions",
          "Keep the original channel positions on the output buffers",
          FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
202 203 204
}

static void
Philippe Normand's avatar
Philippe Normand committed
205
gst_deinterleave_init (GstDeinterleave * self)
206
{
207
  self->keep_positions = FALSE;
208
  self->func = NULL;
Philippe Normand's avatar
Philippe Normand committed
209
  gst_audio_info_init (&self->audio_info);
210

211 212
  /* Add sink pad */
  self->sink = gst_pad_new_from_static_template (&sink_template, "sink");
213 214
  gst_pad_set_chain_function (self->sink,
      GST_DEBUG_FUNCPTR (gst_deinterleave_chain));
215 216
  gst_pad_set_event_function (self->sink,
      GST_DEBUG_FUNCPTR (gst_deinterleave_sink_event));
217 218
  gst_pad_set_query_function (self->sink,
      GST_DEBUG_FUNCPTR (gst_deinterleave_sink_query));
219 220 221
  gst_element_add_pad (GST_ELEMENT (self), self->sink);
}

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
typedef struct
{
  GstCaps *caps;
  GstPad *pad;
} CopyStickyEventsData;

static gboolean
copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
{
  CopyStickyEventsData *data = user_data;

  if (GST_EVENT_TYPE (*event) >= GST_EVENT_CAPS && data->caps) {
    gst_pad_set_caps (data->pad, data->caps);
    data->caps = NULL;
  }

  if (GST_EVENT_TYPE (*event) != GST_EVENT_CAPS)
    gst_pad_push_event (data->pad, gst_event_ref (*event));

  return TRUE;
}

244 245 246 247 248 249
static void
gst_deinterleave_add_new_pads (GstDeinterleave * self, GstCaps * caps)
{
  GstPad *pad;
  guint i;

Philippe Normand's avatar
Philippe Normand committed
250
  for (i = 0; i < GST_AUDIO_INFO_CHANNELS (&self->audio_info); i++) {
Wim Taymans's avatar
Wim Taymans committed
251
    gchar *name = g_strdup_printf ("src_%u", i);
252
    GstCaps *srccaps;
Philippe Normand's avatar
Philippe Normand committed
253 254 255
    GstAudioInfo info;
    GstAudioFormat format = GST_AUDIO_INFO_FORMAT (&self->audio_info);
    gint rate = GST_AUDIO_INFO_RATE (&self->audio_info);
256
    GstAudioChannelPosition position = GST_AUDIO_CHANNEL_POSITION_MONO;
257
    CopyStickyEventsData data;
258

Philippe Normand's avatar
Philippe Normand committed
259 260 261
    /* Set channel position if we know it */
    if (self->keep_positions)
      position = GST_AUDIO_INFO_POSITION (&self->audio_info, i);
262

Philippe Normand's avatar
Philippe Normand committed
263 264
    gst_audio_info_init (&info);
    gst_audio_info_set_format (&info, format, rate, 1, &position);
265

Philippe Normand's avatar
Philippe Normand committed
266
    srccaps = gst_audio_info_to_caps (&info);
267

Philippe Normand's avatar
Philippe Normand committed
268 269
    pad = gst_pad_new_from_static_template (&src_template, name);
    g_free (name);
270

271
    gst_pad_use_fixed_caps (pad);
272 273
    gst_pad_set_query_function (pad,
        GST_DEBUG_FUNCPTR (gst_deinterleave_src_query));
274
    gst_pad_set_active (pad, TRUE);
275 276 277 278 279 280

    data.pad = pad;
    data.caps = srccaps;
    gst_pad_sticky_events_foreach (self->sink, copy_sticky_events, &data);
    if (data.caps)
      gst_pad_set_caps (pad, data.caps);
281
    gst_element_add_pad (GST_ELEMENT (self), pad);
282
    self->srcpads = g_list_prepend (self->srcpads, gst_object_ref (pad));
283

Philippe Normand's avatar
Philippe Normand committed
284
    gst_caps_unref (srccaps);
285 286 287
  }

  gst_element_no_more_pads (GST_ELEMENT (self));
288
  self->srcpads = g_list_reverse (self->srcpads);
289 290
}

291
static gboolean
292 293 294 295
gst_deinterleave_set_pads_caps (GstDeinterleave * self, GstCaps * caps)
{
  GList *l;
  gint i;
296
  gboolean ret = TRUE;
297 298 299 300

  for (l = self->srcpads, i = 0; l; l = l->next, i++) {
    GstPad *pad = GST_PAD (l->data);
    GstCaps *srccaps;
Philippe Normand's avatar
Philippe Normand committed
301
    GstAudioInfo info;
302

303 304 305 306
    if (!gst_audio_info_from_caps (&info, caps)) {
      ret = FALSE;
      continue;
    }
Philippe Normand's avatar
Philippe Normand committed
307
    if (self->keep_positions)
308
      GST_AUDIO_INFO_POSITION (&info, 0) =
Philippe Normand's avatar
Philippe Normand committed
309
          GST_AUDIO_INFO_POSITION (&self->audio_info, i);
310

Philippe Normand's avatar
Philippe Normand committed
311
    srccaps = gst_audio_info_to_caps (&info);
312 313

    gst_pad_set_caps (pad, srccaps);
Philippe Normand's avatar
Philippe Normand committed
314
    gst_caps_unref (srccaps);
315
  }
316
  return ret;
317 318
}

319 320 321
static void
gst_deinterleave_remove_pads (GstDeinterleave * self)
{
322
  GList *l;
323

324
  GST_INFO_OBJECT (self, "removing pads");
325

326 327
  for (l = self->srcpads; l; l = l->next) {
    GstPad *pad = GST_PAD (l->data);
328

329 330 331 332 333
    gst_element_remove_pad (GST_ELEMENT_CAST (self), pad);
    gst_object_unref (pad);
  }
  g_list_free (self->srcpads);
  self->srcpads = NULL;
334 335 336 337

  gst_caps_replace (&self->sinkcaps, NULL);
}

338
static gboolean
Philippe Normand's avatar
Philippe Normand committed
339
gst_deinterleave_set_process_function (GstDeinterleave * self)
340
{
Philippe Normand's avatar
Philippe Normand committed
341
  switch (GST_AUDIO_INFO_WIDTH (&self->audio_info)) {
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
    case 8:
      self->func = (GstDeinterleaveFunc) deinterleave_8;
      break;
    case 16:
      self->func = (GstDeinterleaveFunc) deinterleave_16;
      break;
    case 24:
      self->func = (GstDeinterleaveFunc) deinterleave_24;
      break;
    case 32:
      self->func = (GstDeinterleaveFunc) deinterleave_32;
      break;
    case 64:
      self->func = (GstDeinterleaveFunc) deinterleave_64;
      break;
    default:
      return FALSE;
  }
  return TRUE;
}

363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
static gboolean
gst_deinterleave_check_caps_change (GstDeinterleave * self,
    GstAudioInfo * old_info, GstAudioInfo * new_info)
{
  gint i;
  gboolean same_layout = TRUE;
  gboolean was_unpositioned;
  gboolean is_unpositioned = GST_AUDIO_INFO_IS_UNPOSITIONED (new_info);
  gint new_channels = GST_AUDIO_INFO_CHANNELS (new_info);
  gint old_channels;

  was_unpositioned = GST_AUDIO_INFO_IS_UNPOSITIONED (old_info);
  old_channels = GST_AUDIO_INFO_CHANNELS (old_info);

  /* We allow caps changes as long as the number of channels doesn't change
   * and the channel positions stay the same. _getcaps() should've cared
   * for this already but better be safe.
   */
  if (new_channels != old_channels)
    goto cannot_change_caps;

  /* Now check the channel positions. If we had no channel positions
   * and get them or the other way around things have changed.
   * If we had channel positions and get different ones things have
   * changed too of course
   */
  if ((!was_unpositioned && is_unpositioned) || (was_unpositioned
          && !is_unpositioned))
    goto cannot_change_caps;

  if (!is_unpositioned) {
    if (GST_AUDIO_INFO_CHANNELS (old_info) !=
        GST_AUDIO_INFO_CHANNELS (new_info))
      goto cannot_change_caps;
    for (i = 0; i < GST_AUDIO_INFO_CHANNELS (old_info); i++) {
      if (new_info->position[i] != old_info->position[i]) {
        same_layout = FALSE;
        break;
      }
    }
    if (!same_layout)
      goto cannot_change_caps;
  }

  return TRUE;

cannot_change_caps:
  return FALSE;
}

413
static gboolean
Philippe Normand's avatar
Philippe Normand committed
414
gst_deinterleave_sink_setcaps (GstDeinterleave * self, GstCaps * caps)
415
{
416 417
  GstCaps *srccaps;
  GstStructure *s;
418

419 420
  GST_DEBUG_OBJECT (self, "got caps: %" GST_PTR_FORMAT, caps);

Philippe Normand's avatar
Philippe Normand committed
421 422
  if (!gst_audio_info_from_caps (&self->audio_info, caps))
    goto invalid_caps;
423

Philippe Normand's avatar
Philippe Normand committed
424 425
  if (!gst_deinterleave_set_process_function (self))
    goto unsupported_caps;
426

Philippe Normand's avatar
Philippe Normand committed
427 428 429 430
  if (self->sinkcaps && !gst_caps_is_equal (caps, self->sinkcaps)) {
    GstAudioInfo old_info;

    gst_audio_info_init (&old_info);
431 432
    if (!gst_audio_info_from_caps (&old_info, self->sinkcaps))
      goto info_from_caps_failed;
433

434 435
    if (gst_deinterleave_check_caps_change (self, &old_info, &self->audio_info)) {
      if (!gst_deinterleave_set_process_function (self))
436
        goto cannot_change_caps;
437 438
    } else
      goto cannot_change_caps;
439

440 441 442 443 444 445 446 447
  }

  gst_caps_replace (&self->sinkcaps, caps);

  /* Get srcpad caps */
  srccaps = gst_caps_copy (caps);
  s = gst_caps_get_structure (srccaps, 0);
  gst_structure_set (s, "channels", G_TYPE_INT, 1, NULL);
Philippe Normand's avatar
Philippe Normand committed
448
  gst_structure_remove_field (s, "channel-mask");
449 450 451 452

  /* If we already have pads, update the caps otherwise
   * add new pads */
  if (self->srcpads) {
453 454
    if (!gst_deinterleave_set_pads_caps (self, srccaps))
      goto set_caps_failed;
455
  } else {
456 457 458
    gst_deinterleave_add_new_pads (self, srccaps);
  }

459
  gst_caps_unref (srccaps);
460 461 462

  return TRUE;

463
cannot_change_caps:
464
  {
465 466 467
    GST_WARNING_OBJECT (self, "caps change from %" GST_PTR_FORMAT
        " to %" GST_PTR_FORMAT " not supported: channel number or channel "
        "positions change", self->sinkcaps, caps);
468 469
    return FALSE;
  }
470 471 472 473 474
unsupported_caps:
  {
    GST_ERROR_OBJECT (self, "caps not supported: %" GST_PTR_FORMAT, caps);
    return FALSE;
  }
Philippe Normand's avatar
Philippe Normand committed
475
invalid_caps:
476
  {
477
    GST_ERROR_OBJECT (self, "invalid caps");
478 479
    return FALSE;
  }
480 481 482 483 484 485 486 487 488 489 490
set_caps_failed:
  {
    GST_ERROR_OBJECT (self, "set_caps failed");
    gst_caps_unref (srccaps);
    return FALSE;
  }
info_from_caps_failed:
  {
    GST_ERROR_OBJECT (self, "coud not get info from caps");
    return FALSE;
  }
491 492
}

493 494 495 496 497 498 499 500 501
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);
Philippe Normand's avatar
Philippe Normand committed
502
    gst_structure_remove_field (s, "channel-mask");
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
    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);
  }
}

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
static gboolean
gst_deinterleave_sink_acceptcaps (GstPad * pad, GstObject * parent,
    GstCaps * caps)
{
  GstDeinterleave *self = GST_DEINTERLEAVE (parent);
  GstCaps *templ_caps = gst_pad_get_pad_template_caps (pad);
  gboolean ret;

  ret = gst_caps_can_intersect (templ_caps, caps);
  gst_caps_unref (templ_caps);
  if (ret && self->sinkcaps) {
    GstAudioInfo new_info;

    gst_audio_info_init (&new_info);
    if (!gst_audio_info_from_caps (&new_info, caps))
      goto info_from_caps_failed;
    ret =
        gst_deinterleave_check_caps_change (self, &self->audio_info, &new_info);
  }

  return ret;

info_from_caps_failed:
  {
    GST_ERROR_OBJECT (self, "coud not get info from caps");
    return FALSE;
  }
}

552
static GstCaps *
553
gst_deinterleave_getcaps (GstPad * pad, GstObject * parent, GstCaps * filter)
554
{
Philippe Normand's avatar
Philippe Normand committed
555
  GstDeinterleave *self = GST_DEINTERLEAVE (parent);
556 557 558 559 560 561 562 563 564 565 566 567 568 569
  GstCaps *ret;
  GList *l;

  GST_OBJECT_LOCK (self);
  /* Intersect all of our pad template caps with the peer caps of the pad
   * to get all formats that are possible up- and downstream.
   *
   * For the pad for which the caps are requested we don't remove the channel
   * informations as they must be in the returned caps and incompatibilities
   * will be detected here already
   */
  ret = gst_caps_new_any ();
  for (l = GST_ELEMENT (self)->pads; l != NULL; l = l->next) {
    GstPad *ourpad = GST_PAD (l->data);
570
    GstCaps *peercaps = NULL, *ourcaps;
Thiago Santos's avatar
Thiago Santos committed
571
    GstCaps *templ_caps = gst_pad_get_pad_template_caps (ourpad);
572

Thiago Santos's avatar
Thiago Santos committed
573 574
    ourcaps = gst_caps_copy (templ_caps);
    gst_caps_unref (templ_caps);
575 576 577

    if (pad == ourpad) {
      if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK)
Philippe Normand's avatar
Philippe Normand committed
578
        __set_channels (ourcaps, GST_AUDIO_INFO_CHANNELS (&self->audio_info));
579 580 581 582
      else
        __set_channels (ourcaps, 1);
    } else {
      __remove_channels (ourcaps);
583 584 585 586
      /* Only ask for peer caps for other pads than pad
       * as otherwise gst_pad_peer_get_caps() might call
       * back into this function and deadlock
       */
Philippe Normand's avatar
Philippe Normand committed
587 588
      peercaps = gst_pad_peer_query_caps (ourpad, NULL);
      peercaps = gst_caps_make_writable (peercaps);
589 590 591 592 593 594 595 596
    }

    /* If the peer exists and has caps add them to the intersection,
     * otherwise assume that the peer accepts everything */
    if (peercaps) {
      GstCaps *intersection;
      GstCaps *oldret = ret;

597 598
      __remove_channels (peercaps);

599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
      intersection = gst_caps_intersect (peercaps, ourcaps);

      ret = gst_caps_intersect (ret, intersection);
      gst_caps_unref (intersection);
      gst_caps_unref (peercaps);
      gst_caps_unref (oldret);
    } else {
      GstCaps *oldret = ret;

      ret = gst_caps_intersect (ret, ourcaps);
      gst_caps_unref (oldret);
    }
    gst_caps_unref (ourcaps);
  }
  GST_OBJECT_UNLOCK (self);

615 616 617 618 619 620 621 622
  if (filter) {
    GstCaps *aux;

    aux = gst_caps_intersect (ret, filter);
    gst_caps_unref (ret);
    ret = aux;
  }

623 624 625 626 627
  GST_DEBUG_OBJECT (pad, "Intersected caps to %" GST_PTR_FORMAT, ret);

  return ret;
}

628
static gboolean
Philippe Normand's avatar
Philippe Normand committed
629
gst_deinterleave_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
630
{
Philippe Normand's avatar
Philippe Normand committed
631
  GstDeinterleave *self = GST_DEINTERLEAVE (parent);
632 633 634 635 636 637 638 639 640 641 642 643 644
  gboolean ret;

  GST_DEBUG ("Got %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event),
      GST_DEBUG_PAD_NAME (pad));

  /* Send FLUSH_STOP, FLUSH_START and EOS immediately, no matter if
   * we have src pads already or not. Queue all other events and
   * push them after we have src pads
   */
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_FLUSH_STOP:
    case GST_EVENT_FLUSH_START:
    case GST_EVENT_EOS:
Philippe Normand's avatar
Philippe Normand committed
645 646 647 648 649 650 651 652 653
      ret = gst_pad_event_default (pad, parent, event);
      break;
    case GST_EVENT_CAPS:
    {
      GstCaps *caps;

      gst_event_parse_caps (event, &caps);
      ret = gst_deinterleave_sink_setcaps (self, caps);
      gst_event_unref (event);
654
      break;
Philippe Normand's avatar
Philippe Normand committed
655 656
    }

657
    default:
658 659
      if (!self->srcpads && !GST_EVENT_IS_STICKY (event)) {
        /* Sticky events are copied when creating a new pad */
660 661 662 663
        GST_OBJECT_LOCK (self);
        self->pending_events = g_list_append (self->pending_events, event);
        GST_OBJECT_UNLOCK (self);
        ret = TRUE;
664 665
      } else {
        ret = gst_pad_event_default (pad, parent, event);
666 667 668 669 670 671 672
      }
      break;
  }

  return ret;
}

673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
static gboolean
gst_deinterleave_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
  gboolean res;

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

      gst_query_parse_caps (query, &filter);
      caps = gst_deinterleave_getcaps (pad, parent, filter);
      gst_query_set_caps_result (query, caps);
      gst_caps_unref (caps);
      res = TRUE;
      break;
    }
690 691 692 693 694 695 696 697 698 699
    case GST_QUERY_ACCEPT_CAPS:{
      GstCaps *caps;
      gboolean ret;

      gst_query_parse_accept_caps (query, &caps);
      ret = gst_deinterleave_sink_acceptcaps (pad, parent, caps);
      gst_query_set_accept_caps_result (query, ret);
      res = TRUE;
      break;
    }
700 701 702 703 704 705 706 707
    default:
      res = gst_pad_query_default (pad, parent, query);
      break;
  }

  return res;
}

708
static gboolean
Philippe Normand's avatar
Philippe Normand committed
709
gst_deinterleave_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
710
{
Philippe Normand's avatar
Philippe Normand committed
711
  GstDeinterleave *self = GST_DEINTERLEAVE (parent);
712 713
  gboolean res;

Philippe Normand's avatar
Philippe Normand committed
714
  res = gst_pad_query_default (pad, parent, query);
715 716 717 718 719 720 721 722 723 724 725

  if (res && GST_QUERY_TYPE (query) == GST_QUERY_DURATION) {
    GstFormat format;
    gint64 dur;

    gst_query_parse_duration (query, &format, &dur);

    /* Need to divide by the number of channels in byte format
     * to get the correct value. All other formats should be fine
     */
    if (format == GST_FORMAT_BYTES && dur != -1)
Philippe Normand's avatar
Philippe Normand committed
726 727
      gst_query_set_duration (query, format,
          dur / GST_AUDIO_INFO_CHANNELS (&self->audio_info));
728 729 730 731 732 733 734 735 736 737
  } else if (res && GST_QUERY_TYPE (query) == GST_QUERY_POSITION) {
    GstFormat format;
    gint64 pos;

    gst_query_parse_position (query, &format, &pos);

    /* Need to divide by the number of channels in byte format
     * to get the correct value. All other formats should be fine
     */
    if (format == GST_FORMAT_BYTES && pos != -1)
Philippe Normand's avatar
Philippe Normand committed
738 739 740 741 742 743
      gst_query_set_position (query, format,
          pos / GST_AUDIO_INFO_CHANNELS (&self->audio_info));
  } else if (res && GST_QUERY_TYPE (query) == GST_QUERY_CAPS) {
    GstCaps *filter, *caps;

    gst_query_parse_caps (query, &filter);
744
    caps = gst_deinterleave_getcaps (pad, parent, filter);
Philippe Normand's avatar
Philippe Normand committed
745 746
    gst_query_set_caps_result (query, caps);
    gst_caps_unref (caps);
747 748 749 750 751
  }

  return res;
}

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
static void
gst_deinterleave_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstDeinterleave *self = GST_DEINTERLEAVE (object);

  switch (prop_id) {
    case PROP_KEEP_POSITIONS:
      self->keep_positions = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_deinterleave_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstDeinterleave *self = GST_DEINTERLEAVE (object);

  switch (prop_id) {
    case PROP_KEEP_POSITIONS:
      g_value_set_boolean (value, self->keep_positions);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

784 785 786
static GstFlowReturn
gst_deinterleave_process (GstDeinterleave * self, GstBuffer * buf)
{
787
  GstFlowReturn ret = GST_FLOW_OK;
Philippe Normand's avatar
Philippe Normand committed
788
  guint channels = GST_AUDIO_INFO_CHANNELS (&self->audio_info);
789
  guint pads_pushed = 0, buffers_allocated = 0;
Philippe Normand's avatar
Philippe Normand committed
790 791 792 793
  guint nframes =
      gst_buffer_get_size (buf) / channels /
      (GST_AUDIO_INFO_WIDTH (&self->audio_info) / 8);
  guint bufsize = nframes * (GST_AUDIO_INFO_WIDTH (&self->audio_info) / 8);
794
  guint i;
795
  GList *srcs;
796 797
  GstBuffer **buffers_out = g_new0 (GstBuffer *, channels);
  guint8 *in, *out;
Philippe Normand's avatar
Philippe Normand committed
798
  GstMapInfo read_info;
799
  GList *pending_events, *l;
Philippe Normand's avatar
Philippe Normand committed
800

801 802
  /* Send any pending events to all src pads */
  GST_OBJECT_LOCK (self);
803 804 805 806 807
  pending_events = self->pending_events;
  self->pending_events = NULL;
  GST_OBJECT_UNLOCK (self);

  if (pending_events) {
808 809 810
    GstEvent *event;

    GST_DEBUG_OBJECT (self, "Sending pending events to all src pads");
811 812
    for (l = pending_events; l; l = l->next) {
      event = l->data;
813 814 815 816
      for (srcs = self->srcpads; srcs != NULL; srcs = srcs->next)
        gst_pad_push_event (GST_PAD (srcs->data), gst_event_ref (event));
      gst_event_unref (event);
    }
817
    g_list_free (pending_events);
818
  }
819 820

  gst_buffer_map (buf, &read_info, GST_MAP_READ);
821

822
  /* Allocate buffers */
823
  for (srcs = self->srcpads, i = 0; srcs; srcs = srcs->next, i++) {
Wim Taymans's avatar
Wim Taymans committed
824
    buffers_out[i] = gst_buffer_new_allocate (NULL, bufsize, NULL);
825

826 827
    /* Make sure we got a correct buffer. The only other case we allow
     * here is an unliked pad */
Philippe Normand's avatar
Philippe Normand committed
828
    if (!buffers_out[i])
829
      goto alloc_buffer_failed;
Philippe Normand's avatar
Philippe Normand committed
830
    else if (buffers_out[i] && gst_buffer_get_size (buffers_out[i]) != bufsize)
831
      goto alloc_buffer_bad_size;
832

833
    if (buffers_out[i]) {
Philippe Normand's avatar
Philippe Normand committed
834 835
      gst_buffer_copy_into (buffers_out[i], buf, GST_BUFFER_COPY_METADATA, 0,
          -1);
836 837 838 839
      buffers_allocated++;
    }
  }

840
  /* Return NOT_LINKED if no pad was linked */
841
  if (!buffers_allocated) {
842 843
    GST_WARNING_OBJECT (self,
        "Couldn't allocate any buffers because no pad was linked");
844 845
    ret = GST_FLOW_NOT_LINKED;
    goto done;
846 847
  }

848
  /* deinterleave */
849
  for (srcs = self->srcpads, i = 0; srcs; srcs = srcs->next, i++) {
850
    GstPad *pad = (GstPad *) srcs->data;
Philippe Normand's avatar
Philippe Normand committed
851 852 853 854
    GstMapInfo write_info;

    in = (guint8 *) read_info.data;
    in += i * (GST_AUDIO_INFO_WIDTH (&self->audio_info) / 8);
855
    if (buffers_out[i]) {
Philippe Normand's avatar
Philippe Normand committed
856 857
      gst_buffer_map (buffers_out[i], &write_info, GST_MAP_WRITE);
      out = (guint8 *) write_info.data;
858
      self->func (out, in, channels, nframes);
Philippe Normand's avatar
Philippe Normand committed
859 860
      gst_buffer_unmap (buffers_out[i], &write_info);

861 862 863 864 865 866 867 868 869 870 871
      ret = gst_pad_push (pad, buffers_out[i]);
      buffers_out[i] = NULL;
      if (ret == GST_FLOW_OK)
        pads_pushed++;
      else if (ret == GST_FLOW_NOT_LINKED)
        ret = GST_FLOW_OK;
      else
        goto push_failed;
    }
  }

872
  /* Return NOT_LINKED if no pad was linked */
873 874 875
  if (!pads_pushed)
    ret = GST_FLOW_NOT_LINKED;

876 877
  GST_DEBUG_OBJECT (self, "Pushed on %d pads", pads_pushed);

878
done:
Philippe Normand's avatar
Philippe Normand committed
879
  gst_buffer_unmap (buf, &read_info);
880
  gst_buffer_unref (buf);
881
  g_free (buffers_out);
882 883 884 885
  return ret;

alloc_buffer_failed:
  {
886
    GST_WARNING ("gst_pad_alloc_buffer() returned %s", gst_flow_get_name (ret));
887 888 889 890 891 892 893 894 895 896 897
    goto clean_buffers;

  }
alloc_buffer_bad_size:
  {
    GST_WARNING ("called alloc_buffer(), but didn't get requested bytes");
    ret = GST_FLOW_NOT_NEGOTIATED;
    goto clean_buffers;
  }
push_failed:
  {
898
    GST_DEBUG ("push() failed, flow = %s", gst_flow_get_name (ret));
899 900 901 902
    goto clean_buffers;
  }
clean_buffers:
  {
Philippe Normand's avatar
Philippe Normand committed
903
    gst_buffer_unmap (buf, &read_info);
904
    for (i = 0; i < channels; i++) {
905 906
      if (buffers_out[i])
        gst_buffer_unref (buffers_out[i]);
907 908
    }
    gst_buffer_unref (buf);
909
    g_free (buffers_out);
910 911 912 913 914
    return ret;
  }
}

static GstFlowReturn
Philippe Normand's avatar
Philippe Normand committed
915
gst_deinterleave_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
916
{
Philippe Normand's avatar
Philippe Normand committed
917
  GstDeinterleave *self = GST_DEINTERLEAVE (parent);
918 919
  GstFlowReturn ret;

920
  g_return_val_if_fail (self->func != NULL, GST_FLOW_NOT_NEGOTIATED);
Philippe Normand's avatar
Philippe Normand committed
921 922 923 924
  g_return_val_if_fail (GST_AUDIO_INFO_WIDTH (&self->audio_info) > 0,
      GST_FLOW_NOT_NEGOTIATED);
  g_return_val_if_fail (GST_AUDIO_INFO_CHANNELS (&self->audio_info) > 0,
      GST_FLOW_NOT_NEGOTIATED);
925 926 927 928

  ret = gst_deinterleave_process (self, buffer);

  if (ret != GST_FLOW_OK)
929
    GST_DEBUG_OBJECT (self, "flow return: %s", gst_flow_get_name (ret));
930 931 932 933

  return ret;
}

Philippe Normand's avatar
Philippe Normand committed
934 935
static GstStateChangeReturn
gst_deinterleave_change_state (GstElement * element, GstStateChange transition)
936
{
Philippe Normand's avatar
Philippe Normand committed
937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
  GstStateChangeReturn ret;
  GstDeinterleave *self = GST_DEINTERLEAVE (element);

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
      break;
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      gst_deinterleave_remove_pads (self);

      self->func = NULL;

      if (self->pending_events) {
        g_list_foreach (self->pending_events, (GFunc) gst_mini_object_unref,
            NULL);
        g_list_free (self->pending_events);
        self->pending_events = NULL;
      }
      break;
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
      break;
    default:
      break;
959
  }
960

Philippe Normand's avatar
Philippe Normand committed
961
  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
962

Philippe Normand's avatar
Philippe Normand committed
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
  switch (transition) {
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_deinterleave_remove_pads (self);

      self->func = NULL;

      if (self->pending_events) {
        g_list_foreach (self->pending_events, (GFunc) gst_mini_object_unref,
            NULL);
        g_list_free (self->pending_events);
        self->pending_events = NULL;
      }
      break;
    case GST_STATE_CHANGE_READY_TO_NULL:
      break;
    default:
      break;
  }
  return ret;
984
}