gstreamer/gst/interleave/deinterleave.c
Sebastian Dröge 239cf06d81 deinterleave: Return the current caps on the srcpads on caps queries
It's not like we could accept any other caps here. The caps are decided by the
upstream caps event.

Also keep the filter order intact when filtering the results against the
filter caps.

https://bugzilla.gnome.org/show_bug.cgi?id=763326
2016-03-24 14:47:40 +02:00

1019 lines
29 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.org>
*
* deinterleave.c: deinterleave samples
*
* 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 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
*/
/**
* SECTION:element-deinterleave
* @see_also: interleave
*
* Splits one interleaved multichannel audio stream into many mono audio streams.
*
* 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.
*
* In most cases a queue and an audioconvert element should be added after each source pad
* before further processing of the audio data.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* 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
* ]| Decodes an MP3 file and encodes the left and right channel into separate
* Ogg Vorbis files.
* |[
* 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.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gst/gst.h>
#include <string.h>
#include "deinterleave.h"
GST_DEBUG_CATEGORY_STATIC (gst_deinterleave_debug);
#define GST_CAT_DEFAULT gst_deinterleave_debug
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("audio/x-raw, "
"format = (string) " GST_AUDIO_FORMATS_ALL ", "
"rate = (int) [ 1, MAX ], "
"channels = (int) 1, layout = (string) {non-interleaved, interleaved}"));
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw, "
"format = (string) " GST_AUDIO_FORMATS_ALL ", "
"rate = (int) [ 1, MAX ], "
"channels = (int) [ 1, MAX ], layout = (string) interleaved"));
#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;
}
}
#define gst_deinterleave_parent_class parent_class
G_DEFINE_TYPE (GstDeinterleave, gst_deinterleave, GST_TYPE_ELEMENT);
enum
{
PROP_0,
PROP_KEEP_POSITIONS
};
static GstFlowReturn gst_deinterleave_chain (GstPad * pad, GstObject * parent,
GstBuffer * buffer);
static gboolean gst_deinterleave_sink_setcaps (GstDeinterleave * self,
GstCaps * caps);
static GstStateChangeReturn
gst_deinterleave_change_state (GstElement * element, GstStateChange transition);
static gboolean gst_deinterleave_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_deinterleave_sink_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static gboolean gst_deinterleave_src_query (GstPad * pad, GstObject * parent,
GstQuery * query);
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);
static void
gst_deinterleave_finalize (GObject * obj)
{
GstDeinterleave *self = GST_DEINTERLEAVE (obj);
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;
}
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
static void
gst_deinterleave_class_init (GstDeinterleaveClass * klass)
{
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");
gst_element_class_set_static_metadata (gstelement_class,
"Audio deinterleaver", "Filter/Converter/Audio",
"Splits one interleaved multichannel audio stream into many mono audio streams",
"Andy Wingo <wingo at pobox.com>, " "Iain <iain@prettypeople.org>, "
"Sebastian Dröge <slomo@circular-chaos.org>");
gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
gstelement_class->change_state = gst_deinterleave_change_state;
gobject_class->finalize = gst_deinterleave_finalize;
gobject_class->set_property = gst_deinterleave_set_property;
gobject_class->get_property = gst_deinterleave_get_property;
/**
* 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.
*
*/
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));
}
static void
gst_deinterleave_init (GstDeinterleave * self)
{
self->keep_positions = FALSE;
self->func = NULL;
gst_audio_info_init (&self->audio_info);
/* Add sink pad */
self->sink = gst_pad_new_from_static_template (&sink_template, "sink");
gst_pad_set_chain_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_chain));
gst_pad_set_event_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_sink_event));
gst_pad_set_query_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_sink_query));
gst_element_add_pad (GST_ELEMENT (self), self->sink);
}
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;
}
static void
gst_deinterleave_add_new_pads (GstDeinterleave * self, GstCaps * caps)
{
GstPad *pad;
guint i;
for (i = 0; i < GST_AUDIO_INFO_CHANNELS (&self->audio_info); i++) {
gchar *name = g_strdup_printf ("src_%u", i);
GstCaps *srccaps;
GstAudioInfo info;
GstAudioFormat format = GST_AUDIO_INFO_FORMAT (&self->audio_info);
gint rate = GST_AUDIO_INFO_RATE (&self->audio_info);
GstAudioChannelPosition position = GST_AUDIO_CHANNEL_POSITION_MONO;
CopyStickyEventsData data;
/* Set channel position if we know it */
if (self->keep_positions)
position = GST_AUDIO_INFO_POSITION (&self->audio_info, i);
gst_audio_info_init (&info);
gst_audio_info_set_format (&info, format, rate, 1, &position);
srccaps = gst_audio_info_to_caps (&info);
pad = gst_pad_new_from_static_template (&src_template, name);
g_free (name);
gst_pad_use_fixed_caps (pad);
gst_pad_set_query_function (pad,
GST_DEBUG_FUNCPTR (gst_deinterleave_src_query));
gst_pad_set_active (pad, TRUE);
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);
gst_element_add_pad (GST_ELEMENT (self), pad);
self->srcpads = g_list_prepend (self->srcpads, gst_object_ref (pad));
gst_caps_unref (srccaps);
}
gst_element_no_more_pads (GST_ELEMENT (self));
self->srcpads = g_list_reverse (self->srcpads);
}
static gboolean
gst_deinterleave_set_pads_caps (GstDeinterleave * self, GstCaps * caps)
{
GList *l;
gint i;
gboolean ret = TRUE;
for (l = self->srcpads, i = 0; l; l = l->next, i++) {
GstPad *pad = GST_PAD (l->data);
GstCaps *srccaps;
GstAudioInfo info;
if (!gst_audio_info_from_caps (&info, caps)) {
ret = FALSE;
continue;
}
if (self->keep_positions)
GST_AUDIO_INFO_POSITION (&info, 0) =
GST_AUDIO_INFO_POSITION (&self->audio_info, i);
srccaps = gst_audio_info_to_caps (&info);
gst_pad_set_caps (pad, srccaps);
gst_caps_unref (srccaps);
}
return ret;
}
static void
gst_deinterleave_remove_pads (GstDeinterleave * self)
{
GList *l;
GST_INFO_OBJECT (self, "removing pads");
for (l = self->srcpads; l; l = l->next) {
GstPad *pad = GST_PAD (l->data);
gst_element_remove_pad (GST_ELEMENT_CAST (self), pad);
gst_object_unref (pad);
}
g_list_free (self->srcpads);
self->srcpads = NULL;
gst_caps_replace (&self->sinkcaps, NULL);
}
static gboolean
gst_deinterleave_set_process_function (GstDeinterleave * self)
{
switch (GST_AUDIO_INFO_WIDTH (&self->audio_info)) {
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;
}
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;
}
static gboolean
gst_deinterleave_sink_setcaps (GstDeinterleave * self, GstCaps * caps)
{
GstCaps *srccaps;
GstStructure *s;
GST_DEBUG_OBJECT (self, "got caps: %" GST_PTR_FORMAT, caps);
if (!gst_audio_info_from_caps (&self->audio_info, caps))
goto invalid_caps;
if (!gst_deinterleave_set_process_function (self))
goto unsupported_caps;
if (self->sinkcaps && !gst_caps_is_equal (caps, self->sinkcaps)) {
GstAudioInfo old_info;
gst_audio_info_init (&old_info);
if (!gst_audio_info_from_caps (&old_info, self->sinkcaps))
goto info_from_caps_failed;
if (gst_deinterleave_check_caps_change (self, &old_info, &self->audio_info)) {
if (!gst_deinterleave_set_process_function (self))
goto cannot_change_caps;
} else
goto cannot_change_caps;
}
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);
gst_structure_remove_field (s, "channel-mask");
/* If we already have pads, update the caps otherwise
* add new pads */
if (self->srcpads) {
if (!gst_deinterleave_set_pads_caps (self, srccaps))
goto set_caps_failed;
} else {
gst_deinterleave_add_new_pads (self, srccaps);
}
gst_caps_unref (srccaps);
return TRUE;
cannot_change_caps:
{
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);
return FALSE;
}
unsupported_caps:
{
GST_ERROR_OBJECT (self, "caps not supported: %" GST_PTR_FORMAT, caps);
return FALSE;
}
invalid_caps:
{
GST_ERROR_OBJECT (self, "invalid caps");
return FALSE;
}
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;
}
}
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);
}
}
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;
}
}
static GstCaps *
gst_deinterleave_getcaps (GstPad * pad, GstObject * parent, GstCaps * filter)
{
GstDeinterleave *self = GST_DEINTERLEAVE (parent);
GstCaps *ret;
GstIterator *it;
GstIteratorResult res;
GValue v = G_VALUE_INIT;
if (pad != self->sink) {
ret = gst_pad_get_current_caps (pad);
if (ret) {
if (filter) {
GstCaps *tmp =
gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (ret);
ret = tmp;
}
return ret;
}
}
/* 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 ();
it = gst_element_iterate_pads (GST_ELEMENT_CAST (self));
do {
res = gst_iterator_next (it, &v);
switch (res) {
case GST_ITERATOR_OK:{
GstPad *ourpad = GST_PAD (g_value_get_object (&v));
GstCaps *peercaps = NULL, *ourcaps;
GstCaps *templ_caps = gst_pad_get_pad_template_caps (ourpad);
ourcaps = gst_caps_copy (templ_caps);
gst_caps_unref (templ_caps);
if (pad == ourpad) {
if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK)
__set_channels (ourcaps,
GST_AUDIO_INFO_CHANNELS (&self->audio_info));
else
__set_channels (ourcaps, 1);
} else {
__remove_channels (ourcaps);
/* 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
*/
peercaps = gst_pad_peer_query_caps (ourpad, NULL);
peercaps = gst_caps_make_writable (peercaps);
}
/* 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;
__remove_channels (peercaps);
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);
g_value_reset (&v);
break;
}
case GST_ITERATOR_DONE:
break;
case GST_ITERATOR_ERROR:
gst_caps_unref (ret);
ret = gst_caps_new_empty ();
break;
case GST_ITERATOR_RESYNC:
gst_caps_unref (ret);
ret = gst_caps_new_empty ();
gst_iterator_resync (it);
break;
}
} while (res != GST_ITERATOR_DONE && res != GST_ITERATOR_ERROR);
g_value_unset (&v);
gst_iterator_free (it);
if (filter) {
GstCaps *aux;
aux = gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (ret);
ret = aux;
}
GST_DEBUG_OBJECT (pad, "Intersected caps to %" GST_PTR_FORMAT, ret);
return ret;
}
static gboolean
gst_deinterleave_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstDeinterleave *self = GST_DEINTERLEAVE (parent);
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:
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);
break;
}
default:
if (!self->srcpads && !GST_EVENT_IS_STICKY (event)) {
/* Sticky events are copied when creating a new pad */
GST_OBJECT_LOCK (self);
self->pending_events = g_list_append (self->pending_events, event);
GST_OBJECT_UNLOCK (self);
ret = TRUE;
} else {
ret = gst_pad_event_default (pad, parent, event);
}
break;
}
return ret;
}
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;
}
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;
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
return res;
}
static gboolean
gst_deinterleave_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstDeinterleave *self = GST_DEINTERLEAVE (parent);
gboolean res;
res = gst_pad_query_default (pad, parent, query);
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)
gst_query_set_duration (query, format,
dur / GST_AUDIO_INFO_CHANNELS (&self->audio_info));
} 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)
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);
caps = gst_deinterleave_getcaps (pad, parent, filter);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
}
return res;
}
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;
}
}
static GstFlowReturn
gst_deinterleave_process (GstDeinterleave * self, GstBuffer * buf)
{
GstFlowReturn ret = GST_FLOW_OK;
guint channels = GST_AUDIO_INFO_CHANNELS (&self->audio_info);
guint pads_pushed = 0, buffers_allocated = 0;
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);
guint i;
GList *srcs;
GstBuffer **buffers_out = g_new0 (GstBuffer *, channels);
guint8 *in, *out;
GstMapInfo read_info;
GList *pending_events, *l;
/* Send any pending events to all src pads */
GST_OBJECT_LOCK (self);
pending_events = self->pending_events;
self->pending_events = NULL;
GST_OBJECT_UNLOCK (self);
if (pending_events) {
GstEvent *event;
GST_DEBUG_OBJECT (self, "Sending pending events to all src pads");
for (l = pending_events; l; l = l->next) {
event = l->data;
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);
}
g_list_free (pending_events);
}
gst_buffer_map (buf, &read_info, GST_MAP_READ);
/* Allocate buffers */
for (srcs = self->srcpads, i = 0; srcs; srcs = srcs->next, i++) {
buffers_out[i] = gst_buffer_new_allocate (NULL, bufsize, NULL);
/* Make sure we got a correct buffer. The only other case we allow
* here is an unliked pad */
if (!buffers_out[i])
goto alloc_buffer_failed;
else if (buffers_out[i]
&& gst_buffer_get_size (buffers_out[i]) != bufsize)
goto alloc_buffer_bad_size;
if (buffers_out[i]) {
gst_buffer_copy_into (buffers_out[i], buf, GST_BUFFER_COPY_METADATA, 0,
-1);
buffers_allocated++;
}
}
/* Return NOT_LINKED if no pad was linked */
if (!buffers_allocated) {
GST_WARNING_OBJECT (self,
"Couldn't allocate any buffers because no pad was linked");
ret = GST_FLOW_NOT_LINKED;
goto done;
}
/* deinterleave */
for (srcs = self->srcpads, i = 0; srcs; srcs = srcs->next, i++) {
GstPad *pad = (GstPad *) srcs->data;
GstMapInfo write_info;
in = (guint8 *) read_info.data;
in += i * (GST_AUDIO_INFO_WIDTH (&self->audio_info) / 8);
if (buffers_out[i]) {
gst_buffer_map (buffers_out[i], &write_info, GST_MAP_WRITE);
out = (guint8 *) write_info.data;
self->func (out, in, channels, nframes);
gst_buffer_unmap (buffers_out[i], &write_info);
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;
}
}
/* Return NOT_LINKED if no pad was linked */
if (!pads_pushed)
ret = GST_FLOW_NOT_LINKED;
GST_DEBUG_OBJECT (self, "Pushed on %d pads", pads_pushed);
done:
gst_buffer_unmap (buf, &read_info);
gst_buffer_unref (buf);
g_free (buffers_out);
return ret;
alloc_buffer_failed:
{
GST_WARNING ("gst_pad_alloc_buffer() returned %s", gst_flow_get_name (ret));
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:
{
GST_DEBUG ("push() failed, flow = %s", gst_flow_get_name (ret));
goto clean_buffers;
}
clean_buffers:
{
gst_buffer_unmap (buf, &read_info);
for (i = 0; i < channels; i++) {
if (buffers_out[i])
gst_buffer_unref (buffers_out[i]);
}
gst_buffer_unref (buf);
g_free (buffers_out);
return ret;
}
}
static GstFlowReturn
gst_deinterleave_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
GstDeinterleave *self = GST_DEINTERLEAVE (parent);
GstFlowReturn ret;
g_return_val_if_fail (self->func != NULL, GST_FLOW_NOT_NEGOTIATED);
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);
ret = gst_deinterleave_process (self, buffer);
if (ret != GST_FLOW_OK)
GST_DEBUG_OBJECT (self, "flow return: %s", gst_flow_get_name (ret));
return ret;
}
static GstStateChangeReturn
gst_deinterleave_change_state (GstElement * element, GstStateChange transition)
{
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;
}
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_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;
}