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 input 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.