gstreamer/gst/interleave/interleave.c
Philippe Normand 2bd77e1c8a interleave: set src pad caps upon last sink pad CAPS event
Gather caps on all sink pads before setting the src pad caps. This is
specially needed when the audio channel mapping is set on the sink
pads and the element needs to preserve it on its src pad.

https://bugzilla.gnome.org/show_bug.cgi?id=690267
2012-12-18 12:58:43 +01:00

1355 lines
39 KiB
C

/* 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.rg>
*
* interleave.c: interleave samples, mostly based on adder.
*
* 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.
*/
/* TODO:
* - handle caps changes
* - handle more queries/events
*/
/**
* SECTION:element-interleave
* @see_also: deinterleave
*
* Merges separate mono inputs into one interleaved stream.
*
* This element handles all raw floating point sample formats and all signed integer sample formats. The first
* caps on one of the sinkpads will set the caps of the output so usually an audioconvert element should be
* placed before every sinkpad of interleave.
*
* It's possible to change the number of channels while the pipeline is running by adding or removing
* some of the request pads but this will change the caps of the output buffers. Changing the input
* caps is _not_ supported yet.
*
* The channel number of every sinkpad in the out can be retrieved from the "channel" property of the pad.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* 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
* ]| 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.
* |[
* gst-launch-1.0 interleave name=i ! audioconvert ! wavenc ! filesink location=file.wav filesrc location=file1.wav ! decodebin ! audioconvert ! "audio/x-raw,channels=1" ! queue ! i.sink_0 filesrc location=file2.wav ! decodebin ! audioconvert ! "audio/x-raw,channels=1" ! queue ! i.sink_1
* ]| Interleaves two Mono WAV files to a single Stereo WAV file.
* </refsect2>
*/
/* 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 <gst/gst.h>
#include <string.h>
#include "interleave.h"
#include <gst/audio/audio.h>
#include <gst/audio/audio-enumtypes.h>
GST_DEBUG_CATEGORY_STATIC (gst_interleave_debug);
#define GST_CAT_DEFAULT gst_interleave_debug
static GstStaticPadTemplate 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 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")
);
#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;
}
}
typedef struct
{
GstPad parent;
guint channel;
} GstInterleavePad;
enum
{
PROP_PAD_0,
PROP_PAD_CHANNEL
};
static void gst_interleave_pad_class_init (GstPadClass * klass);
#define GST_TYPE_INTERLEAVE_PAD (gst_interleave_pad_get_type())
#define GST_INTERLEAVE_PAD(pad) (G_TYPE_CHECK_INSTANCE_CAST((pad),GST_TYPE_INTERLEAVE_PAD,GstInterleavePad))
#define GST_INTERLEAVE_PAD_CAST(pad) ((GstInterleavePad *) pad)
#define GST_IS_INTERLEAVE_PAD(pad) (G_TYPE_CHECK_INSTANCE_TYPE((pad),GST_TYPE_INTERLEAVE_PAD))
static GType
gst_interleave_pad_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0)) {
type = g_type_register_static_simple (GST_TYPE_PAD,
g_intern_static_string ("GstInterleavePad"), sizeof (GstPadClass),
(GClassInitFunc) gst_interleave_pad_class_init,
sizeof (GstInterleavePad), NULL, 0);
}
return type;
}
static void
gst_interleave_pad_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstInterleavePad *self = GST_INTERLEAVE_PAD (object);
switch (prop_id) {
case PROP_PAD_CHANNEL:
g_value_set_uint (value, self->channel);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_interleave_pad_class_init (GstPadClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
gobject_class->get_property = gst_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));
}
#define gst_interleave_parent_class parent_class
G_DEFINE_TYPE (GstInterleave, gst_interleave, GST_TYPE_ELEMENT);
enum
{
PROP_0,
PROP_CHANNEL_POSITIONS,
PROP_CHANNEL_POSITIONS_FROM_INPUT
};
static void gst_interleave_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_interleave_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static GstPad *gst_interleave_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
static void gst_interleave_release_pad (GstElement * element, GstPad * pad);
static GstStateChangeReturn gst_interleave_change_state (GstElement * element,
GstStateChange transition);
static gboolean gst_interleave_src_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static gboolean gst_interleave_src_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_interleave_sink_event (GstCollectPads * pads,
GstCollectData * data, GstEvent * event, gpointer user_data);
static gboolean gst_interleave_sink_setcaps (GstInterleave * self,
GstPad * pad, const GstCaps * caps, const GstAudioInfo * info);
static GstCaps *gst_interleave_sink_getcaps (GstPad * pad, GstObject * parent,
GstCaps * filter);
static GstFlowReturn gst_interleave_collected (GstCollectPads * pads,
GstInterleave * self);
static void
gst_interleave_finalize (GObject * object)
{
GstInterleave *self = GST_INTERLEAVE (object);
if (self->collect) {
gst_object_unref (self->collect);
self->collect = NULL;
}
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;
}
gst_caps_replace (&self->sinkcaps, NULL);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_interleave_channel_positions_to_mask (GValueArray * positions,
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);
}
ret = gst_audio_channel_positions_to_mask (pos, channels, FALSE, mask);
g_free (pos);
return ret;
}
static void
gst_interleave_set_channel_positions (GstInterleave * self, GstStructure * s)
{
guint64 channel_mask = 0;
if (self->channel_positions != NULL &&
self->channels == self->channel_positions->n_values) {
if (!gst_interleave_channel_positions_to_mask (self->channel_positions,
&channel_mask)) {
GST_WARNING_OBJECT (self, "Invalid channel positions, using NONE");
channel_mask = 0;
}
} else {
GST_WARNING_OBJECT (self, "Using NONE channel positions");
}
gst_structure_set (s, "channel-mask", GST_TYPE_BITMASK, channel_mask, NULL);
}
static void
gst_interleave_class_init (GstInterleaveClass * klass)
{
GstElementClass *gstelement_class;
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (gst_interleave_debug, "interleave", 0,
"interleave element");
gst_element_class_set_static_metadata (gstelement_class, "Audio interleaver",
"Filter/Converter/Audio",
"Folds many mono channels into one interleaved audio stream",
"Andy Wingo <wingo at pobox.com>, "
"Sebastian Dröge <slomo@circular-chaos.org>");
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&sink_template));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&src_template));
/* Reference GstInterleavePad class to have the type registered from
* a threadsafe context
*/
g_type_class_ref (GST_TYPE_INTERLEAVE_PAD);
gobject_class->finalize = gst_interleave_finalize;
gobject_class->set_property = gst_interleave_set_property;
gobject_class->get_property = gst_interleave_get_property;
/**
* 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));
gstelement_class->request_new_pad =
GST_DEBUG_FUNCPTR (gst_interleave_request_new_pad);
gstelement_class->release_pad =
GST_DEBUG_FUNCPTR (gst_interleave_release_pad);
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_interleave_change_state);
}
static void
gst_interleave_init (GstInterleave * self)
{
self->src = gst_pad_new_from_static_template (&src_template, "src");
gst_pad_set_query_function (self->src,
GST_DEBUG_FUNCPTR (gst_interleave_src_query));
gst_pad_set_event_function (self->src,
GST_DEBUG_FUNCPTR (gst_interleave_src_event));
gst_pad_set_active (self->src, TRUE);
gst_element_add_pad (GST_ELEMENT (self), self->src);
self->collect = gst_collect_pads_new ();
gst_collect_pads_set_function (self->collect,
(GstCollectPadsFunction) gst_interleave_collected, 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_interleave_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstInterleave *self = GST_INTERLEAVE (object);
switch (prop_id) {
case PROP_CHANNEL_POSITIONS:
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;
self->channels = self->channel_positions->n_values;
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_interleave_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstInterleave *self = GST_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 GstPad *
gst_interleave_request_new_pad (GstElement * element, GstPadTemplate * templ,
const gchar * req_name, const GstCaps * caps)
{
GstInterleave *self = GST_INTERLEAVE (element);
GstPad *new_pad;
gchar *pad_name;
gint channels, padnumber;
GValue val = { 0, };
if (templ->direction != GST_PAD_SINK)
goto not_sink_pad;
padnumber = g_atomic_int_add (&self->padcounter, 1);
if (self->channel_positions_from_input)
channels = g_atomic_int_add (&self->channels, 1);
else
channels = padnumber;
pad_name = g_strdup_printf ("sink_%u", padnumber);
new_pad = GST_PAD_CAST (g_object_new (GST_TYPE_INTERLEAVE_PAD,
"name", pad_name, "direction", templ->direction,
"template", templ, NULL));
GST_INTERLEAVE_PAD_CAST (new_pad)->channel = channels;
GST_DEBUG_OBJECT (self, "requested new pad %s", pad_name);
g_free (pad_name);
gst_pad_use_fixed_caps (new_pad);
gst_collect_pads_add_pad (self->collect, new_pad, sizeof (GstCollectData),
NULL, TRUE);
gst_collect_pads_set_event_function (self->collect,
(GstCollectPadsEventFunction)
GST_DEBUG_FUNCPTR (gst_interleave_sink_event), self);
if (!gst_element_add_pad (element, new_pad))
goto could_not_add;
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 */
if (self->sinkcaps) {
GstCaps *srccaps;
GstStructure *s;
/* Take lock to make sure processing finishes first */
GST_OBJECT_LOCK (self->collect);
srccaps = gst_caps_copy (self->sinkcaps);
s = gst_caps_get_structure (srccaps, 0);
gst_structure_set (s, "channels", G_TYPE_INT, self->channels, NULL);
gst_interleave_set_channel_positions (self, s);
/* FIXME: send caps event after stream-start event */
gst_pad_set_active (self->src, TRUE);
gst_pad_set_caps (self->src, srccaps);
gst_caps_unref (srccaps);
GST_OBJECT_UNLOCK (self->collect);
}
return new_pad;
/* errors */
not_sink_pad:
{
g_warning ("interleave: requested new pad that is not a SINK pad\n");
return NULL;
}
could_not_add:
{
GST_DEBUG_OBJECT (self, "could not add pad %s", GST_PAD_NAME (new_pad));
gst_collect_pads_remove_pad (self->collect, new_pad);
gst_object_unref (new_pad);
return NULL;
}
}
static void
gst_interleave_release_pad (GstElement * element, GstPad * pad)
{
GstInterleave *self = GST_INTERLEAVE (element);
GList *l;
GstAudioChannelPosition position;
g_return_if_fail (GST_IS_INTERLEAVE_PAD (pad));
/* Take lock to make sure we're not changing this when processing buffers */
GST_OBJECT_LOCK (self->collect);
g_atomic_int_add (&self->channels, -1);
if (gst_pad_has_current_caps (pad))
g_atomic_int_add (&self->configured_sinkpads_counter, -1);
position = GST_INTERLEAVE_PAD_CAST (pad)->channel;
g_value_array_remove (self->input_channel_positions, position);
/* Update channel numbers */
GST_OBJECT_LOCK (self);
for (l = GST_ELEMENT_CAST (self)->sinkpads; l != NULL; l = l->next) {
GstInterleavePad *ipad = GST_INTERLEAVE_PAD (l->data);
if (GST_INTERLEAVE_PAD_CAST (pad)->channel < ipad->channel)
ipad->channel--;
}
GST_OBJECT_UNLOCK (self);
/* Update the src caps if we already have them */
if (self->sinkcaps) {
if (self->channels > 0) {
GstCaps *srccaps;
GstStructure *s;
srccaps = gst_caps_copy (self->sinkcaps);
s = gst_caps_get_structure (srccaps, 0);
gst_structure_set (s, "channels", G_TYPE_INT, self->channels, NULL);
gst_interleave_set_channel_positions (self, s);
gst_pad_set_active (self->src, TRUE);
gst_pad_set_caps (self->src, srccaps);
gst_caps_unref (srccaps);
} else {
gst_caps_replace (&self->sinkcaps, NULL);
}
}
GST_OBJECT_UNLOCK (self->collect);
gst_collect_pads_remove_pad (self->collect, pad);
gst_element_remove_pad (element, pad);
}
static GstStateChangeReturn
gst_interleave_change_state (GstElement * element, GstStateChange transition)
{
GstInterleave *self;
GstStateChangeReturn ret;
self = GST_INTERLEAVE (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
self->timestamp = 0;
self->offset = 0;
gst_event_replace (&self->pending_segment, NULL);
self->send_stream_start = TRUE;
gst_collect_pads_start (self->collect);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
default:
break;
}
/* Stop before calling the parent's state change function as
* GstCollectPads might take locks and we would deadlock in that
* case
*/
if (transition == GST_STATE_CHANGE_PAUSED_TO_READY)
gst_collect_pads_stop (self->collect);
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_caps_replace (&self->sinkcaps, NULL);
gst_event_replace (&self->pending_segment, NULL);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
return ret;
}
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. */
static GstCaps *
gst_interleave_sink_getcaps (GstPad * pad, GstObject * parent, GstCaps * filter)
{
GstInterleave *self = GST_INTERLEAVE (parent);
GstCaps *result, *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);
} else {
/* get the downstream possible caps */
peercaps = gst_pad_peer_query_caps (self->src, NULL);
/* get the allowed caps on this sinkpad */
sinkcaps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
__remove_channels (sinkcaps);
if (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);
}
GST_OBJECT_UNLOCK (self);
GST_DEBUG_OBJECT (pad, "Returning caps %" GST_PTR_FORMAT, result);
return result;
}
static void
gst_interleave_set_process_function (GstInterleave * self)
{
switch (self->width) {
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;
}
}
static gboolean
gst_interleave_sink_setcaps (GstInterleave * self, GstPad * pad,
const GstCaps * caps, const GstAudioInfo * info)
{
g_return_val_if_fail (GST_IS_INTERLEAVE_PAD (pad), FALSE);
/* TODO: handle caps changes */
if (self->sinkcaps && !gst_caps_is_subset (caps, self->sinkcaps)) {
goto cannot_change_caps;
} else {
GstCaps *srccaps;
GstStructure *s;
gboolean res;
self->width = GST_AUDIO_INFO_WIDTH (info);
self->rate = GST_AUDIO_INFO_RATE (info);
gst_interleave_set_process_function (self);
srccaps = gst_caps_copy (caps);
s = gst_caps_get_structure (srccaps, 0);
gst_structure_remove_field (s, "channel-mask");
gst_structure_set (s, "channels", G_TYPE_INT, self->channels, NULL);
gst_interleave_set_channel_positions (self, s);
gst_pad_set_active (self->src, TRUE);
res = gst_pad_set_caps (self->src, srccaps);
gst_caps_unref (srccaps);
if (!res)
goto src_did_not_accept;
}
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);
}
return TRUE;
cannot_change_caps:
{
GST_WARNING_OBJECT (self, "caps of %" GST_PTR_FORMAT " already set, can't "
"change", self->sinkcaps);
return FALSE;
}
src_did_not_accept:
{
GST_WARNING_OBJECT (self, "src did not accept setcaps()");
return FALSE;
}
}
static gboolean
gst_interleave_sink_event (GstCollectPads * pads, GstCollectData * data,
GstEvent * event, gpointer user_data)
{
GstInterleave *self = GST_INTERLEAVE (user_data);
gboolean ret = TRUE;
GST_DEBUG ("Got %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event),
GST_DEBUG_PAD_NAME (data->pad));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_STOP:
GST_OBJECT_LOCK (self);
gst_event_replace (&self->pending_segment, NULL);
GST_OBJECT_UNLOCK (self);
break;
case GST_EVENT_SEGMENT:
{
GST_OBJECT_LOCK (self);
gst_event_replace (&self->pending_segment, event);
GST_OBJECT_UNLOCK (self);
break;
}
case GST_EVENT_CAPS:
{
GstCaps *caps;
GstAudioInfo info;
GValue *val;
guint channel;
gst_event_parse_caps (event, &caps);
if (!gst_audio_info_from_caps (&info, caps)) {
GST_WARNING_OBJECT (self, "invalid sink caps");
gst_event_unref (event);
event = NULL;
ret = FALSE;
break;
}
if (self->channel_positions_from_input
&& GST_AUDIO_INFO_CHANNELS (&info) == 1) {
channel = GST_INTERLEAVE_PAD_CAST (data->pad)->channel;
val = g_value_array_get_nth (self->input_channel_positions, channel);
g_value_set_enum (val, GST_AUDIO_INFO_POSITION (&info, 0));
}
if (!gst_pad_has_current_caps (data->pad))
g_atomic_int_add (&self->configured_sinkpads_counter, 1);
/* Last caps that are set on a sink pad are used as output caps */
if (g_atomic_int_get (&self->configured_sinkpads_counter) ==
self->channels) {
ret = gst_interleave_sink_setcaps (self, data->pad, caps, &info);
gst_event_unref (event);
event = NULL;
}
break;
}
case GST_EVENT_TAG:
GST_FIXME_OBJECT (self, "FIXME: merge tags and send after stream-start");
break;
default:
break;
}
/* now GstCollectPads can take care of the rest, e.g. EOS */
if (event != NULL)
return gst_collect_pads_event_default (pads, data, event, FALSE);
return ret;
}
static gboolean
gst_interleave_src_query_duration (GstInterleave * self, GstQuery * query)
{
gint64 max;
gboolean res;
GstFormat format;
GstIterator *it;
gboolean done;
/* parse format */
gst_query_parse_duration (query, &format, NULL);
max = -1;
res = TRUE;
done = FALSE;
/* Take maximum of all durations */
it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (self));
while (!done) {
GstIteratorResult ires;
GValue item = { 0, };
ires = gst_iterator_next (it, &item);
switch (ires) {
case GST_ITERATOR_DONE:
done = TRUE;
break;
case GST_ITERATOR_OK:
{
GstPad *pad = GST_PAD_CAST (g_value_dup_object (&item));
gint64 duration;
/* ask sink peer for duration */
res &= gst_pad_peer_query_duration (pad, format, &duration);
/* take max from all valid return values */
if (res) {
/* valid unknown length, stop searching */
if (duration == -1) {
max = duration;
done = TRUE;
}
/* else see if bigger than current max */
else if (duration > max)
max = duration;
}
gst_object_unref (pad);
g_value_unset (&item);
break;
}
case GST_ITERATOR_RESYNC:
max = -1;
res = TRUE;
gst_iterator_resync (it);
break;
default:
res = FALSE;
done = TRUE;
break;
}
}
gst_iterator_free (it);
if (res) {
/* If in bytes format we have to multiply with the number of channels
* to get the correct results. All other formats should be fine */
if (format == GST_FORMAT_BYTES && max != -1)
max *= self->channels;
/* and store the max */
GST_DEBUG_OBJECT (self, "Total duration in format %s: %"
GST_TIME_FORMAT, gst_format_get_name (format), GST_TIME_ARGS (max));
gst_query_set_duration (query, format, max);
}
return res;
}
static gboolean
gst_interleave_src_query_latency (GstInterleave * self, GstQuery * query)
{
GstClockTime min, max;
gboolean live;
gboolean res;
GstIterator *it;
gboolean done;
res = TRUE;
done = FALSE;
live = FALSE;
min = 0;
max = GST_CLOCK_TIME_NONE;
/* Take maximum of all latency values */
it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (self));
while (!done) {
GstIteratorResult ires;
GValue item = { 0, };
ires = gst_iterator_next (it, &item);
switch (ires) {
case GST_ITERATOR_DONE:
done = TRUE;
break;
case GST_ITERATOR_OK:
{
GstPad *pad = GST_PAD_CAST (g_value_dup_object (&item));
GstQuery *peerquery;
GstClockTime min_cur, max_cur;
gboolean live_cur;
peerquery = gst_query_new_latency ();
/* Ask peer for latency */
res &= gst_pad_peer_query (pad, peerquery);
/* take max from all valid return values */
if (res) {
gst_query_parse_latency (peerquery, &live_cur, &min_cur, &max_cur);
if (min_cur > min)
min = min_cur;
if (max_cur != GST_CLOCK_TIME_NONE &&
((max != GST_CLOCK_TIME_NONE && max_cur > max) ||
(max == GST_CLOCK_TIME_NONE)))
max = max_cur;
live = live || live_cur;
}
gst_query_unref (peerquery);
gst_object_unref (pad);
g_value_unset (&item);
break;
}
case GST_ITERATOR_RESYNC:
live = FALSE;
min = 0;
max = GST_CLOCK_TIME_NONE;
res = TRUE;
gst_iterator_resync (it);
break;
default:
res = FALSE;
done = TRUE;
break;
}
}
gst_iterator_free (it);
if (res) {
/* store the results */
GST_DEBUG_OBJECT (self, "Calculated total latency: live %s, min %"
GST_TIME_FORMAT ", max %" GST_TIME_FORMAT,
(live ? "yes" : "no"), GST_TIME_ARGS (min), GST_TIME_ARGS (max));
gst_query_set_latency (query, live, min, max);
}
return res;
}
static gboolean
gst_interleave_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstInterleave *self = GST_INTERLEAVE (parent);
gboolean res = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
{
GstFormat format;
gst_query_parse_position (query, &format, NULL);
switch (format) {
case GST_FORMAT_TIME:
/* FIXME, bring to stream time, might be tricky */
gst_query_set_position (query, format, self->timestamp);
res = TRUE;
break;
case GST_FORMAT_BYTES:
gst_query_set_position (query, format,
self->offset * self->channels * self->width);
res = TRUE;
break;
case GST_FORMAT_DEFAULT:
gst_query_set_position (query, format, self->offset);
res = TRUE;
break;
default:
break;
}
break;
}
case GST_QUERY_DURATION:
res = gst_interleave_src_query_duration (self, query);
break;
case GST_QUERY_LATENCY:
res = gst_interleave_src_query_latency (self, query);
break;
case GST_QUERY_CAPS:
{
GstCaps *filter, *caps;
gst_query_parse_caps (query, &filter);
caps = gst_interleave_sink_getcaps (pad, parent, filter);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
res = TRUE;
}
default:
/* FIXME, needs a custom query handler because we have multiple
* sinkpads */
res = gst_pad_query_default (pad, parent, query);
break;
}
return res;
}
static gboolean
forward_event_func (const GValue * item, GValue * ret, GstEvent * event)
{
GstPad *pad = GST_PAD_CAST (g_value_dup_object (item));
gst_event_ref (event);
GST_LOG_OBJECT (pad, "About to send event %s", GST_EVENT_TYPE_NAME (event));
if (!gst_pad_push_event (pad, event)) {
g_value_set_boolean (ret, FALSE);
GST_WARNING_OBJECT (pad, "Sending event %p (%s) failed.",
event, GST_EVENT_TYPE_NAME (event));
} else {
GST_LOG_OBJECT (pad, "Sent event %p (%s).",
event, GST_EVENT_TYPE_NAME (event));
}
gst_object_unref (pad);
return TRUE;
}
static gboolean
forward_event (GstInterleave * self, GstEvent * event)
{
GstIterator *it;
GValue vret = { 0 };
GST_LOG_OBJECT (self, "Forwarding event %p (%s)", event,
GST_EVENT_TYPE_NAME (event));
g_value_init (&vret, G_TYPE_BOOLEAN);
g_value_set_boolean (&vret, TRUE);
it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (self));
gst_iterator_fold (it, (GstIteratorFoldFunction) forward_event_func, &vret,
event);
gst_iterator_free (it);
gst_event_unref (event);
return g_value_get_boolean (&vret);
}
static gboolean
gst_interleave_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstInterleave *self = GST_INTERLEAVE (parent);
gboolean result;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_QOS:
/* QoS might be tricky */
result = FALSE;
break;
case GST_EVENT_SEEK:
{
GstSeekFlags flags;
gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
/* check if we are flushing */
if (flags & GST_SEEK_FLAG_FLUSH) {
/* make sure we accept nothing anymore and return WRONG_STATE */
gst_collect_pads_set_flushing (self->collect, TRUE);
/* flushing seek, start flush downstream, the flush will be done
* when all pads received a FLUSH_STOP. */
gst_pad_push_event (self->src, gst_event_new_flush_start ());
}
result = forward_event (self, event);
break;
}
case GST_EVENT_NAVIGATION:
/* navigation is rather pointless. */
result = FALSE;
break;
default:
/* just forward the rest for now */
result = forward_event (self, event);
break;
}
return result;
}
static GstFlowReturn
gst_interleave_collected (GstCollectPads * pads, GstInterleave * self)
{
guint size;
GstBuffer *outbuf = NULL;
GstFlowReturn ret = GST_FLOW_OK;
GSList *collected;
guint nsamples;
guint ncollected = 0;
gboolean empty = TRUE;
gint width = self->width / 8;
GstMapInfo write_info;
GstClockTime timestamp = -1;
/* FIXME: send caps and tags after stream-start */
#if 0
if (self->send_stream_start) {
gchar s_id[32];
/* stream-start (FIXME: create id based on input ids) */
g_snprintf (s_id, sizeof (s_id), "interleave-%08x", g_random_int ());
gst_pad_push_event (self->src, gst_event_new_stream_start (s_id));
self->send_stream_start = FALSE;
}
#endif
size = gst_collect_pads_available (pads);
if (size == 0)
goto eos;
g_return_val_if_fail (self->func != NULL, GST_FLOW_NOT_NEGOTIATED);
g_return_val_if_fail (self->width > 0, GST_FLOW_NOT_NEGOTIATED);
g_return_val_if_fail (self->channels > 0, GST_FLOW_NOT_NEGOTIATED);
g_return_val_if_fail (self->rate > 0, GST_FLOW_NOT_NEGOTIATED);
g_return_val_if_fail (size % width == 0, GST_FLOW_ERROR);
GST_DEBUG_OBJECT (self, "Starting to collect %u bytes from %d channels", size,
self->channels);
nsamples = size / width;
outbuf = gst_buffer_new_allocate (NULL, size * self->channels, NULL);
if (outbuf == NULL || gst_buffer_get_size (outbuf) < size * self->channels) {
gst_buffer_unref (outbuf);
return GST_FLOW_NOT_NEGOTIATED;
}
gst_buffer_map (outbuf, &write_info, GST_MAP_WRITE);
memset (write_info.data, 0, size * self->channels);
for (collected = pads->data; collected != NULL; collected = collected->next) {
GstCollectData *cdata;
GstBuffer *inbuf;
guint8 *outdata;
GstMapInfo input_info;
cdata = (GstCollectData *) collected->data;
inbuf = gst_collect_pads_take_buffer (pads, cdata, size);
if (inbuf == NULL) {
GST_DEBUG_OBJECT (cdata->pad, "No buffer available");
goto next;
}
ncollected++;
gst_buffer_map (inbuf, &input_info, GST_MAP_READ);
if (timestamp == -1)
timestamp = GST_BUFFER_TIMESTAMP (inbuf);
if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP))
goto next;
empty = FALSE;
outdata =
write_info.data + width * GST_INTERLEAVE_PAD_CAST (cdata->pad)->channel;
self->func (outdata, input_info.data, self->channels, nsamples);
gst_buffer_unmap (inbuf, &input_info);
next:
if (inbuf)
gst_buffer_unref (inbuf);
}
if (ncollected == 0) {
gst_buffer_unmap (outbuf, &write_info);
goto eos;
}
GST_OBJECT_LOCK (self);
if (self->pending_segment) {
GstEvent *event;
GstSegment segment;
event = self->pending_segment;
self->pending_segment = NULL;
GST_OBJECT_UNLOCK (self);
/* convert the input segment to time now */
gst_event_copy_segment (event, &segment);
if (segment.format != GST_FORMAT_TIME) {
gst_event_unref (event);
/* not time, convert */
switch (segment.format) {
case GST_FORMAT_BYTES:
segment.start *= width;
if (segment.stop != -1)
segment.stop *= width;
if (segment.position != -1)
segment.position *= width;
/* fallthrough for the samples case */
case GST_FORMAT_DEFAULT:
segment.start =
gst_util_uint64_scale_int (segment.start, GST_SECOND, self->rate);
if (segment.stop != -1)
segment.stop =
gst_util_uint64_scale_int (segment.stop, GST_SECOND,
self->rate);
if (segment.position != -1)
segment.position =
gst_util_uint64_scale_int (segment.position, GST_SECOND,
self->rate);
break;
default:
GST_WARNING ("can't convert segment values");
segment.start = 0;
segment.stop = -1;
segment.position = 0;
break;
}
event = gst_event_new_segment (&segment);
}
gst_pad_push_event (self->src, event);
GST_OBJECT_LOCK (self);
}
GST_OBJECT_UNLOCK (self);
if (timestamp != -1) {
self->offset = gst_util_uint64_scale_int (timestamp, self->rate,
GST_SECOND);
self->timestamp = timestamp;
}
GST_BUFFER_TIMESTAMP (outbuf) = self->timestamp;
GST_BUFFER_OFFSET (outbuf) = self->offset;
self->offset += nsamples;
self->timestamp = gst_util_uint64_scale_int (self->offset,
GST_SECOND, self->rate);
GST_BUFFER_DURATION (outbuf) =
self->timestamp - GST_BUFFER_TIMESTAMP (outbuf);
if (empty)
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP);
gst_buffer_unmap (outbuf, &write_info);
GST_LOG_OBJECT (self, "pushing outbuf, timestamp %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)));
ret = gst_pad_push (self->src, outbuf);
return ret;
eos:
{
GST_DEBUG_OBJECT (self, "no data available, must be EOS");
if (outbuf)
gst_buffer_unref (outbuf);
gst_pad_push_event (self->src, gst_event_new_eos ());
return GST_FLOW_EOS;
}
}