| /* 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 |
| * @title: 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->channels <= 64 && |
| 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 if (self->channels <= 64) { |
| 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_pad_mark_reconfigure (GST_AGGREGATOR_SRC_PAD (aagg)); |
| |
| gst_caps_unref (sinkcaps); |
| new = 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_update_src_caps (GstAggregator * agg, GstCaps * caps, |
| GstCaps ** ret) |
| { |
| GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (agg); |
| GstStructure *s; |
| |
| /* This means that either no caps have been set on the sink pad (if |
| * sinkcaps is NULL) or that there is no sink pad (if channels == 0). |
| */ |
| GST_OBJECT_LOCK (self); |
| if (self->sinkcaps == NULL || self->channels == 0) { |
| GST_OBJECT_UNLOCK (self); |
| return GST_FLOW_NOT_NEGOTIATED; |
| } |
| |
| *ret = gst_caps_copy (self->sinkcaps); |
| s = gst_caps_get_structure (*ret, 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 (self); |
| |
| return GST_FLOW_OK; |
| } |
| |
| static gboolean |
| gst_audio_interleave_negotiated_src_caps (GstAggregator * agg, GstCaps * caps) |
| { |
| GstAudioInterleave *self = GST_AUDIO_INTERLEAVE (agg); |
| GstAudioAggregatorPad *srcpad = GST_AUDIO_AGGREGATOR_PAD (agg->srcpad); |
| |
| if (!GST_AGGREGATOR_CLASS (parent_class)->negotiated_src_caps (agg, caps)) |
| return FALSE; |
| |
| gst_audio_interleave_set_process_function (self, &srcpad->info); |
| |
| return TRUE; |
| } |
| |
| 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_with_gtype (gstelement_class, |
| &gst_audio_interleave_src_template, GST_TYPE_AUDIO_AGGREGATOR_PAD); |
| gst_element_class_add_static_pad_template_with_gtype (gstelement_class, |
| &gst_audio_interleave_sink_template, GST_TYPE_AUDIO_INTERLEAVE_PAD); |
| 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->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->update_src_caps = gst_audio_interleave_update_src_caps; |
| agg_class->negotiated_src_caps = gst_audio_interleave_negotiated_src_caps; |
| |
| 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; |
| |
| 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! */ |
| |
| GST_OBJECT_LOCK (self); |
| padnumber = g_atomic_int_add (&self->padcounter, 1); |
| channel = self->channels++; |
| if (!self->channel_positions_from_input) |
| channel = padnumber; |
| GST_OBJECT_UNLOCK (self); |
| |
| 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_pad_mark_reconfigure (GST_AGGREGATOR_SRC_PAD (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); |
| |
| self->channels--; |
| |
| 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--; |
| } |
| |
| gst_pad_mark_reconfigure (GST_AGGREGATOR_SRC_PAD (self)); |
| 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, channel; |
| guint8 *outdata; |
| GstAggregator *agg = GST_AGGREGATOR (aagg); |
| GstAudioAggregatorPad *srcpad = GST_AUDIO_AGGREGATOR_PAD (agg->srcpad); |
| |
| GST_OBJECT_LOCK (aagg); |
| GST_OBJECT_LOCK (aaggpad); |
| |
| out_width = GST_AUDIO_INFO_WIDTH (&srcpad->info) / 8; |
| in_bpf = GST_AUDIO_INFO_BPF (&aaggpad->info); |
| out_bpf = GST_AUDIO_INFO_BPF (&srcpad->info); |
| out_channels = GST_AUDIO_INFO_CHANNELS (&srcpad->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); |
| |
| if (self->channels > 64) { |
| channel = pad->channel; |
| } else { |
| channel = self->default_channels_ordering_map[pad->channel]; |
| } |
| |
| outdata = outmap.data + (out_offset * out_bpf) + (out_width * channel); |
| |
| |
| self->func (outdata, inmap.data + (in_offset * in_bpf), out_channels, |
| num_frames); |
| |
| |
| gst_buffer_unmap (inbuf, &inmap); |
| gst_buffer_unmap (outbuf, &outmap); |
| |
| GST_OBJECT_UNLOCK (aaggpad); |
| GST_OBJECT_UNLOCK (aagg); |
| |
| 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; |
| } |