xine's output layer
Post plugin layer
In this section you will learn, how the powerful post plugin architecture
works and how to write post plugins.
General principle of post plugins
The name "post plugin" comes from "postprocessing" which already describes
what these plugins are supposed to do: they take video frames, audio
buffers or subpicture planes as they leave the decoders and apply arbitrary
processing to them. Then they pass processed elements on to the output
loops. Post plugins can not only be chained to process the predecessor's
output and advance the data to their successor, they can form arbitrary trees,
since post plugins can have any number of inputs and outputs. Additionally,
the interconnection of the plugins currently inserted in such a tree can
be changed on the fly during playback. The public function
xine_post_wire() is used by frontends to form such
connections.
Due to the variety of possible applications, the interface post plugins have
to implement is just the basic foundation. The complexity comes from hooking
your post plugin into the engine data paths for video frames and audio buffers,
intercepting the data and performing your operation on them. This is done by
taking the interface of a video or audio port, replacing some of the functions
with your own ones and passing the interface to the decoder or predecessor
post plugin that is going to feed you with data by accessing this interface
and by doing that, calling the functions you provide. From there you can do
almost anything you want. Constructing video frames from audio buffers to
visualize sound is possible as well as just outputting an integer giving the
average brightness of an image. It is also possible to invent post plugins
with no output (not very useful, unless the plugin has some side-effect) or
no input at all; for the latter you have to create your own pthread, otherwise
your plugin will not do anything. An audio signal generator could be
implemented like this. The various data types, post plugins can
accept as input or offer as output are defined in xine.h
as XINE_POST_DATA_* defines.
Some terminology used in the following explanations:
down direction:
The direction from the decoders to the output. This is the way video or audio
data (actual content and meta information) usually travels through the engine.
up direction:
The direction from the output to the decoders. This is the way some video or audio
metadata like metronom timestamps travel through the engine.
interception:
Post plugins are inserted into the engine data paths by the means of the decorator
design pattern. This works by taking engine structures with member funtions like
video or audio ports, video frames or overlay managers and inserting your own functions
into a copy of this structure. This is called interception. This modified structure
is then passed up to the plugin that uses it and thus calls your replaced functions.
Writing a xine post plugin
The post plugin API is declared in src/xine-engine/post.h
The plugin info of post plugins contains the post plugin type, which is one of the
XINE_POST_TYPE_* defines and the init_class function of the plugin.
post_plugin_t *open_plugin(post_class_t *class_gen, int inputs, xine_audio_port_t **audio_target, xine_video_port_t **video_target);
Returns an instance of the plugin. Some post plugins evaluate inputs
to open a variable number of inputs. Since almost all post plugins have audio or video
outputs, you can hand in a NULL-terminated array of ports to connect to these outputs.
In this function you can also intercept these ports so that your plugin is actually used.
There is a helper function to initialize a post_plugin_t, which you are
encouraged to use: _x_post_init().
char *get_identifier(post_class_t *class_gen);
This function returns a short identifier describing the plugin.
char *get_description(post_class_t *class_gen);
This function returns a plaintext, one-line string describing the plugin.
void dispose(post_class_t *class_gen);
This function frees the memory used by the video out plugin class object.
The post_plugin_t structure contains the publicly visible
part of the post plugin with the audio and video inputs and the type of
the post plugin. Not publicly visible are the lists of all inputs and outputs,
the dispose() function and some internal stuff which
plugins do not have to worry about.
void dispose(post_plugin_t *this_gen);
This function frees the memory used by the plugin instance, but not necessarily
immediately. Since post plugins enter their own functions into engine structures,
they might still be needed when dispose() is being called.
They maintain a usage counter to detect that. To check for such a condition, you
should use the _x_post_dispose() helper function like that:
if (_x_post_dispose(this))
really_free(this);
_x_post_dispose() frees any ressources allocated by any of the
post plugin helper functions and returns boolean true, if the plugin is not needed
any more.
Interception
Currently, we have four engine structures which can be intercepted by post plugins:
video ports, video frames, overlay managers and audio ports. You could do this
interception manually, but since this is quite a complex process, there are helper
functions to assist you and their usage is encouraged.
Intercepting a video port
post_video_port_t *_x_post_intercept_video_port(post_plugin_t *post,
xine_video_port_t *port, post_in_t **input, post_out_t **output);
This function will intercept port and returns a structure
for you to pass up. All functions in the port will be replaced with dummy
pass through functions that do nothing but relaying the call down to the original
port. If you want (that is, input or output are
not NULL), this function will also create the input and output descriptors complete
with rewiring functions and add them to the relevant lists.
This is required, if you want this port to be advertised by the plugin to the outside world.
post_video_port_t makes a variety of interception schemes very easy.
If you want to replace any of the default functions with your own, just enter it
into new_port. You can use original_port
from within your function to propagate calls down to the original port.
The constraint is that your functions have to ensure that every original
port held open scores one usage counter point, so that opened ports are always
closed before the plugin is disposed. Therefore, you should use the macro
_x_post_inc_usage() before calling
original_port->open() and use the macro
_x_post_dec_usage() after calling
original_port->close(). Note that _x_post_dec_usage()
might dispose the plugin, when dispose() has been called
earlier and usage count drops to zero, so do never touch plugin structures after
_x_post_dec_usage(). In addition, you must never call a port
function of the original port when the port is not open.
Intercepting video frames or the overlay manager of the port is even easier.
You do not have to reimplement get_frame() or
get_overlay_manager(). Just enter a intercept_frame
or intercept_ovl function which returns boolean true, if
you want to intercept. The functions to insert in the intercepted frame or overlay
manager are taken from new_frame and new_manager
respectively. Note that the defaults are reversed: If you do not enter such a
decision function for either one, all frames and no overlay manager will be intercepted.
For convenience post_video_port_t also contains pointers to the
current stream and to the current post plugin and a user_data pointer, where you
can put in anything you need in addition. If your port is used by more than one
thread, you can also enforce locking on the port, frame or overlay manager level
by entering a lock into port_lock, frame_lock or
manager_lock respectively.
Intercepting an audio port
Audio port interception is just a stripped down version of video port interception.
Everything related to frames and overlay manager is not needed and audio buffers
do not need to be intercepted, since they have no member functions. Everything else
of the above still applies.
Intercepting overlay manager
void _x_post_intercept_overlay_manager(video_overlay_manager_t *original, post_video_port_t *port);
Interception of the overlay manager is done automatically when your
intercept_ovl() decision function returns boolean true.
Should you ever decide not to use that, interception can be done with this helper
function, which simply creates an intercepted overlay manager with dummy
pass through functions in port->new_manager and stores the original
manager in port->original_manager.
No matter how you intercepted the overlay manager, your own replacement
functions will receive port->new_manager as the overlay manager
argument. But you most likely want to have access to the post_video_port_t
from within your functions. For that, there exists a pointer retrieval function:
post_video_port_t *_x_post_ovl_manager_to_port(video_overlay_manager_t *manager);
Intercepting a video frame
vo_frame_t *_x_post_intercept_video_frame(vo_frame_t *frame, post_video_port_t *port);
vo_frame_t *_x_post_restore_video_frame(vo_frame_t *frame, post_video_port_t *port);
Interception of video frames is done automatically when your
intercept_frame() decision function returns boolean true or
when there is no such function in post_video_port_t.
Should you ever decide not to use that, interception can be done with the helper
function _x_post_intercept_video_frame().
Since the same video frame can be in use in the decoder and in the output and in
any post plugin in between at the same time, simply modifying the frame
structure does not work, because every user of the frame needs to see his version
and the frame must always travel along the same path through the plugins for its
entire lifetime. To ensure that, _x_post_intercept_video_frame()
provides a shallow copy of the frame structure with the original frame attached to
copy->next. This copy will be filled with your own
frame functions from port->new_frame and with dummy pass
through functions for those you did not provide. This way, every part of xine
using this frame will see its own frame structure with a list of frame
contexts from down the data path attached to frame->next.
_x_post_restore_video_frame() reverses this and should be
used when the frame is freed or disposed.
Your own replacement functions will receive the copied frame as as argument.
But you most likely want to have access to the post_video_port_t
from within your functions. For that, there exists a pointer retrieval function:
post_video_port_t *_x_post_video_frame_to_port(vo_frame_t *frame);
The constraint is that your functions have to ensure that every intercepted
frame scores one usage counter point, so that these frames are always
freed or disposed before the plugin is disposed. Therefore, you should use the macro
_x_post_inc_usage() before calling
_x_post_intercept_video_frame() and use the macro
_x_post_dec_usage() after calling
_x_post_restore_video_frame(). Note that _x_post_dec_usage()
might dispose the plugin, when dispose() has been called
earlier and usage count drops to zero, so do never touch plugin structures after
_x_post_dec_usage().
From within your own frame functions, you can propagate calls to the original
frame by calling a function on frame->next. Since
modifications to the frame can travel both upwards and downwards (decoders and
output can modify the frame), changes need to be copied between the frame
structure contexts. You should use the _x_post_frame_copy_down()
and _x_post_frame_copy_up() helper functions like that:
_x_post_frame_copy_down(frame, frame->next);
frame->next->draw(frame->next, stream);
_x_post_frame_copy_up(frame, frame->next);
If your post plugin modifies the content of the frame, you have to modify
a deep copy of the frame, because the decoder might still use the frame as
a reference frame for future decoding. The usual procedure is:
modified_frame = port->original_port->get_frame(port->original_port, ...);
_x_post_frame_copy_down(frame, modified_frame);
copy_and_modify(frame, modified_frame);
skip = modified_frame->draw(modified_frame, stream);
_x_post_frame_copy_up(frame, modified_frame);
modified_frame->free(modified_frame);
When the output receives a frame via draw(),
it usually receives the stream where the frame
originates as well and modifies the state of this stream by passing
the frame through the stream's metronom. Sometimes this is unwanted.
For example, when you pass the same frame to the output more than once, it
will confuse metronom. To solve this, you can call
frame->next->draw() with NULL as the stream.
You might also want to prevent frames from being passed down to the output
completely, because your post plugin creates something else from these frames,
but does not need them to be drawn. In both these situations, you have
to call the helper function _x_post_frame_u_turn()
when the frame is drawn, because this does some housekeeping which the
decoder might expect to take place.
The following diagram summarizes the situations of a video frame passing
through a post plugin:
video frame passing through a post plugin
Summary of constraints
Call _x_post_inc_usage() before port open()
before any other port function.
Call _x_post_inc_usage() before issueing an intercepted frame.
Call _x_post_dec_usage() after port close()
and do not call any port functions after that.
Call _x_post_dec_usage() after restoring a frame.
When a frame is drawn by your plugin, it must either be drawn on the original
port with the correct stream as argument or U-turned (or passed through a
private metronom manually).
If your post plugin keeps locked frames, release them when your input port is being
closed.
Do not assume your plugin is alive after _x_post_dec_usage().
Rewiring and the ticket system
Rewiring is the reconnection of one post plugin's outputs and another post plugin's
inputs. This can happen on the fly during playback, which makes this a very delicate
process. In one such input to output connection, only the output is active by either
writing data directly to the connected intput or by calling functions there. Therefore
we have to notify only the output, when it is rewired. This is done by calling the
rewire() member function of the corresponding
xine_post_out_t when the frontend issues a rewiring on this output.
This is done from the frontend thread for the rewire operation to return synchroneously.
But since this can happen on the fly, we have to assure that no other thread is relying
on the connection while we modify it. For this, threads working within post plugins
have to be accounted and on demand suspended in safe positions. For this, xine offers
a synchronization facility called "tickets".
Ticket system principles and operations
The idea of the ticket system is based on an extended read-write lock, where there can
be many readers in parallel, but only one exclusive writer. A certain interface might
require you to have a ticket before calling its functions. The ticket system now
allows multiple threads to operate within the component behind the interface by
granting as many tickets as needed. But should an outside operation require exclusive
access to the component, all granted tickets can be revoked and have to be given back
by the threads who hold them, which suspends the threads. After the exclusive
operation, tickets will be reissued so all suspended threads can continue where they
stopped. We will now look at the ticket primitives in detail:
acquire()
You receive a new ticket. If the ticket is currently revoked, you can be blocked
until it is reissued. There is one exception to this: You can acquire a revoked
ticket, if you revoked it atomic yourself. You can also acquire a ticket irrevocably.
Between acquire and release of an irrevocable ticket, it is guaranteed that
you will not be blocked by ticket revocation.
release()
You give a ticket back when you do not need it any longer. If the ticket is
currently revoked, you can be blocked until it is reissued. If you acquired the
ticket irrevocably, you have to release it irrevocably as well.
renew()
You must only call this function, when the ticket has been revoked, so be
sure to check ticket_revoked before. You give the ticket
back and receive a new one. In between, you can be blocked until the ticket is
reissued. You have to renew irrevocably, if you cannot assure that the thread holds
no irrevocable tickets. If you can assure this, renew revocably.
revoke()
This function can only be called by the xine engine, plugins do not have access to it.
It revokes all tickets and after finite time, all threads will run into a
acquire(), release() or renew()
and will be suspended there. Then the revocation returns and you can modify structures
or call functions with the knowledge that all ticket holders will remain in safe
positions. When you additionally need exclusive access where no other revoker
can interfere, you have to revoke atomic.
issue()
This function can only be called by the xine engine, plugins do not have access to it.
It ends ticket revocation and hands out new tickets to all threads that applied with a
acquire() or renew(). If you revoked the
tickets atomic, you have to issue them atomic.
When you use the ticket system in any way, you have to obey to the following rules:
Assure to release irrevocable tickets ater a finite time.
Assure to release or renew revocable tickets ater a finite time.
Assure to reissue tickets you revoked atomic after a finite time.
Pair calls properly.
Never revoke a ticket you hold.
Never acquire a ticket you revoked atomic before.
Never acquire a ticket revocable more than once.
Ticket handling in decoder and post plugins
The contract of the video and audio port interfaces is that you need to have
the port ticket, when you want to call functions of these interfaces. The decoder
plugins do not have to worry about this at all, since the decoder loops inside the
engine handle the ticketing. Post plugins get the ticket assigned by the engine,
but usually, a post plugin does not create a new thread, it is called by the
decoder thread and therefore already owns the necessary ticket. All port functions
are also ticket-safe as they follow all the rules given above.
You only have to remember that tickets need to be renewed as soon as possible,
when the are revoked. For this, the helper function
_x_post_rewire() should be used in prominent locations
where it is safe to be suspended. Candidates for such locations are at the
beginning of the port's open() and
get_frame()/get_buffer() functions.
The default pass through implementations for intercepted ports already do this.
The port tickets are revoked, whenever a rewiring takes place or the engine
switches into pause mode. The tickets are reissued, when the rewiring is finished
or the engine switches from pause mode into playback. Some post plugins might
contain critical parts, where they must not be interrupted by a rewire or pause.
These sections can be enclosed in _x_post_lock() and
_x_post_unlock(), which will simply acquire and release an
irrevocable ticket for you. As long as you hold such a ticket, it is guaranteed
that you will never be interrupted by a pause or rewire.
Video output
In order to allow for device-dependant acceleration features, xine
calls upon the video output plugin for more than just displaying
images. The tasks performed by the video plugins are:
Allocation of vo_frame_t structures and their
subsequent destruction.
Allocation of memory for use by one frame (this is to allow
for the ability of some video output plugins to map frames directly
into video-card memory hence removing the need for the frame to
be copied across the PCI/AGP bus at display time).
Most important, the ability to render/copy a given
frame to the output device.
Optionally the copying of the frame from a file dependant
colour-space and depth into the frame structure. This is to allow for
on-the fly colour-space conversion and scaling if required (e.g. the XShm
ouput plugin uses this mechanism).
Although these extra responsibilities add great complexity to your
plugin it should be noted that they allow plugins to take full advantage
of any special hardware-acceleration without sacrificing flexibility.
Writing a xine video out plugin
The video out plugin API is declared in src/xine-engine/video_out.h
The plugin info of video out plugins contains the visual type, priority,
and the init_class function of the plugin.
The visual_type field is used by xine to
determine if the GUI used by the client is supported by the plugin
(e.g. X11 output plugins require the GUI to be running under the
X Windowing system) and also to determine the type of information passed to the
open_plugin() function as its visual parameter.
char *get_description(video_driver_class_t *this_gen);
This function returns a plaintext, one-line string describing the plugin.
char *get_identifier(video_driver_class_t *this_gen);
This function returns a shorter identifier describing the plugin.
void dispose(video_driver_class_t *this_gen);
This function frees the memory used by the video out plugin class object.
vo_driver_t *get_instance(video_driver_class_t *class_gen, const void *visual);
Returns an instance of the plugin.
The visual is a pointer to a visual-dependant
structure/variable. For example, if you had previously claimed your
plugin was of the VISUAL_TYPE_X11 type, this would be a pointer
to a x11_visual_t, which amongst other things hold the
Display variable associated with the
X-server xine should display to. See plugin source-code for other
VISUAL_TYPE_* constants and associated structures. Note that this
field is provided by the client application and so if you wish to add another visual
type you will either need to extend an existing client or write a new
one.
uint32_t get_capabilities(vo_driver_t *this_gen);
Returns a bit mask describing the output plugin's capabilities.
You may logically OR the VO_CAP_* constants together to get
a suitable bit-mask (via the '|' operator).
int get_property(vo_driver_t *self, int property);
int set_property(vo_driver_t *self, int property, int value);
void get_property_min_max(vo_driver_t *self, int property, int *min, int *max);
Handle the getting, setting of properties and define their bounds.
Valid property IDs can be found in the video_out.h
header file.
int gui_data_exchange(vo_driver_t *self, int data_type, void *data);
Accepts various forms of data from the UI (e.g. the mouse has moved or the
window has been hidden). Look at existing plugins for examples of data
exchanges from various UIs.
vo_frame_t *alloc_frame(vo_driver_t *self);
Returns a pointer to a xine video frame.
Typically the video plugin will add private fields to the end of the
vo_frame_t structure which are used for internal purposes by the plugin.
The function pointers within the frame structure provide a mechanism for the
driver to retain full control of how the frames are managed and rendered to. If
the VO_CAP_COPIES_IMAGE flag was set in the plugins capabilities then the
copy field is required and will be called sequentially for each 16-pixel high
strip in the image. The plugin may then decide, based on the frame's format, how
this is copied into the frame.
void update_frame_format(vo_driver_t *self, vo_frame_t *img, uint32_t width, uint32_t height, double ratio, int format, int flags);
This function will be called each time the colour-depth/space or size of a frame changes.
Typically this function would allocate sufficient memory for the frame, assign the pointers
to the individual planes of the frame to the base field of the
frame and perform any driver-specific changes.
void display_frame(vo_driver_t *self, vo_frame_t *vo_img);
Renders a given frame to the output device.
void overlay_begin(vo_driver_t *self, vo_frame_t *vo_img, int changed);
void overlay_blend(vo_driver_t *self, vo_frame_t *vo_img, vo_overlay_t *overlay);
void overlay_end(vo_driver_t *self, vo_frame_t *vo_img);
These are used to blend overlays on frames. overlay_begin() is called,
when the overlay appears for the first time, overlay_blend() is then
called for every subsequent frame and overlay_end() is called, when
the overlay should disappear again.
int redraw_needed(vo_driver_t *self);
Queries the driver, if the current frame needs to be drawn again.
void dispose(vo_driver_t *self);
Releases all resources and frees the plugin.