mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 10:40:34 +00:00
4f426f6f54
The accumulator is filled by intersecting with all the pad caps, as such it must be initialized with ANY (like it is before the iteration is started) and not to EMPTY. Fixes the CAPS query always returning EMPTY caps when resyncing happened during the query, e.g. because pads were added/removed.
1019 lines
29 KiB
C
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_any ();
|
|
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;
|
|
}
|