mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-08 21:28:45 +00:00
290 lines
9.2 KiB
XML
290 lines
9.2 KiB
XML
<chapter id="chapter-advanced-request">
|
|
<title>Request and Sometimes pads</title>
|
|
<para>
|
|
Until now, we've only dealt with pads that are always available. However,
|
|
there's also pads that are only being created in some cases, or only if
|
|
the application requests the pad. The first is called a
|
|
<emphasis>sometimes</emphasis>; the second is called a
|
|
<emphasis>request</emphasis> pad. The availability of a pad (always,
|
|
sometimes or request) can be seen in a pad's template. This chapter will
|
|
discuss when each of the two is useful, how they are created and when
|
|
they should be disposed.
|
|
</para>
|
|
|
|
<sect1 id="section-reqpad-sometimes" xreflabel="Sometimes pads">
|
|
<title>Sometimes pads</title>
|
|
<para>
|
|
A <quote>sometimes</quote> pad is a pad that is created under certain
|
|
conditions, but not in all cases. This mostly depends on stream content:
|
|
demuxers will generally parse the stream header, decide what elementary
|
|
(video, audio, subtitle, etc.) streams are embedded inside the system
|
|
stream, and will then create a sometimes pad for each of those elementary
|
|
streams. At its own choice, it can also create more than one instance of
|
|
each of those per element instance. The only limitation is that each
|
|
newly created pad should have a unique name. Sometimes pads are disposed
|
|
when the stream data is disposed, too (i.e. when going from PAUSED to the
|
|
READY state). You should <emphasis>not</emphasis> dispose the pad on EOS,
|
|
because someone might re-activate the pipeline and seek back to before
|
|
the end-of-stream point. The stream should still stay valid after EOS, at
|
|
least until the stream data is disposed. In any case, the element is
|
|
always the owner of such a pad.
|
|
</para>
|
|
<para>
|
|
The example code below will parse a text file, where the first line is
|
|
a number (n). The next lines all start with a number (0 to n-1), which
|
|
is the number of the source pad over which the data should be sent.
|
|
</para>
|
|
<programlisting>
|
|
3
|
|
0: foo
|
|
1: bar
|
|
0: boo
|
|
2: bye
|
|
</programlisting>
|
|
<para>
|
|
The code to parse this file and create the dynamic <quote>sometimes</quote>
|
|
pads, looks like this:
|
|
</para>
|
|
<programlisting>
|
|
<![CDATA[
|
|
typedef struct _GstMyFilter {
|
|
[..]
|
|
gboolean firstrun;
|
|
GList *srcpadlist;
|
|
} GstMyFilter;
|
|
|
|
static GstStaticPadTemplate src_factory =
|
|
GST_STATIC_PAD_TEMPLATE (
|
|
"src_%u",
|
|
GST_PAD_SRC,
|
|
GST_PAD_SOMETIMES,
|
|
GST_STATIC_CAPS ("ANY")
|
|
);
|
|
|
|
static void
|
|
gst_my_filter_class_init (GstMyFilterClass *klass)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
[..]
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&src_factory));
|
|
[..]
|
|
}
|
|
|
|
static void
|
|
gst_my_filter_init (GstMyFilter *filter)
|
|
{
|
|
[..]
|
|
filter->firstrun = TRUE;
|
|
filter->srcpadlist = NULL;
|
|
}
|
|
|
|
/*
|
|
* Get one line of data - without newline.
|
|
*/
|
|
|
|
static GstBuffer *
|
|
gst_my_filter_getline (GstMyFilter *filter)
|
|
{
|
|
guint8 *data;
|
|
gint n, num;
|
|
|
|
/* max. line length is 512 characters - for safety */
|
|
for (n = 0; n < 512; n++) {
|
|
num = gst_bytestream_peek_bytes (filter->bs, &data, n + 1);
|
|
if (num != n + 1)
|
|
return NULL;
|
|
|
|
/* newline? */
|
|
if (data[n] == '\n') {
|
|
GstBuffer *buf = gst_buffer_new_allocate (NULL, n + 1, NULL);
|
|
|
|
gst_bytestream_peek_bytes (filter->bs, &data, n);
|
|
gst_buffer_fill (buf, 0, data, n);
|
|
gst_buffer_memset (buf, n, '\0', 1);
|
|
gst_bytestream_flush_fast (filter->bs, n + 1);
|
|
|
|
return buf;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_my_filter_loopfunc (GstElement *element)
|
|
{
|
|
GstMyFilter *filter = GST_MY_FILTER (element);
|
|
GstBuffer *buf;
|
|
GstPad *pad;
|
|
GstMapInfo map;
|
|
gint num, n;
|
|
|
|
/* parse header */
|
|
if (filter->firstrun) {
|
|
gchar *padname;
|
|
guint8 id;
|
|
|
|
if (!(buf = gst_my_filter_getline (filter))) {
|
|
gst_element_error (element, STREAM, READ, (NULL),
|
|
("Stream contains no header"));
|
|
return;
|
|
}
|
|
gst_buffer_extract (buf, 0, &id, 1);
|
|
num = atoi (id);
|
|
gst_buffer_unref (buf);
|
|
|
|
/* for each of the streams, create a pad */
|
|
for (n = 0; n < num; n++) {
|
|
padname = g_strdup_printf ("src_%u", n);
|
|
pad = gst_pad_new_from_static_template (src_factory, padname);
|
|
g_free (padname);
|
|
|
|
/* here, you would set _event () and _query () functions */
|
|
|
|
/* need to activate the pad before adding */
|
|
gst_pad_set_active (pad, TRUE);
|
|
|
|
gst_element_add_pad (element, pad);
|
|
filter->srcpadlist = g_list_append (filter->srcpadlist, pad);
|
|
}
|
|
}
|
|
|
|
/* and now, simply parse each line and push over */
|
|
if (!(buf = gst_my_filter_getline (filter))) {
|
|
GstEvent *event = gst_event_new (GST_EVENT_EOS);
|
|
GList *padlist;
|
|
|
|
for (padlist = srcpadlist;
|
|
padlist != NULL; padlist = g_list_next (padlist)) {
|
|
pad = GST_PAD (padlist->data);
|
|
gst_pad_push_event (pad, gst_event_ref (event));
|
|
}
|
|
gst_event_unref (event);
|
|
/* pause the task here */
|
|
return;
|
|
}
|
|
|
|
/* parse stream number and go beyond the ':' in the data */
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
num = atoi (map.data[0]);
|
|
if (num >= 0 && num < g_list_length (filter->srcpadlist)) {
|
|
pad = GST_PAD (g_list_nth_data (filter->srcpadlist, num);
|
|
|
|
/* magic buffer parsing foo */
|
|
for (n = 0; map.data[n] != ':' &&
|
|
map.data[n] != '\0'; n++) ;
|
|
if (map.data[n] != '\0') {
|
|
GstBuffer *sub;
|
|
|
|
/* create region copy that starts right past the space. The reason
|
|
* that we don't just forward the data pointer is because the
|
|
* pointer is no longer the start of an allocated block of memory,
|
|
* but just a pointer to a position somewhere in the middle of it.
|
|
* That cannot be freed upon disposal, so we'd either crash or have
|
|
* a memleak. Creating a region copy is a simple way to solve that. */
|
|
sub = gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL,
|
|
n + 1, map.size - n - 1);
|
|
gst_pad_push (pad, sub);
|
|
}
|
|
}
|
|
gst_buffer_unmap (buf, &map);
|
|
gst_buffer_unref (buf);
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<para>
|
|
Note that we use a lot of checks everywhere to make sure that the content
|
|
in the file is valid. This has two purposes: first, the file could be
|
|
erroneous, in which case we prevent a crash. The second and most important
|
|
reason is that - in extreme cases - the file could be used maliciously to
|
|
cause undefined behaviour in the plugin, which might lead to security
|
|
issues. <emphasis>Always</emphasis> assume that the file could be used to
|
|
do bad things.
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 id="section-reqpad-request" xreflabel="Request pads">
|
|
<title>Request pads</title>
|
|
<para>
|
|
<quote>Request</quote> pads are similar to sometimes pads, except that
|
|
request are created on demand of something outside of the element rather
|
|
than something inside the element. This concept is often used in muxers,
|
|
where - for each elementary stream that is to be placed in the output
|
|
system stream - one sink pad will be requested. It can also be used in
|
|
elements with a variable number of input or outputs pads, such as the
|
|
<classname>tee</classname> (multi-output) or
|
|
<classname>input-selector</classname> (multi-input) elements.
|
|
</para>
|
|
<para>
|
|
To implement request pads, you need to provide a padtemplate with a
|
|
GST_PAD_REQUEST presence and implement the
|
|
<function>request_new_pad</function> virtual method in
|
|
<classname>GstElement</classname>.
|
|
To clean up, you will need to implement the
|
|
<function>release_pad</function> virtual method.
|
|
</para>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static GstPad * gst_my_filter_request_new_pad (GstElement *element,
|
|
GstPadTemplate *templ,
|
|
const gchar *name,
|
|
const GstCaps *caps);
|
|
|
|
static void gst_my_filter_release_pad (GstElement *element,
|
|
GstPad *pad);
|
|
|
|
static GstStaticPadTemplate sink_factory =
|
|
GST_STATIC_PAD_TEMPLATE (
|
|
"sink_%u",
|
|
GST_PAD_SINK,
|
|
GST_PAD_REQUEST,
|
|
GST_STATIC_CAPS ("ANY")
|
|
);
|
|
|
|
static void
|
|
gst_my_filter_class_init (GstMyFilterClass *klass)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
[..]
|
|
gst_element_class_add_pad_template (klass,
|
|
gst_static_pad_template_get (&sink_factory));
|
|
[..]
|
|
element_class->request_new_pad = gst_my_filter_request_new_pad;
|
|
element_class->release_pad = gst_my_filter_release_pad;
|
|
}
|
|
|
|
static GstPad *
|
|
gst_my_filter_request_new_pad (GstElement *element,
|
|
GstPadTemplate *templ,
|
|
const gchar *name,
|
|
const GstCaps *caps)
|
|
{
|
|
GstPad *pad;
|
|
GstMyFilterInputContext *context;
|
|
|
|
context = g_new0 (GstMyFilterInputContext, 1);
|
|
pad = gst_pad_new_from_template (templ, name);
|
|
gst_pad_set_element_private (pad, context);
|
|
|
|
/* normally, you would set _chain () and _event () functions here */
|
|
|
|
gst_element_add_pad (element, pad);
|
|
|
|
return pad;
|
|
}
|
|
|
|
static void
|
|
gst_my_filter_release_pad (GstElement *element,
|
|
GstPad *pad)
|
|
{
|
|
GstMyFilterInputContext *context;
|
|
|
|
context = gst_pad_get_element_private (pad);
|
|
g_free (context);
|
|
|
|
gst_element_remove_pad (element, pad);
|
|
}
|
|
|
|
]]>
|
|
</programlisting>
|
|
</sect1>
|
|
</chapter>
|