mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 00:36:51 +00:00
1201 lines
33 KiB
C
1201 lines
33 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>
|
|
*
|
|
* gstsignalprocessor.c:
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
* SECTION:gstsignalprocessor
|
|
*
|
|
* This baseclass allows to write elements that need data on all pads before
|
|
* their processing function can run.
|
|
*
|
|
* In push mode (gst_signal_processor_chain) it operates as follows:
|
|
* 1. store each received buffer on the pad and decrement pending_in
|
|
* 2. when pending_in==0, process as much as we can and push outputs
|
|
*
|
|
* In pull mode (gst_signal_processor_getrange) it operates as follows:
|
|
* 1. if there is an output ready, deliver
|
|
* 2. otherwise pull from each sink-pad, process requested frames and deliver
|
|
* the buffer
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <gst/audio/audio.h>
|
|
#include "gstsignalprocessor.h"
|
|
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_signal_processor_debug);
|
|
#define GST_CAT_DEFAULT gst_signal_processor_debug
|
|
|
|
#define GST_TYPE_SIGNAL_PROCESSOR_PAD_TEMPLATE \
|
|
(gst_signal_processor_pad_template_get_type ())
|
|
#define GST_SIGNAL_PROCESSOR_PAD_TEMPLATE(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SIGNAL_PROCESSOR_PAD_TEMPLATE,\
|
|
GstSignalProcessorPadTemplate))
|
|
typedef struct _GstSignalProcessorPadTemplate GstSignalProcessorPadTemplate;
|
|
typedef GstPadTemplateClass GstSignalProcessorPadTemplateClass;
|
|
|
|
struct _GstSignalProcessorPadTemplate
|
|
{
|
|
GstPadTemplate parent;
|
|
|
|
guint index;
|
|
guint channels;
|
|
};
|
|
|
|
static GType
|
|
gst_signal_processor_pad_template_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (!type) {
|
|
static const GTypeInfo info = {
|
|
sizeof (GstSignalProcessorPadTemplateClass), NULL, NULL, NULL, NULL,
|
|
NULL, sizeof (GstSignalProcessorPadTemplate), 0, NULL
|
|
};
|
|
|
|
type = g_type_register_static (GST_TYPE_PAD_TEMPLATE,
|
|
"GstSignalProcessorPadTemplate", &info, 0);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/*
|
|
* gst_signal_processor_class_add_pad_template:
|
|
* @klass: element class
|
|
* @name: pad name
|
|
* @direction: pad direction (src/sink)
|
|
* @index: index for the pad per direction (starting from 0)
|
|
* @channels: number of channels in this pad
|
|
* @positions: array of channel positions in order
|
|
*
|
|
*/
|
|
void
|
|
gst_signal_processor_class_add_pad_template (GstSignalProcessorClass * klass,
|
|
const gchar * name, GstPadDirection direction, guint index, guint channels)
|
|
{
|
|
GstPadTemplate *new;
|
|
GstCaps *caps;
|
|
|
|
g_return_if_fail (GST_IS_SIGNAL_PROCESSOR_CLASS (klass));
|
|
g_return_if_fail (name != NULL);
|
|
g_return_if_fail (direction == GST_PAD_SRC || direction == GST_PAD_SINK);
|
|
|
|
caps = gst_caps_new_simple ("audio/x-raw-float",
|
|
"endianness", G_TYPE_INT, G_BYTE_ORDER,
|
|
"width", G_TYPE_INT, 32, "channels", G_TYPE_INT, channels,
|
|
"rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
|
|
|
|
new = g_object_new (GST_TYPE_SIGNAL_PROCESSOR_PAD_TEMPLATE,
|
|
"name", name, "name-template", name,
|
|
"direction", direction, "presence", GST_PAD_ALWAYS, "caps", caps, NULL);
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
GST_SIGNAL_PROCESSOR_PAD_TEMPLATE (new)->index = index;
|
|
GST_SIGNAL_PROCESSOR_PAD_TEMPLATE (new)->channels = channels;
|
|
|
|
gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass), new);
|
|
}
|
|
|
|
|
|
#define GST_TYPE_SIGNAL_PROCESSOR_PAD (gst_signal_processor_pad_get_type ())
|
|
#define GST_SIGNAL_PROCESSOR_PAD(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SIGNAL_PROCESSOR_PAD,\
|
|
GstSignalProcessorPad))
|
|
typedef struct _GstSignalProcessorPad GstSignalProcessorPad;
|
|
typedef GstPadClass GstSignalProcessorPadClass;
|
|
|
|
struct _GstSignalProcessorPad
|
|
{
|
|
GstPad parent;
|
|
|
|
GstBuffer *pen;
|
|
GstMapInfo map; /* mapped data to read from / write to */
|
|
|
|
/* index for the pad per direction (starting from 0) */
|
|
guint index;
|
|
|
|
/* number of channels for the pad */
|
|
guint channels;
|
|
|
|
/* these are only used for sink pads */
|
|
guint samples_avail; /* available mono sample frames */
|
|
};
|
|
|
|
static GType
|
|
gst_signal_processor_pad_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (!type) {
|
|
static const GTypeInfo info = {
|
|
sizeof (GstSignalProcessorPadClass), NULL, NULL, NULL, NULL,
|
|
NULL, sizeof (GstSignalProcessorPad), 0, NULL
|
|
};
|
|
|
|
type = g_type_register_static (GST_TYPE_PAD,
|
|
"GstSignalProcessorPad", &info, 0);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
static GstElementClass *parent_class = NULL;
|
|
|
|
static void gst_signal_processor_class_init (GstSignalProcessorClass * klass);
|
|
static void gst_signal_processor_init (GstSignalProcessor * self,
|
|
GstSignalProcessorClass * klass);
|
|
|
|
GType
|
|
gst_signal_processor_get_type (void)
|
|
{
|
|
static volatile gsize signal_processor_type = 0;
|
|
|
|
if (g_once_init_enter (&signal_processor_type)) {
|
|
GType _type;
|
|
static const GTypeInfo signal_processor_info = {
|
|
sizeof (GstSignalProcessorClass),
|
|
NULL,
|
|
NULL,
|
|
(GClassInitFunc) gst_signal_processor_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstSignalProcessor),
|
|
0,
|
|
(GInstanceInitFunc) gst_signal_processor_init,
|
|
};
|
|
|
|
_type = g_type_register_static (GST_TYPE_ELEMENT,
|
|
"GstSignalProcessor", &signal_processor_info, G_TYPE_FLAG_ABSTRACT);
|
|
g_once_init_leave (&signal_processor_type, _type);
|
|
}
|
|
return signal_processor_type;
|
|
}
|
|
|
|
static void gst_signal_processor_finalize (GObject * object);
|
|
static gboolean gst_signal_processor_src_activate_mode (GstPad * pad,
|
|
GstObject * parent, GstPadMode mode, gboolean active);
|
|
static gboolean gst_signal_processor_sink_activate_mode (GstPad * pad,
|
|
GstObject * parent, GstPadMode mode, gboolean active);
|
|
static GstStateChangeReturn gst_signal_processor_change_state (GstElement *
|
|
element, GstStateChange transition);
|
|
|
|
static gboolean gst_signal_processor_event (GstPad * pad, GstObject * parent,
|
|
GstEvent * event);
|
|
static GstFlowReturn gst_signal_processor_getrange (GstPad * pad,
|
|
GstObject * parent, guint64 offset, guint length, GstBuffer ** buffer);
|
|
static GstFlowReturn gst_signal_processor_chain (GstPad * pad,
|
|
GstObject * parent, GstBuffer * buffer);
|
|
static gboolean gst_signal_processor_setcaps (GstPad * pad, GstCaps * caps);
|
|
|
|
|
|
static void
|
|
gst_signal_processor_class_init (GstSignalProcessorClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gstelement_class = GST_ELEMENT_CLASS (klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_signal_processor_finalize);
|
|
|
|
gstelement_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_signal_processor_change_state);
|
|
|
|
GST_DEBUG_REGISTER_FUNCPTR (gst_signal_processor_setcaps);
|
|
GST_DEBUG_REGISTER_FUNCPTR (gst_signal_processor_event);
|
|
GST_DEBUG_REGISTER_FUNCPTR (gst_signal_processor_chain);
|
|
GST_DEBUG_REGISTER_FUNCPTR (gst_signal_processor_sink_activate_mode);
|
|
GST_DEBUG_REGISTER_FUNCPTR (gst_signal_processor_getrange);
|
|
GST_DEBUG_REGISTER_FUNCPTR (gst_signal_processor_src_activate_mode);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_signal_processor_debug, "signalprocessor", 0,
|
|
"signalprocessor baseclass");
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_add_pad_from_template (GstSignalProcessor * self,
|
|
GstPadTemplate * templ)
|
|
{
|
|
GstPad *pad;
|
|
|
|
pad = g_object_new (GST_TYPE_SIGNAL_PROCESSOR_PAD,
|
|
"name", GST_OBJECT_NAME (templ), "direction", templ->direction,
|
|
"template", templ, NULL);
|
|
GST_SIGNAL_PROCESSOR_PAD (pad)->index =
|
|
GST_SIGNAL_PROCESSOR_PAD_TEMPLATE (templ)->index;
|
|
GST_SIGNAL_PROCESSOR_PAD (pad)->channels =
|
|
GST_SIGNAL_PROCESSOR_PAD_TEMPLATE (templ)->channels;
|
|
|
|
if (templ->direction == GST_PAD_SINK) {
|
|
GST_DEBUG_OBJECT (pad, "added new sink pad");
|
|
|
|
gst_pad_set_event_function (pad, gst_signal_processor_event);
|
|
gst_pad_set_chain_function (pad, gst_signal_processor_chain);
|
|
gst_pad_set_activatemode_function (pad,
|
|
gst_signal_processor_sink_activate_mode);
|
|
} else {
|
|
GST_DEBUG_OBJECT (pad, "added new src pad");
|
|
|
|
gst_pad_set_getrange_function (pad, gst_signal_processor_getrange);
|
|
gst_pad_set_activatemode_function (pad,
|
|
gst_signal_processor_src_activate_mode);
|
|
}
|
|
|
|
gst_element_add_pad (GST_ELEMENT (self), pad);
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_init (GstSignalProcessor * self,
|
|
GstSignalProcessorClass * klass)
|
|
{
|
|
GList *templates;
|
|
|
|
templates =
|
|
gst_element_class_get_pad_template_list (GST_ELEMENT_CLASS (klass));
|
|
|
|
while (templates) {
|
|
GstPadTemplate *templ = GST_PAD_TEMPLATE (templates->data);
|
|
gst_signal_processor_add_pad_from_template (self, templ);
|
|
templates = templates->next;
|
|
}
|
|
|
|
self->state = GST_SIGNAL_PROCESSOR_STATE_NULL;
|
|
|
|
self->group_in = g_new0 (GstSignalProcessorGroup, klass->num_group_in);
|
|
self->group_out = g_new0 (GstSignalProcessorGroup, klass->num_group_out);
|
|
self->audio_in = g_new0 (GstMapInfo, klass->num_audio_in);
|
|
self->audio_out = g_new0 (GstMapInfo, klass->num_audio_out);
|
|
|
|
/* init */
|
|
self->pending_in = klass->num_group_in + klass->num_audio_in;
|
|
self->pending_out = 0;
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_finalize (GObject * object)
|
|
{
|
|
GstSignalProcessor *self = GST_SIGNAL_PROCESSOR (object);
|
|
|
|
g_free (self->group_in);
|
|
self->group_in = NULL;
|
|
g_free (self->group_out);
|
|
self->group_out = NULL;
|
|
g_free (self->audio_in);
|
|
self->audio_in = NULL;
|
|
g_free (self->audio_out);
|
|
self->audio_out = NULL;
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_setup (GstSignalProcessor * self, GstCaps * caps)
|
|
{
|
|
GstSignalProcessorClass *klass;
|
|
gboolean ret = TRUE;
|
|
|
|
klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
|
|
GST_INFO_OBJECT (self, "setup()");
|
|
|
|
g_return_val_if_fail (self->state == GST_SIGNAL_PROCESSOR_STATE_NULL, FALSE);
|
|
|
|
if (klass->setup)
|
|
ret = klass->setup (self, caps);
|
|
|
|
if (!ret)
|
|
goto setup_failed;
|
|
|
|
self->state = GST_SIGNAL_PROCESSOR_STATE_INITIALIZED;
|
|
|
|
return ret;
|
|
|
|
setup_failed:
|
|
{
|
|
GST_INFO_OBJECT (self, "setup() failed for caps: %" GST_PTR_FORMAT, caps);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_start (GstSignalProcessor * self)
|
|
{
|
|
GstSignalProcessorClass *klass;
|
|
gboolean ret = TRUE;
|
|
|
|
klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
|
|
g_return_val_if_fail (self->state == GST_SIGNAL_PROCESSOR_STATE_INITIALIZED,
|
|
FALSE);
|
|
|
|
GST_INFO_OBJECT (self, "start()");
|
|
|
|
if (klass->start)
|
|
ret = klass->start (self);
|
|
|
|
if (!ret)
|
|
goto start_failed;
|
|
|
|
self->state = GST_SIGNAL_PROCESSOR_STATE_RUNNING;
|
|
|
|
return ret;
|
|
|
|
start_failed:
|
|
{
|
|
GST_INFO_OBJECT (self, "start() failed");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_stop (GstSignalProcessor * self)
|
|
{
|
|
GstSignalProcessorClass *klass;
|
|
GstElement *elem;
|
|
GList *sinks;
|
|
|
|
klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
elem = GST_ELEMENT (self);
|
|
|
|
GST_INFO_OBJECT (self, "stop()");
|
|
|
|
g_return_if_fail (self->state == GST_SIGNAL_PROCESSOR_STATE_RUNNING);
|
|
|
|
if (klass->stop)
|
|
klass->stop (self);
|
|
|
|
for (sinks = elem->sinkpads; sinks; sinks = sinks->next)
|
|
/* force set_caps when going to RUNNING, see note in _setcaps () */
|
|
gst_pad_set_caps (GST_PAD (sinks->data), NULL);
|
|
|
|
/* should also flush our buffers perhaps? */
|
|
|
|
self->state = GST_SIGNAL_PROCESSOR_STATE_INITIALIZED;
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_cleanup (GstSignalProcessor * self)
|
|
{
|
|
GstSignalProcessorClass *klass;
|
|
gint i;
|
|
|
|
klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
|
|
GST_INFO_OBJECT (self, "cleanup()");
|
|
|
|
g_return_if_fail (self->state == GST_SIGNAL_PROCESSOR_STATE_INITIALIZED);
|
|
|
|
if (klass->cleanup)
|
|
klass->cleanup (self);
|
|
|
|
for (i = 0; i < klass->num_group_in; ++i) {
|
|
g_free (self->group_in[i].buffer);
|
|
memset (&self->group_in[i], 0, sizeof (GstSignalProcessorGroup));
|
|
}
|
|
|
|
for (i = 0; i < klass->num_group_out; ++i) {
|
|
g_free (self->group_out[i].buffer);
|
|
memset (&self->group_out[i], 0, sizeof (GstSignalProcessorGroup));
|
|
}
|
|
|
|
self->state = GST_SIGNAL_PROCESSOR_STATE_NULL;
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_setcaps_pull (GstSignalProcessor * self, GstPad * pad,
|
|
GstCaps * caps)
|
|
{
|
|
if (GST_PAD_IS_SRC (pad)) {
|
|
GList *l;
|
|
|
|
for (l = GST_ELEMENT (self)->sinkpads; l; l = l->next)
|
|
if (!gst_pad_set_caps (GST_PAD (l->data), caps))
|
|
goto src_setcaps_failed;
|
|
} else {
|
|
GstPad *peer;
|
|
gboolean res;
|
|
|
|
peer = gst_pad_get_peer (pad);
|
|
if (!peer)
|
|
goto unlinked_sink;
|
|
|
|
res = gst_pad_set_caps (peer, caps);
|
|
gst_object_unref (peer);
|
|
|
|
if (!res)
|
|
goto peer_setcaps_failed;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
src_setcaps_failed:
|
|
{
|
|
/* not logging, presumably the sink pad already logged */
|
|
return FALSE;
|
|
}
|
|
unlinked_sink:
|
|
{
|
|
GST_WARNING_OBJECT (self, "unlinked sink pad %" GST_PTR_FORMAT ", I wonder "
|
|
"how we passed activate_pull()", pad);
|
|
return FALSE;
|
|
}
|
|
peer_setcaps_failed:
|
|
{
|
|
GST_INFO_OBJECT (self, "peer of %" GST_PTR_FORMAT " did not accept caps",
|
|
pad);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_setcaps (GstPad * pad, GstCaps * caps)
|
|
{
|
|
GstSignalProcessor *self;
|
|
|
|
self = GST_SIGNAL_PROCESSOR (gst_pad_get_parent (pad));
|
|
|
|
if (self->mode == GST_PAD_MODE_PULL && !gst_caps_is_equal (caps, self->caps)
|
|
&& !gst_signal_processor_setcaps_pull (self, pad, caps))
|
|
goto setcaps_pull_failed;
|
|
|
|
/* the whole processor has one caps; if the sample rate changes, let subclass
|
|
implementations know */
|
|
if (!gst_caps_is_equal (caps, self->caps)) {
|
|
GstStructure *s;
|
|
|
|
GST_DEBUG_OBJECT (pad, "got caps %" GST_PTR_FORMAT, caps);
|
|
|
|
if (GST_SIGNAL_PROCESSOR_IS_RUNNING (self))
|
|
gst_signal_processor_stop (self);
|
|
if (GST_SIGNAL_PROCESSOR_IS_INITIALIZED (self))
|
|
gst_signal_processor_cleanup (self);
|
|
|
|
s = gst_caps_get_structure (caps, 0);
|
|
if (!gst_structure_get_int (s, "rate", &self->sample_rate))
|
|
goto no_sample_rate;
|
|
|
|
if (!gst_signal_processor_setup (self, caps))
|
|
goto start_or_setup_failed;
|
|
|
|
gst_caps_replace (&self->caps, caps);
|
|
} else {
|
|
GST_DEBUG_OBJECT (self, "skipping, have caps already");
|
|
}
|
|
|
|
/* we use this method to manage the processor's state, hence the caps clearing
|
|
in stop(). so it can be that we enter here just to manage the processor's
|
|
state, to take it to RUNNING from already being INITIALIZED with the right
|
|
sample rate (e.g., when having gone PLAYING->READY->PLAYING). make sure
|
|
when we leave that the processor is RUNNING. */
|
|
if (!GST_SIGNAL_PROCESSOR_IS_INITIALIZED (self)
|
|
&& !gst_signal_processor_setup (self, self->caps))
|
|
goto start_or_setup_failed;
|
|
if (!GST_SIGNAL_PROCESSOR_IS_RUNNING (self)
|
|
&& !gst_signal_processor_start (self))
|
|
goto start_or_setup_failed;
|
|
|
|
gst_object_unref (self);
|
|
|
|
return TRUE;
|
|
|
|
no_sample_rate:
|
|
{
|
|
GST_WARNING_OBJECT (self, "got no sample-rate");
|
|
gst_object_unref (self);
|
|
return FALSE;
|
|
}
|
|
start_or_setup_failed:
|
|
{
|
|
GST_WARNING_OBJECT (self, "start or setup failed");
|
|
gst_object_unref (self);
|
|
return FALSE;
|
|
}
|
|
setcaps_pull_failed:
|
|
{
|
|
GST_WARNING_OBJECT (self, "activating in pull-mode failed");
|
|
gst_object_unref (self);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* De-interleave a pad (gstreamer => plugin) */
|
|
static void
|
|
gst_signal_processor_deinterleave_group (GstSignalProcessorGroup * group,
|
|
guint nframes)
|
|
{
|
|
guint i, j;
|
|
g_assert (group->nframes == nframes);
|
|
g_assert (group->interleaved_map.data);
|
|
g_assert (group->buffer);
|
|
for (i = 0; i < nframes; ++i)
|
|
for (j = 0; j < group->channels; ++j)
|
|
group->buffer[(j * nframes) + i]
|
|
= group->interleaved_map.data[(i * group->channels) + j];
|
|
}
|
|
|
|
/* Interleave a pad (plugin => gstreamer) */
|
|
static void
|
|
gst_signal_processor_interleave_group (GstSignalProcessorGroup * group,
|
|
guint nframes)
|
|
{
|
|
guint i, j;
|
|
g_assert (group->nframes == nframes);
|
|
g_assert (group->interleaved_map.data);
|
|
g_assert (group->buffer);
|
|
for (i = 0; i < nframes; ++i)
|
|
for (j = 0; j < group->channels; ++j)
|
|
group->interleaved_map.data[(i * group->channels) + j]
|
|
= group->buffer[(j * nframes) + i];
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
|
{
|
|
GstSignalProcessor *self;
|
|
GstSignalProcessorClass *bclass;
|
|
gboolean ret;
|
|
|
|
self = GST_SIGNAL_PROCESSOR (parent);
|
|
bclass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
|
|
GST_DEBUG_OBJECT (pad, "got event %s", GST_EVENT_TYPE_NAME (event));
|
|
|
|
/* FIXME, this probably isn't the correct interface: what about return values,
|
|
* what about overriding event_default
|
|
* Sync with GstBaseTransform::gst_base_transform_sink_event */
|
|
if (bclass->event)
|
|
bclass->event (self, event);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_CAPS:
|
|
{
|
|
GstCaps *caps;
|
|
|
|
gst_event_parse_caps (event, &caps);
|
|
ret = gst_signal_processor_setcaps (pad, caps);
|
|
gst_event_unref (event);
|
|
break;
|
|
}
|
|
case GST_EVENT_FLUSH_START:
|
|
ret = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
/* clear errors now */
|
|
self->flow_state = GST_FLOW_OK;
|
|
ret = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
default:
|
|
ret = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* gst_signal_processor_prepare:
|
|
* @self: the element
|
|
* nframes: wanted sample frames
|
|
*
|
|
* Checks if wan
|
|
*
|
|
* Returns: available sample frames
|
|
*/
|
|
static guint
|
|
gst_signal_processor_prepare (GstSignalProcessor * self, guint nframes)
|
|
{
|
|
GstElement *elem = (GstElement *) self;
|
|
GstSignalProcessorClass *klass;
|
|
GList *sinks, *srcs;
|
|
guint samples_avail = nframes;
|
|
guint i, in_group_index = 0, out_group_index = 0;
|
|
gboolean is_gap = FALSE;
|
|
GstClockTime ts, tss = GST_CLOCK_TIME_NONE, tse = GST_CLOCK_TIME_NONE;
|
|
|
|
klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
|
|
/* first, assign audio_in pointers, and determine the number of samples that
|
|
* we can process */
|
|
i = 0;
|
|
for (sinks = elem->sinkpads; sinks; sinks = sinks->next, ++i) {
|
|
GstSignalProcessorPad *sinkpad;
|
|
|
|
sinkpad = (GstSignalProcessorPad *) sinks->data;
|
|
g_assert (sinkpad->samples_avail > 0);
|
|
samples_avail = MIN (samples_avail, sinkpad->samples_avail);
|
|
if (sinkpad->channels > 1) {
|
|
GstSignalProcessorGroup *group = &self->group_in[in_group_index++];
|
|
group->interleaved_map.data = sinkpad->map.data;
|
|
/* allocate buffer for de-interleaving */
|
|
if (!group->buffer || group->channels < sinkpad->channels
|
|
|| group->nframes < samples_avail) {
|
|
group->buffer =
|
|
(gfloat *) g_realloc (group->buffer,
|
|
samples_avail * sinkpad->channels * sizeof (gfloat));
|
|
memset (group->buffer, '\0',
|
|
samples_avail * sinkpad->channels * sizeof (gfloat));
|
|
}
|
|
g_assert (group->buffer);
|
|
group->channels = sinkpad->channels;
|
|
group->nframes = samples_avail;
|
|
gst_signal_processor_deinterleave_group (group, samples_avail);
|
|
} else {
|
|
self->audio_in[sinkpad->index] = sinkpad->map;
|
|
}
|
|
}
|
|
|
|
GST_LOG_OBJECT (self, "want %u samples, have %u samples", nframes,
|
|
samples_avail);
|
|
|
|
/* FIXME: return if samples_avail==0 ? */
|
|
|
|
if ((sinks = elem->sinkpads)) {
|
|
is_gap = TRUE;
|
|
while (sinks) {
|
|
GstSignalProcessorPad *sinkpad = (GstSignalProcessorPad *) sinks->data;
|
|
|
|
is_gap &= GST_BUFFER_FLAG_IS_SET (sinkpad->pen, GST_BUFFER_FLAG_GAP);
|
|
ts = GST_BUFFER_TIMESTAMP (sinkpad->pen);
|
|
if (GST_CLOCK_TIME_IS_VALID (ts)) {
|
|
tss = !GST_CLOCK_TIME_IS_VALID (tss) ? ts : MIN (tss, ts);
|
|
tse = !GST_CLOCK_TIME_IS_VALID (tse) ? ts : MAX (tse, ts);
|
|
}
|
|
sinks = sinks->next;
|
|
}
|
|
ts = (tss == tse) ? tss : GST_CLOCK_TIME_NONE;
|
|
GST_LOG_OBJECT (self, "is gap: %d, tss %" GST_TIME_FORMAT ", tse %"
|
|
GST_TIME_FORMAT, is_gap, GST_TIME_ARGS (tss), GST_TIME_ARGS (tse));
|
|
} else {
|
|
/* FIXME: calculate own timestamps */
|
|
ts = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
/* now assign output buffers. we can avoid allocation by reusing input
|
|
buffers, but only if process() can work in place, and if the input buffer
|
|
is the exact size of the number of samples we are processing. */
|
|
sinks = elem->sinkpads;
|
|
srcs = elem->srcpads;
|
|
|
|
GST_LOG_OBJECT (self, "allocating %d buffers", g_list_length (srcs));
|
|
|
|
if (GST_SIGNAL_PROCESSOR_CLASS_CAN_PROCESS_IN_PLACE (klass)) {
|
|
while (sinks && srcs) {
|
|
GstSignalProcessorPad *sinkpad, *srcpad;
|
|
|
|
sinkpad = (GstSignalProcessorPad *) sinks->data;
|
|
srcpad = (GstSignalProcessorPad *) srcs->data;
|
|
|
|
if (sinkpad->channels == 1 && sinkpad->channels == srcpad->channels
|
|
&& gst_buffer_get_size (sinkpad->pen) ==
|
|
samples_avail * sizeof (gfloat)) {
|
|
/* reusable, yay */
|
|
g_assert (sinkpad->samples_avail == samples_avail);
|
|
srcpad->pen = sinkpad->pen;
|
|
sinkpad->pen = NULL;
|
|
self->audio_out[srcpad->index] = sinkpad->map;
|
|
self->pending_out++;
|
|
|
|
srcs = srcs->next;
|
|
}
|
|
|
|
sinks = sinks->next;
|
|
}
|
|
}
|
|
|
|
g_return_val_if_fail (GST_SIGNAL_PROCESSOR_IS_RUNNING (self), 0);
|
|
|
|
/* now allocate for any remaining outputs */
|
|
while (srcs) {
|
|
GstSignalProcessorPad *srcpad;
|
|
|
|
srcpad = (GstSignalProcessorPad *) srcs->data;
|
|
|
|
srcpad->pen =
|
|
gst_buffer_new_allocate (NULL,
|
|
samples_avail * srcpad->channels * sizeof (gfloat), NULL);
|
|
|
|
if (srcpad->channels > 1) {
|
|
GstSignalProcessorGroup *group = &self->group_out[out_group_index++];
|
|
gst_buffer_map (srcpad->pen, &group->interleaved_map, GST_MAP_READWRITE);
|
|
if (!group->buffer || group->channels < srcpad->channels
|
|
|| group->nframes < samples_avail)
|
|
group->buffer =
|
|
(gfloat *) g_realloc (group->buffer,
|
|
samples_avail * srcpad->channels * sizeof (gfloat));
|
|
group->channels = srcpad->channels;
|
|
group->nframes = samples_avail;
|
|
self->pending_out++;
|
|
} else {
|
|
gst_buffer_map (srcpad->pen, &self->audio_out[srcpad->index],
|
|
GST_MAP_READWRITE);
|
|
self->pending_out++;
|
|
}
|
|
|
|
/* set time stamp */
|
|
GST_BUFFER_TIMESTAMP (srcpad->pen) = ts;
|
|
/* FIXME: handle gap flag ? */
|
|
|
|
srcs = srcs->next;
|
|
}
|
|
|
|
/* update controlled parameters */
|
|
if (samples_avail && GST_CLOCK_TIME_IS_VALID (ts)) {
|
|
gst_object_sync_values ((GstObject *) self, ts);
|
|
}
|
|
|
|
return samples_avail;
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_update_inputs (GstSignalProcessor * self, guint nprocessed)
|
|
{
|
|
GstElement *elem = (GstElement *) self;
|
|
GList *sinks;
|
|
|
|
for (sinks = elem->sinkpads; sinks; sinks = sinks->next) {
|
|
GstSignalProcessorPad *sinkpad;
|
|
|
|
sinkpad = (GstSignalProcessorPad *) sinks->data;
|
|
g_assert (sinkpad->samples_avail >= nprocessed);
|
|
|
|
if (sinkpad->pen && sinkpad->samples_avail == nprocessed) {
|
|
/* used up this buffer, unpen */
|
|
gst_buffer_unref (sinkpad->pen);
|
|
sinkpad->pen = NULL;
|
|
}
|
|
|
|
if (!sinkpad->pen) {
|
|
/* this buffer was used up */
|
|
self->pending_in++;
|
|
sinkpad->map.data = NULL;
|
|
sinkpad->samples_avail = 0;
|
|
} else {
|
|
/* advance ->data pointers and decrement ->samples_avail, unreffing buffer
|
|
if no samples are left */
|
|
sinkpad->samples_avail -= nprocessed;
|
|
sinkpad->map.data += nprocessed * sinkpad->channels; /* gfloat* arithmetic */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_update_outputs (GstSignalProcessor * self,
|
|
guint nprocessed)
|
|
{
|
|
GstSignalProcessorClass *klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
guint i;
|
|
for (i = 0; i < klass->num_group_out; ++i)
|
|
gst_signal_processor_interleave_group (&self->group_out[i], nprocessed);
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_process (GstSignalProcessor * self, guint nframes)
|
|
{
|
|
GstSignalProcessorClass *klass;
|
|
|
|
/* check if we have buffers enqueued */
|
|
g_return_val_if_fail (self->pending_in == 0, FALSE);
|
|
g_return_val_if_fail (self->pending_out == 0, FALSE);
|
|
|
|
/* check how much input is available and prepare output buffers */
|
|
nframes = gst_signal_processor_prepare (self, nframes);
|
|
if (G_UNLIKELY (nframes == 0))
|
|
goto flow_error;
|
|
|
|
klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
|
|
GST_LOG_OBJECT (self, "process(%u)", nframes);
|
|
|
|
klass->process (self, nframes);
|
|
|
|
gst_signal_processor_update_inputs (self, nframes);
|
|
gst_signal_processor_update_outputs (self, nframes);
|
|
|
|
return TRUE;
|
|
|
|
flow_error:
|
|
{
|
|
GST_WARNING_OBJECT (self,
|
|
"gst_signal_processor_prepare() returned %d, flow_ref=%s", nframes,
|
|
gst_flow_get_name (self->flow_state));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_pen_buffer (GstSignalProcessor * self, GstPad * pad,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstSignalProcessorPad *spad = (GstSignalProcessorPad *) pad;
|
|
|
|
if (spad->pen)
|
|
goto had_buffer;
|
|
|
|
/* keep the reference */
|
|
spad->pen = buffer;
|
|
gst_buffer_map (buffer, &spad->map, GST_MAP_READWRITE);
|
|
spad->samples_avail = spad->map.size / sizeof (float) / spad->channels;
|
|
|
|
g_assert (self->pending_in != 0);
|
|
|
|
self->pending_in--;
|
|
|
|
return;
|
|
|
|
/* ERRORS */
|
|
had_buffer:
|
|
{
|
|
GST_WARNING ("Pad %s:%s already has penned buffer",
|
|
GST_DEBUG_PAD_NAME (pad));
|
|
gst_buffer_unref (buffer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_flush (GstSignalProcessor * self)
|
|
{
|
|
GList *pads;
|
|
GstSignalProcessorClass *klass;
|
|
|
|
klass = GST_SIGNAL_PROCESSOR_GET_CLASS (self);
|
|
|
|
GST_INFO_OBJECT (self, "flush()");
|
|
|
|
/* release enqueued buffers */
|
|
for (pads = GST_ELEMENT (self)->pads; pads; pads = pads->next) {
|
|
GstSignalProcessorPad *spad = (GstSignalProcessorPad *) pads->data;
|
|
|
|
if (spad->pen) {
|
|
gst_buffer_unmap (spad->pen, &spad->map);
|
|
gst_buffer_unref (spad->pen);
|
|
spad->pen = NULL;
|
|
spad->samples_avail = 0;
|
|
}
|
|
}
|
|
|
|
/* no outputs prepared and inputs for each pad needed */
|
|
self->pending_out = 0;
|
|
self->pending_in = klass->num_audio_in;
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_do_pulls (GstSignalProcessor * self, guint nframes)
|
|
{
|
|
GList *sinkpads;
|
|
|
|
/* FIXME: not threadsafe atm */
|
|
|
|
sinkpads = GST_ELEMENT (self)->sinkpads;
|
|
|
|
for (; sinkpads; sinkpads = sinkpads->next) {
|
|
GstSignalProcessorPad *spad = (GstSignalProcessorPad *) sinkpads->data;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstBuffer *buf;
|
|
|
|
if (spad->pen) {
|
|
g_warning ("Unexpectedly full buffer pen for pad %s:%s",
|
|
GST_DEBUG_PAD_NAME (spad));
|
|
continue;
|
|
}
|
|
|
|
buf = NULL;
|
|
ret =
|
|
gst_pad_pull_range (GST_PAD (spad), -1, nframes * sizeof (gfloat),
|
|
&buf);
|
|
|
|
if (ret != GST_FLOW_OK) {
|
|
gst_signal_processor_flush (self);
|
|
self->flow_state = ret;
|
|
return;
|
|
} else if (!buf) {
|
|
g_critical ("Pull failed to make a buffer!");
|
|
self->flow_state = GST_FLOW_ERROR;
|
|
return;
|
|
} else {
|
|
gst_signal_processor_pen_buffer (self, GST_PAD (spad), buf);
|
|
}
|
|
}
|
|
|
|
if (self->pending_in != 0) {
|
|
g_critical ("Something wierd happened...");
|
|
self->flow_state = GST_FLOW_ERROR;
|
|
} else {
|
|
gst_signal_processor_process (self, nframes);
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_signal_processor_getrange (GstPad * pad, GstObject * parent, guint64 offset,
|
|
guint length, GstBuffer ** buffer)
|
|
{
|
|
GstSignalProcessor *self;
|
|
GstSignalProcessorPad *spad = (GstSignalProcessorPad *) pad;
|
|
GstFlowReturn ret = GST_FLOW_ERROR;
|
|
|
|
self = GST_SIGNAL_PROCESSOR (parent);
|
|
|
|
if (spad->pen) {
|
|
*buffer = spad->pen;
|
|
spad->pen = NULL;
|
|
g_assert (self->pending_out != 0);
|
|
self->pending_out--;
|
|
ret = GST_FLOW_OK;
|
|
} else {
|
|
gst_signal_processor_do_pulls (self, length / sizeof (gfloat));
|
|
if (!spad->pen) {
|
|
/* this is an error condition */
|
|
*buffer = NULL;
|
|
ret = self->flow_state;
|
|
} else {
|
|
*buffer = spad->pen;
|
|
spad->pen = NULL;
|
|
self->pending_out--;
|
|
ret = GST_FLOW_OK;
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "returns %s", gst_flow_get_name (ret));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_signal_processor_do_pushes (GstSignalProcessor * self)
|
|
{
|
|
GList *srcpads;
|
|
|
|
/* not threadsafe atm */
|
|
|
|
srcpads = GST_ELEMENT (self)->srcpads;
|
|
|
|
for (; srcpads; srcpads = srcpads->next) {
|
|
GstSignalProcessorPad *spad = (GstSignalProcessorPad *) srcpads->data;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstBuffer *buffer;
|
|
|
|
if (!spad->pen) {
|
|
g_warning ("Unexpectedly empty buffer pen for pad %s:%s",
|
|
GST_DEBUG_PAD_NAME (spad));
|
|
continue;
|
|
}
|
|
|
|
/* take buffer from pen */
|
|
buffer = spad->pen;
|
|
spad->pen = NULL;
|
|
|
|
ret = gst_pad_push (GST_PAD (spad), buffer);
|
|
|
|
if (ret != GST_FLOW_OK) {
|
|
gst_signal_processor_flush (self);
|
|
self->flow_state = ret;
|
|
return;
|
|
} else {
|
|
g_assert (self->pending_out > 0);
|
|
self->pending_out--;
|
|
}
|
|
}
|
|
|
|
if (self->pending_out != 0) {
|
|
g_critical ("Something wierd happened...");
|
|
self->flow_state = GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_signal_processor_chain (GstPad * pad, GstObject * parent,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstSignalProcessor *self;
|
|
|
|
self = GST_SIGNAL_PROCESSOR (parent);
|
|
|
|
GST_LOG_OBJECT (self, "chain(%s:%s, %p) : p_in=%u, p_out=%u",
|
|
GST_DEBUG_PAD_NAME (pad), buffer, self->pending_in, self->pending_out);
|
|
|
|
gst_signal_processor_pen_buffer (self, pad, buffer);
|
|
|
|
if (self->pending_in == 0) {
|
|
if (gst_signal_processor_process (self, G_MAXUINT))
|
|
gst_signal_processor_do_pushes (self);
|
|
}
|
|
|
|
return self->flow_state;
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_sink_activate_mode (GstPad * pad, GstObject * parent,
|
|
GstPadMode mode, gboolean active)
|
|
{
|
|
gboolean result;
|
|
GstSignalProcessor *self;
|
|
|
|
self = GST_SIGNAL_PROCESSOR (parent);
|
|
|
|
switch (mode) {
|
|
case GST_PAD_MODE_PUSH:
|
|
if (active) {
|
|
if (self->mode == GST_PAD_MODE_NONE) {
|
|
self->mode = GST_PAD_MODE_PUSH;
|
|
result = TRUE;
|
|
} else if (self->mode == GST_PAD_MODE_PUSH) {
|
|
result = TRUE;
|
|
} else {
|
|
g_warning ("foo");
|
|
result = FALSE;
|
|
}
|
|
} else {
|
|
if (self->mode == GST_PAD_MODE_NONE) {
|
|
result = TRUE;
|
|
} else if (self->mode == GST_PAD_MODE_PUSH) {
|
|
self->mode = GST_PAD_MODE_NONE;
|
|
result = TRUE;
|
|
} else {
|
|
g_warning ("foo");
|
|
result = FALSE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "result : %d", result);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
gst_signal_processor_src_activate_mode (GstPad * pad, GstObject * parent,
|
|
GstPadMode mode, gboolean active)
|
|
{
|
|
gboolean result;
|
|
GstSignalProcessor *self;
|
|
|
|
self = GST_SIGNAL_PROCESSOR (parent);
|
|
|
|
switch (mode) {
|
|
case GST_PAD_MODE_PUSH:
|
|
if (active) {
|
|
if (self->mode == GST_PAD_MODE_NONE) {
|
|
GList *l;
|
|
|
|
result = TRUE;
|
|
for (l = GST_ELEMENT (self)->sinkpads; l; l = l->next)
|
|
result &= gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, active);
|
|
if (result)
|
|
self->mode = GST_PAD_MODE_PULL;
|
|
} else if (self->mode == GST_PAD_MODE_PULL) {
|
|
result = TRUE;
|
|
} else {
|
|
g_warning ("foo");
|
|
result = FALSE;
|
|
}
|
|
} else {
|
|
if (self->mode == GST_PAD_MODE_NONE) {
|
|
result = TRUE;
|
|
} else if (self->mode == GST_PAD_MODE_PULL) {
|
|
GList *l;
|
|
|
|
result = TRUE;
|
|
for (l = GST_ELEMENT (self)->sinkpads; l; l = l->next)
|
|
result &= gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, active);
|
|
if (result)
|
|
self->mode = GST_PAD_MODE_NONE;
|
|
result = TRUE;
|
|
} else {
|
|
g_warning ("foo");
|
|
result = FALSE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "result : %d", result);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_signal_processor_change_state (GstElement * element,
|
|
GstStateChange transition)
|
|
{
|
|
GstSignalProcessor *self;
|
|
GstStateChangeReturn result;
|
|
|
|
self = GST_SIGNAL_PROCESSOR (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
self->flow_state = GST_FLOW_OK;
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ((result =
|
|
GST_ELEMENT_CLASS (parent_class)->change_state (element,
|
|
transition)) == GST_STATE_CHANGE_FAILURE)
|
|
goto failure;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
if (GST_SIGNAL_PROCESSOR_IS_RUNNING (self))
|
|
gst_signal_processor_stop (self);
|
|
gst_signal_processor_flush (self);
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
if (GST_SIGNAL_PROCESSOR_IS_INITIALIZED (self))
|
|
gst_signal_processor_cleanup (self);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
failure:
|
|
{
|
|
GST_DEBUG_OBJECT (element, "parent failed state change");
|
|
return result;
|
|
}
|
|
}
|