| This short document gives some guidelines to plugin writers. | |
| chain-based plugins | |
| =================== | |
| - the plugin has a chain function on each sink pad (can be the same function) | |
| ! | |
| ! chain (GstPad *pad, GstBuffer *buffer) | |
| ! { | |
| ! MyElement *elem = MY_ELEMENT (gst_pad_get_parent (pad)); | |
| ! GstBuffer *new_buffer; | |
| ! | |
| ! ... | |
| ! (process the buffer) | |
| ! ... | |
| ! | |
| ! /* you can push as many buffers (0 or more) as you want */ | |
| ! while (!done) { | |
| ! ... | |
| ! (process some more | |
| ! ... | |
| ! gst_pad_push (elem->some_sink_pad, new_buffer); | |
| ! ... | |
| ! /* if you're like going to send a large amount of buffers | |
| ! * it's a good idea to call _yield from time to time after | |
| ! * the buffer has been pushed */ | |
| ! (optional gst_element_yield (GST_ELEMENT (elem)); ) | |
| ! } | |
| ! } | |
| ! | |
| - chain based functions are usually easy and recommended. | |
| - use gst_element_yield if you are going to push a lot of buffers. | |
| loop-based plugins | |
| ================== | |
| - one loop function for the element. | |
| - required for bytestream based plugins. | |
| simple case | |
| ----------- | |
| - one pull at the start | |
| - if you do something like this, you really should consider using a | |
| chain function as it can be significantly optimised by the scheduler. | |
| ! | |
| ! loop (GstElement *element) | |
| ! { | |
| ! MyElement *elem = MY_ELEMENT (element); | |
| ! GstBuffer *buffer; | |
| ! GstBuffer *new_buffer; | |
| ! | |
| ! buffer = gst_pad_pull (elem->sinkpad); | |
| ! | |
| ! ... | |
| ! (do something) | |
| ! ... | |
| ! | |
| ! /* you can push as many buffers (0 or more) as you want */ | |
| ! while (!done) { | |
| ! ... | |
| ! (process some more | |
| ! ... | |
| ! gst_pad_push (elem->some_sink_pad, new_buffer); | |
| ! ... | |
| ! /* if you're like going to send a large amount of buffers | |
| ! * it's a good idea to call _yield from time to time after | |
| ! * the buffer has been pushed */ | |
| ! (optional gst_element_yield (GST_ELEMENT (elem)); ) | |
| ! } | |
| ! } | |
| ! | |
| DONT!! | |
| - infinite loop | |
| ! | |
| ! loop (element) | |
| ! { | |
| ! while (TRUE) { | |
| ! ... | |
| ! _pull () | |
| ! ... | |
| ! | |
| ! ... | |
| ! _push () | |
| ! ... | |
| ! } | |
| ! } | |
| ! | |
| * you can fix this by either: | |
| - setting the GST_ELEMENT_INFINITE_LOOP flag on the element. this is | |
| not recommended, if all plugins in the pipeline (or depending on the | |
| pipeline, some plugins) have this flag, the pipeline will not run. | |
| - calling break; from time to time to get out of the loop. (duh, then | |
| it's not an infinite loop anymore). beware that the next time the loop | |
| function is called, it will be started from the top. | |
| - calling gst_element_yield() from time to time (see NOTES). | |
| - this is fine (albeit rather useless, use a chain function): | |
| ! | |
| ! loop (element) | |
| ! { | |
| ! ... | |
| ! _pull () | |
| ! ... | |
| ! | |
| ! ... | |
| ! _push () | |
| ! ... | |
| ! } | |
| ! | |
| - so is this (albeit rather useless, consider removing the while and the _yield): | |
| ! | |
| ! loop (element) | |
| ! { | |
| ! while (TRUE) { | |
| ! ... | |
| ! _pull () | |
| ! ... | |
| ! | |
| ! ... | |
| ! _push () | |
| ! ... | |
| ! gst_element_yield (element); | |
| ! } | |
| ! } | |
| ! | |
| DONT!! | |
| - allocate data, loop, free data | |
| ! | |
| ! loop (element) | |
| ! { | |
| ! | |
| ! (my funky malloc) | |
| ! | |
| ! while (TRUE) { | |
| ! | |
| ! ... | |
| ! _pull () | |
| ! ... | |
| ! | |
| ! ... | |
| ! _push () | |
| ! ... | |
| ! | |
| ! _yield () | |
| ! } | |
| ! | |
| ! (my funky free) | |
| ! | |
| ! } | |
| ! | |
| - the free will NEVER happen!. | |
| * You can fix this by: | |
| - allocating/freeing data in the state change function | |
| - you could think the following code would work too: | |
| ! | |
| ! (*WRONG* example follows) | |
| ! | |
| ! loop (element) | |
| ! { | |
| ! | |
| ! (my funky malloc) | |
| ! | |
| ! while (TRUE) { | |
| ! | |
| ! ... | |
| ! _pull () | |
| ! if (EOS) | |
| ! break; | |
| ! | |
| ! ... | |
| ! _push () | |
| ! ... | |
| ! | |
| ! _yield () | |
| ! } | |
| ! | |
| ! (my funky free) | |
| ! | |
| ! } | |
| ! | |
| but it'll only free the data if the pipeline was shut down with | |
| and EOS so don't try it. Besides, on EOS, a state change will happen | |
| anyway so free the data there. | |
| bytestream/multiple pull case | |
| ----------------------------- | |
| - same as the simple case, but you can't use a chain based function unless | |
| you want to make things a little harder then they should be. | |
| complicated case | |
| ---------------- | |
| - push and pull are completely mixed. | |
| - the flow is usually something like this: | |
| ! | |
| ! loop (element) | |
| ! { | |
| ! | |
| ! while (TRUE) { | |
| ! | |
| ! while (something) { | |
| ! ... | |
| ! do some _pull () | |
| ! ... | |
| ! do some _push () | |
| ! ... | |
| ! while (something_else) { | |
| ! ... | |
| ! if (testing) | |
| ! do some _pull () | |
| ! ... | |
| ! do some _push () | |
| ! } | |
| ! } | |
| ! | |
| ! while (something_useful) { | |
| ! ... | |
| ! _push () | |
| ! ... | |
| ! } | |
| ! } | |
| ! } | |
| ! | |
| (example made complicated on purpose, but vorbisdec comes close) | |
| - you cannot call break to avoid infinite loops and there are loops that | |
| take a significant amount of time to execute, possibly pushing/pulling | |
| a lot of buffers. | |
| * You can fix this by: | |
| - inserting gst_element_yield () in sane places, don't exagerate because | |
| every yield can potentially never return so you need to keep track of | |
| allocations (see the NOTES below). | |
| NOTES: | |
| ====== | |
| - a call to _yield() can never return. if you have data allocated on the | |
| stack before the yield, keep a pointer to it in the element struct | |
| and free it in the state change function. | |
| IMPLEMENATION DETAILS | |
| ===================== | |
| The infinite loops are only problematic if the scheduler chooses to select | |
| the plugin as an entry point in the chain. _yield() will be a nop if this is | |
| not the case. The scheduler will not select plugins with the INFINITE_LOOP | |
| flag set as entries in a chain. | |
| A _yield in an entry will hand over control to the main thread context, allowing | |
| state changes and other actions to be performed. It will basically exit the | |
| _iterate() function. spending a long time in a loop will degrade app responsiveness | |
| because _iterate will take a long time. | |
| Calling yield, pulling, pushing can potentially never return because a state change | |
| might have happened, killing off execution of the plugin. pulling/pushing buffers | |
| will cause no leaks in this case because the core will free pending buffers in a | |
| state change to READY. The plugin must free allocated data/buffers itself in the state | |
| change function if the yield didn't retrun. | |