A _loop ()
function is a function that is called by
the scheduler, but without providing data to the element. Instead, the
element will become responsible for acquiring its own data, and it will
still be responsible of sending data over to its source pads. This method
noticeably complicates scheduling; you should only write loop-based
elements when you need to. Normally, chain-based elements are preferred.
Examples of elements that have to be loop-based are
elements with multiple sink pads. Since the scheduler will push data into
the pads as it comes (and this might not be synchronous), you will easily
get ascynronous data on both pads, which means that the data that arrives
on the first pad has a different display timestamp then the data arriving
on the second pad at the same time. To get over these issues, you should
write such elements in a loop-based form. Other elements that are
easier to write in a loop-based form than in a
chain-based form are demuxers and parsers. It is not required to write such
elements in a loop-based form, though.
Below is an example of the easiest loop-function that one can write:
static void gst_my_filter_loopfunc (GstElement *element); static void gst_my_filter_init (GstMyFilter *filter) { [..] gst_element_set_loopfunc (GST_ELEMENT (filter), gst_my_filter_loopfunc); [..] } static void gst_my_filter_loopfunc (GstElement *element) { GstMyFilter *filter = GST_MY_FILTER (element); GstData *data; /* acquire data */ data = gst_pad_pull (filter->sinkpad); /* send data */ gst_pad_push (filter->srcpad, data); }
Obviously, this specific example has no single advantage over a chain-based element, so you should never write such elements. However, it's a good introduction to the concept.
Elements with multiple sink pads need to take manual control over their input to assure that the input is synchronized. The following example code could (should) be used in an aggregator, i.e. an element that takes input from multiple streams and sends it out intermangled. Not really useful in practice, but a good example, again.
typedef struct _GstMyFilterInputContext { gboolean eos; GstBuffer *lastbuf; } GstMyFilterInputContext; [..] static void gst_my_filter_init (GstMyFilter *filter) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (filter); GstMyFilterInputContext *context; filter->sinkpad1 = gst_pad_new_from_template ( gst_element_class_get_pad_template (klass, "sink"), "sink_1"); context = g_new0 (GstMyFilterInputContext, 1); gst_pad_set_private_data (filter->sinkpad1, context); [..] filter->sinkpad2 = gst_pad_new_from_template ( gst_element_class_get_pad_template (klass, "sink"), "sink_2"); context = g_new0 (GstMyFilterInputContext, 1); gst_pad_set_private_data (filter->sinkpad2, context); [..] gst_element_set_loopfunc (GST_ELEMENT (filter), gst_my_filter_loopfunc); } [..] static void gst_my_filter_loopfunc (GstElement *element) { GstMyFilter *filter = GST_MY_FILTER (element); GList *padlist; GstMyFilterInputContext *first_context = NULL; /* Go over each sink pad, update the cache if needed, handle EOS * or non-responding streams and see which data we should handle * next. */ for (padlist = gst_element_get_padlist (element); padlist != NULL; padlist = g_list_next (padlist)) { GstPad *pad = GST_PAD (padlist->data); GstMyFilterInputContext *context = gst_pad_get_private_data (pad); if (GST_PAD_IS_SRC (pad)) continue; while (GST_PAD_IS_USABLE (pad) && !context->eos && !context->lastbuf) { GstData *data = gst_pad_pull (pad); if (GST_IS_EVENT (data)) { /* We handle events immediately */ GstEvent *event = GST_EVENT (data); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: context->eos = TRUE; gst_event_unref (event); break; case GST_EVENT_DISCONTINUOUS: g_warning ("HELP! How do I handle this?"); /* fall-through */ default: gst_pad_event_default (pad, event); break; } } else { /* We store the buffer to handle synchronization below */ context->lastbuf = GST_BUFFER (data); } } /* synchronize streams by always using the earliest buffer */ if (context->lastbuf) { if (!first_context) { first_context = context; } else { if (GST_BUFFER_TIMESTAMP (context->lastbuf) < GST_BUFFER_TIMESTAMP (first_context->lastbuf)) first_context = context; } } } /* If we handle no data at all, we're at the end-of-stream, so * we should signal EOS. */ if (!first_context) { gst_pad_push (filter->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS))); gst_element_set_eos (element); return; } /* So we do have data! Let's forward that to our source pad. */ gst_pad_push (filter->srcpad, GST_DATA (first_context->lastbuf)); first_context->lastbuf = NULL; }
Note that a loop-function is allowed to return. Better yet, a loop function has to return so the scheduler can let other elements run (this is particularly true for the optimal scheduler). Whenever the scheduler feels right, it will call the loop-function of the element again.