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 through your plugin, it must either be drawn on the original port with the correct stream as argument or U-turned. 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(). 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.