Add new gui module for use with overlay framebuffer devices.
authorJean-Paul Saman <jpsaman@videolan.org>
Fri, 26 Oct 2007 15:08:08 +0000 (15:08 +0000)
committerJean-Paul Saman <jpsaman@videolan.org>
Fri, 26 Oct 2007 15:08:08 +0000 (15:08 +0000)
configure.ac
modules/gui/Modules.am
modules/gui/fbosd.c [new file with mode: 0644]

index 589b23f..282d4e2 100644 (file)
@@ -5385,6 +5385,18 @@ then
   fi
 fi
 
+dnl
+dnl Framebuffer (overlay) plugin
+dnl
+AC_ARG_ENABLE(fbosd,
+  [  --enable-fbosd          fbosd interface supprt (default disabled)])
+if test "${enable_fbosd}" = "yes"
+then
+  AC_CHECK_HEADERS(linux/fb.h, [
+    VLC_ADD_PLUGINS([fbosd])
+ ])
+fi
+
 dnl
 dnl Visualisation plugin
 dnl
index 9520c3e..dc3b93c 100644 (file)
@@ -1 +1,2 @@
 SOURCES_ncurses = ncurses.c
+SOURCES_fbosd = fbosd.c
diff --git a/modules/gui/fbosd.c b/modules/gui/fbosd.c
new file mode 100644 (file)
index 0000000..8560a5a
--- /dev/null
@@ -0,0 +1,1319 @@
+/*****************************************************************************
+ * fbosd.c : framebuffer osd plugin for vlc
+ *****************************************************************************
+ * Copyright (C) 2007, the VideoLAN team
+ * $Id: vlc-fb-overlay.patch,v 1.8 2007/10/19 14:33:23 jeanpaul.saman Exp $
+ *
+ * Authors: Jean-Paul Saman
+ * Copied from modules/video_output/fb.c by Samuel Hocevar <sam@zoy.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#include <vlc/vlc.h>
+
+#include <errno.h>
+#include <stdlib.h>                                                /* free() */
+#include <string.h>                                            /* strerror() */
+#include <fcntl.h>                                                 /* open() */
+#include <unistd.h>                                               /* close() */
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>                                              /* mmap() */
+
+#include <linux/fb.h>
+
+#include <vlc_image.h>
+#include <vlc_interface.h>
+#include <vlc_input.h>
+#include <vlc_vout.h>
+#include <vlc_filter.h>
+#include <vlc_osd.h>
+#include <vlc_strings.h>
+
+// #define FBOSD_BLENDING 1
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+static int  Create    ( vlc_object_t * );
+static void Destroy   ( vlc_object_t * );
+static void Run       ( intf_thread_t * );
+
+static int  Init      ( intf_thread_t * );
+static void End       ( intf_thread_t * );
+
+static int  OpenDisplay    ( intf_thread_t * );
+static void CloseDisplay   ( intf_thread_t * );
+
+/* Load modules needed for rendering and blending */
+#ifdef FBOSD_BLENDING
+static int  OpenBlending     ( intf_thread_t * );
+static void CloseBlending    ( intf_thread_t * );
+#endif
+static int  OpenTextRenderer ( intf_thread_t * );
+static void CloseTextRenderer( intf_thread_t * );
+
+#if 0
+static int  OpenScaling      ( intf_thread_t * );
+static int  CloseScaling     ( intf_thread_t * );
+#endif
+
+/* Manipulate the overlay buffer */
+static int  OverlayCallback( vlc_object_t *, char const *,
+                             vlc_value_t, vlc_value_t, void * );
+
+static picture_t *AllocatePicture( vlc_object_t *,
+                                         video_format_t * );
+static void DeAllocatePicture( vlc_object_t *, picture_t *,
+                                     video_format_t * );
+static void SetOverlayTransparency( intf_thread_t *,
+                                    vlc_bool_t );
+static picture_t *LoadImage( intf_thread_t *, video_format_t *,
+                             char * );
+
+#ifdef FBOSD_BLENDING
+static int BlendPicture( intf_thread_t *, video_format_t *,
+                         video_format_t *, picture_t *, picture_t * );
+#else
+static picture_t *ConvertImage( intf_thread_t *, picture_t *,
+                                video_format_t *, video_format_t * );
+#endif
+static int RenderPicture( intf_thread_t *, int, int,
+                          picture_t *, picture_t * );
+static picture_t *RenderText( intf_thread_t *, const char *,
+                              video_format_t * );
+
+#define DEVICE_TEXT N_("Framebuffer device")
+#define DEVICE_LONGTEXT N_( \
+    "Framebuffer device to use for rendering (usually /dev/fb0).")
+
+#define ASPECT_RATIO_TEXT N_("Video aspect ratio")
+#define ASPECT_RATIO_LONGTEXT N_( \
+    "Aspect ratio of the video image (4:3, 16:9). Default is square pixels." )
+
+#define FBOSD_IMAGE_TEXT N_("Image file")
+#define FBOSD_IMAGE_LONGTEXT N_( \
+    "Filename of image file to use on the overlay framebuffer." )
+
+#define ALPHA_TEXT N_("Transparency of the image")
+#define ALPHA_LONGTEXT N_( "Transparency value of the new image " \
+    "used in blending. By default it set to fully opaque (255). " \
+    "(from 0 for full transparency to 255 for full opacity)" )
+
+#define FBOSD_TEXT N_("Text")
+#define FBOSD_LONGTEXT N_( "Text to display on the overlay framebuffer." )
+
+#define POSX_TEXT N_("X coordinate")
+#define POSX_LONGTEXT N_("X coordinate of the rendered image")
+
+#define POSY_TEXT N_("Y coordinate")
+#define POSY_LONGTEXT N_("Y coordinate of the rendered image")
+
+#define POS_TEXT N_("Position")
+#define POS_LONGTEXT N_( \
+  "You can enforce the picture position on the overlay " \
+  "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
+  "also use combinations of these values, e.g. 6=top-right).")
+
+#define OPACITY_TEXT N_("Opacity")
+#define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
+    "overlayed text. 0 = transparent, 255 = totally opaque. " )
+
+#define SIZE_TEXT N_("Font size, pixels")
+#define SIZE_LONGTEXT N_("Font size, in pixels. Default is -1 (use default " \
+    "font size)." )
+
+#define COLOR_TEXT N_("Color")
+#define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
+    "the video. This must be an hexadecimal (like HTML colors). The first two "\
+    "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
+    " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
+
+#define CLEAR_TEXT N_( "Clear overlay framebuffer" )
+#define CLEAR_LONGTEXT N_( "The displayed overlay images is cleared by " \
+    "making the overlay completely transparent. All previously rendered " \
+    "images and text will be cleared from the cache." )
+
+#define RENDER_TEXT N_( "Render text or image" )
+#define RENDER_LONGTEXT N_( "Render the image or text in current overlay " \
+    "buffer." )
+
+#define DISPLAY_TEXT N_( "Display on overlay framebuffer" )
+#define DISPLAY_LONGTEXT N_( "All rendered images and text will be " \
+    "displayed on the overlay framebuffer." )
+
+static int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
+static const char *ppsz_pos_descriptions[] =
+{ N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
+  N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
+
+static int pi_color_values[] = { 0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
+               0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
+               0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
+               0x00000080, 0x000000FF, 0x0000FFFF};
+static const char *ppsz_color_descriptions[] = { N_("Default"), N_("Black"),
+               N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
+               N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
+               N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
+               N_("Aqua") };
+
+vlc_module_begin();
+    set_shortname( "fbosd" );
+    set_category( CAT_INTERFACE );
+    set_subcategory( SUBCAT_INTERFACE_MAIN );
+
+    add_file( "fbosd-dev", "/dev/fb1", NULL, DEVICE_TEXT, DEVICE_LONGTEXT,
+              VLC_FALSE );
+    add_string( "fbosd-aspect-ratio", "", NULL, ASPECT_RATIO_TEXT,
+                ASPECT_RATIO_LONGTEXT, VLC_TRUE );
+
+    add_string( "fbosd-image", NULL, NULL, FBOSD_IMAGE_TEXT,
+                FBOSD_IMAGE_LONGTEXT, VLC_TRUE );
+    add_string( "fbosd-text", NULL, NULL, FBOSD_TEXT,
+                FBOSD_LONGTEXT, VLC_TRUE );
+
+#ifdef FBOSD_BLENDING
+    add_integer_with_range( "fbosd-alpha", 255, 0, 255, NULL, ALPHA_TEXT,
+                            ALPHA_LONGTEXT, VLC_TRUE );
+
+#endif
+
+    set_section( N_("Position"), NULL );
+    add_integer( "fbosd-x", 0, NULL, POSX_TEXT,
+                 POSX_LONGTEXT, VLC_FALSE );
+    add_integer( "fbosd-y", 0, NULL, POSY_TEXT,
+                 POSY_LONGTEXT, VLC_FALSE );
+    add_integer( "fbosd-position", 8, NULL, POS_TEXT, POS_LONGTEXT, VLC_TRUE );
+        change_integer_list( pi_pos_values, ppsz_pos_descriptions, 0 );
+
+    set_section( N_("Font"), NULL );
+    add_integer_with_range( "fbosd-font-opacity", 255, 0, 255, NULL,
+        OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
+    add_integer( "fbosd-font-color", 0x00FFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
+                 VLC_FALSE );
+        change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
+    add_integer( "fbosd-font-size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT,
+                 VLC_FALSE );
+
+    set_section( N_("Commands"), NULL );
+    add_bool( "fbosd-clear", VLC_FALSE, NULL, CLEAR_TEXT, CLEAR_LONGTEXT, VLC_TRUE );
+    add_bool( "fbosd-render", VLC_FALSE, NULL, RENDER_TEXT, RENDER_LONGTEXT, VLC_TRUE );
+    add_bool( "fbosd-display", VLC_FALSE, NULL, DISPLAY_TEXT, DISPLAY_LONGTEXT, VLC_TRUE );
+
+    set_description( _("GNU/Linux osd/overlay framebuffer interface") );
+    set_capability( "interface", 10 );
+    set_callbacks( Create, Destroy );
+vlc_module_end();
+
+/*****************************************************************************
+ * intf_sys_t: interface framebuffer method descriptor
+ *****************************************************************************/
+struct intf_sys_t
+{
+    /* Framebuffer information */
+    int                         i_fd;                       /* device handle */
+    struct fb_var_screeninfo    var_info;        /* current mode information */
+    vlc_bool_t                  b_pan;     /* does device supports panning ? */
+    struct fb_cmap              fb_cmap;                /* original colormap */
+    uint16_t                    *p_palette;              /* original palette */
+
+    /* Overlay framebuffer format */
+    video_format_t  fmt_out;
+    picture_t       *p_overlay;
+    size_t          i_page_size;                                /* page size */
+    int             i_width;
+    int             i_height;
+    int             i_aspect;
+    int             i_bytes_per_pixel;
+
+    /* Image and Picture rendering */
+    image_handler_t *p_image;
+#ifdef FBOSD_BLENDING
+    filter_t *p_blend;                              /* alpha blending module */
+#endif
+    filter_t *p_text;                                /* text renderer module */
+#if 0
+    filter_t *p_scale;                                     /* scaling module */
+#endif
+    vlc_bool_t b_force_crop;                    /* force cropping of picture */
+    int i_crop_x, i_crop_y, i_crop_width, i_crop_height;         /* cropping */
+
+    /* Misc */
+    char            *psz_file;
+    char            *psz_text;
+
+    vlc_bool_t      b_image;
+    vlc_bool_t      b_text;
+
+    /* Font style */
+    text_style_t    *p_style;                                /* font control */
+
+    /* Positon of image/text */
+    vlc_bool_t      b_absolute;
+    int             i_x;
+    int             i_y;
+    int             i_pos;
+
+    int             i_alpha;                      /* transparency for images */
+
+    /* commands control */
+    vlc_bool_t      b_need_update;    /* update display with \overlay buffer */
+    vlc_bool_t      b_clear;      /* clear overlay buffer make it tranparent */
+    vlc_bool_t      b_render;   /* render an image or text in overlay buffer */
+};
+
+/*****************************************************************************
+ * Create: allocates FB interface thread output method
+ *****************************************************************************/
+static int Create( vlc_object_t *p_this )
+{
+    intf_thread_t *p_intf = (intf_thread_t *)p_this;
+    intf_sys_t    *p_sys;
+    char          *psz_aspect;
+
+    /* Allocate instance and initialize some members */
+    p_intf->p_sys = p_sys = malloc( sizeof( intf_sys_t ) );
+    if( !p_intf->p_sys )
+    {
+        msg_Err( p_intf, "out of memory" );
+        return VLC_ENOMEM;
+    };
+    memset( p_sys, 0, sizeof(intf_sys_t) );
+
+    p_sys->p_style = malloc( sizeof( text_style_t ) );
+    if( !p_sys->p_style )
+    {
+        free( p_intf->p_sys );
+        msg_Err( p_intf, "out of memory" );
+        return VLC_ENOMEM;
+    }
+    p_intf->p_libvlc->pf_memcpy( p_sys->p_style, &default_text_style, sizeof( text_style_t ) );
+
+    p_intf->pf_run = Run;
+
+    p_sys->p_image = image_HandlerCreate( p_this );
+    if( !p_sys->p_image )
+    {
+        free( p_intf->p_sys->p_style );
+        free( p_intf->p_sys );
+        msg_Err( p_intf, "out of memory" );
+        return VLC_ENOMEM;
+    }
+
+#ifdef FBOSD_BLENDING
+    p_sys->i_alpha = var_CreateGetIntegerCommand( p_intf, "fbosd-alpha" );
+    var_AddCallback( p_intf, "fbosd-alpha", OverlayCallback, NULL );
+#else
+    p_sys->i_alpha = 255;
+#endif
+    p_sys->i_aspect = -1;
+    psz_aspect =
+            var_CreateGetNonEmptyString( p_intf, "fbosd-aspect-ratio" );
+    if( psz_aspect )
+    {
+        char *psz_parser = strchr( psz_aspect, ':' );
+
+        if( psz_parser )
+        {
+            *psz_parser++ = '\0';
+            p_sys->i_aspect = ( atoi( psz_aspect )
+                              * VOUT_ASPECT_FACTOR ) / atoi( psz_parser );
+            p_sys->fmt_out.i_aspect = p_sys->i_aspect;
+        }
+        msg_Dbg( p_intf, "using aspect ratio %d:%d",
+                  atoi( psz_aspect ), atoi( psz_parser ) );
+
+        free( psz_aspect );
+        psz_aspect = NULL;
+    }
+
+    /* Use PAL by default */
+    p_sys->i_width  = p_sys->fmt_out.i_width  = 704;
+    p_sys->i_height = p_sys->fmt_out.i_height = 576;
+
+    p_sys->psz_file =
+            var_CreateGetNonEmptyStringCommand( p_intf, "fbosd-image" );
+    var_AddCallback( p_intf, "fbosd-image", OverlayCallback, NULL );
+    if( p_sys->psz_file &&  *p_sys->psz_file )
+        p_sys->b_image = VLC_TRUE;
+
+    p_sys->psz_text =
+            var_CreateGetNonEmptyStringCommand( p_intf, "fbosd-text" );
+    var_AddCallback( p_intf, "fbosd-text", OverlayCallback, NULL );
+    if( p_sys->psz_text &&  *p_sys->psz_text )
+        p_sys->b_text = VLC_TRUE;
+
+    p_sys->i_pos = var_CreateGetIntegerCommand( p_intf, "fbosd-position" );
+    p_sys->i_x = var_CreateGetIntegerCommand( p_intf, "fbosd-x" );
+    p_sys->i_y = var_CreateGetIntegerCommand( p_intf, "fbosd-y" );
+
+    var_AddCallback( p_intf, "fbosd-position", OverlayCallback, NULL );
+    var_AddCallback( p_intf, "fbosd-x", OverlayCallback, NULL );
+    var_AddCallback( p_intf, "fbosd-y", OverlayCallback, NULL );
+
+    p_sys->p_style->i_font_size =
+            var_CreateGetIntegerCommand( p_intf, "fbosd-font-size" );
+    p_sys->p_style->i_font_color =
+            var_CreateGetIntegerCommand( p_intf, "fbosd-font-color" );
+    p_sys->p_style->i_font_alpha = 255 -
+            var_CreateGetIntegerCommand( p_intf, "fbosd-font-opacity" );
+
+    var_AddCallback( p_intf, "fbosd-font-color", OverlayCallback, NULL );
+    var_AddCallback( p_intf, "fbosd-font-size", OverlayCallback, NULL );
+    var_AddCallback( p_intf, "fbosd-font-opacity", OverlayCallback, NULL );
+
+    p_sys->b_clear = var_CreateGetBoolCommand( p_intf, "fbosd-clear" );
+    p_sys->b_render = var_CreateGetBoolCommand( p_intf, "fbosd-render" );
+    p_sys->b_need_update = var_CreateGetBoolCommand( p_intf, "fbosd-display" );
+
+    var_AddCallback( p_intf, "fbosd-clear", OverlayCallback, NULL );
+    var_AddCallback( p_intf, "fbosd-render", OverlayCallback, NULL );
+    var_AddCallback( p_intf, "fbosd-display", OverlayCallback, NULL );
+
+    /* Check if picture position was overridden */
+    p_sys->b_absolute = VLC_TRUE;
+    if( ( p_sys->i_x >= 0 ) && ( p_sys->i_y >= 0 ) )
+    {
+        p_sys->b_absolute = VLC_FALSE;
+        p_sys->i_y = (p_sys->i_y < p_sys->i_height) ?
+                        p_sys->i_y : p_sys->i_height;
+        p_sys->i_x = (p_sys->i_x < p_sys->i_width) ?
+                        p_sys->i_x : p_sys->i_width;
+    }
+
+    /* Initialize framebuffer */
+    if( OpenDisplay( p_intf ) )
+    {
+        Destroy( VLC_OBJECT(p_intf) );
+        return VLC_EGENERIC;
+    }
+
+    Init( p_intf );
+
+#ifdef FBOSD_BLENDING
+    /* Load the blending module */
+    if( OpenBlending( p_intf ) )
+    {
+        msg_Err( p_intf, "Unable to load image blending module" );
+        Destroy( VLC_OBJECT(p_intf) );
+        return VLC_EGENERIC;
+    }
+#endif
+
+    /* Load text renderer module */
+    if( OpenTextRenderer( p_intf ) )
+    {
+        msg_Err( p_intf, "Unable to load text rendering module" );
+        Destroy( VLC_OBJECT(p_intf) );
+        return VLC_EGENERIC;
+    }
+#if 0
+    /* Load scaling module */
+    if( OpenScaling( p_intf ) )
+    {
+        msg_Err( p_intf, "Unable to load image scaling module" );
+        Destroy( VLC_OBJECT(p_intf) );
+        return VLC_EGENERIC;
+    }
+#endif
+    p_sys->b_render = VLC_TRUE;
+    p_sys->b_need_update = VLC_TRUE;
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Destroy: destroy FB interface thread output method
+ *****************************************************************************
+ * Terminate an output method created by Create
+ *****************************************************************************/
+static void Destroy( vlc_object_t *p_this )
+{
+    intf_thread_t *p_intf = (intf_thread_t *)p_this;
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+
+    p_sys->b_need_update = VLC_FALSE;
+    p_sys->b_render = VLC_FALSE;
+    p_sys->b_clear = VLC_FALSE;
+
+#ifdef FBOSD_BLENDING
+    var_DelCallback( p_intf, "fbosd-alpha", OverlayCallback, NULL );
+    var_Destroy( p_intf, "fbosd-alpha" );
+#endif
+
+    var_DelCallback( p_intf, "fbosd-x", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-y", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-position", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-image", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-text", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-font-size", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-font-color", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-font-opacity", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-clear", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-render", OverlayCallback, NULL );
+    var_DelCallback( p_intf, "fbosd-display", OverlayCallback, NULL );
+
+    var_Destroy( p_intf, "fbosd-x" );
+    var_Destroy( p_intf, "fbosd-y" );
+    var_Destroy( p_intf, "fbosd-position" );
+    var_Destroy( p_intf, "fbosd-image" );
+    var_Destroy( p_intf, "fbosd-text" );
+    var_Destroy( p_intf, "fbosd-font-size" );
+    var_Destroy( p_intf, "fbosd-font-color" );
+    var_Destroy( p_intf, "fbosd-font-opacity" );
+    var_Destroy( p_intf, "fbosd-clear" );
+    var_Destroy( p_intf, "fbosd-render" );
+    var_Destroy( p_intf, "fbosd-display" );
+
+    var_Destroy( p_intf, "fbosd-aspect-ratio" );
+
+    CloseDisplay( p_intf );
+
+#ifdef FBOSD_BLENDING
+    if( p_sys->p_blend ) CloseBlending( p_intf );
+#endif
+    if( p_sys->p_text )  CloseTextRenderer( p_intf );
+#if 0
+    if( p_sys->p_scale ) CloseScaling( p_intf );
+#endif
+    if( p_sys->p_image )
+        image_HandlerDelete( p_sys->p_image );
+    if( p_sys->p_overlay )
+        p_sys->p_overlay->pf_release( p_sys->p_overlay );
+
+    free( p_sys->psz_file );
+    free( p_sys->psz_text );
+    free( p_sys->p_style );
+    free( p_sys );
+}
+
+#ifdef FBOSD_BLENDING
+static int OpenBlending( intf_thread_t *p_intf )
+{
+    if( p_intf->p_sys->p_blend ) return VLC_EGENERIC;
+
+    p_intf->p_sys->p_blend =
+            vlc_object_create( p_intf, VLC_OBJECT_FILTER );
+    vlc_object_attach( p_intf->p_sys->p_blend, p_intf );
+    p_intf->p_sys->p_blend->fmt_out.video.i_x_offset =
+        p_intf->p_sys->p_blend->fmt_out.video.i_y_offset = 0;
+    p_intf->p_sys->p_blend->fmt_out.video.i_aspect =
+            p_intf->p_sys->fmt_out.i_aspect;
+    p_intf->p_sys->p_blend->fmt_out.video.i_chroma =
+            p_intf->p_sys->fmt_out.i_chroma;
+    if( config_GetInt( p_intf, "freetype-yuvp" ) )
+        p_intf->p_sys->p_blend->fmt_in.video.i_chroma =
+                VLC_FOURCC('Y','U','V','P');
+    else
+        p_intf->p_sys->p_blend->fmt_in.video.i_chroma =
+                VLC_FOURCC('Y','U','V','A');
+
+    p_intf->p_sys->p_blend->p_module =
+        module_Need( p_intf->p_sys->p_blend, "video blending", 0, 0 );
+
+    if( !p_intf->p_sys->p_blend->p_module )
+        return VLC_EGENERIC;
+
+    return VLC_SUCCESS;
+}
+
+static void CloseBlending( intf_thread_t *p_intf )
+{
+    if( p_intf->p_sys->p_blend )
+    {
+        if( p_intf->p_sys->p_blend->p_module )
+            module_Unneed( p_intf->p_sys->p_blend,
+                           p_intf->p_sys->p_blend->p_module );
+
+        vlc_object_detach( p_intf->p_sys->p_blend );
+        vlc_object_destroy( p_intf->p_sys->p_blend );
+    }
+}
+#endif
+
+static int OpenTextRenderer( intf_thread_t *p_intf )
+{
+    char *psz_modulename = NULL;
+
+    if( p_intf->p_sys->p_text ) return VLC_EGENERIC;
+
+    p_intf->p_sys->p_text =
+            vlc_object_create( p_intf, VLC_OBJECT_FILTER );
+    vlc_object_attach( p_intf->p_sys->p_text, p_intf );
+
+    p_intf->p_sys->p_text->fmt_out.video.i_width =
+        p_intf->p_sys->p_text->fmt_out.video.i_visible_width =
+        p_intf->p_sys->i_width;
+    p_intf->p_sys->p_text->fmt_out.video.i_height =
+        p_intf->p_sys->p_text->fmt_out.video.i_visible_height =
+        p_intf->p_sys->i_height;
+
+    psz_modulename = var_CreateGetString( p_intf, "text-renderer" );
+    if( psz_modulename && *psz_modulename )
+    {
+        p_intf->p_sys->p_text->p_module =
+            module_Need( p_intf->p_sys->p_text, "text renderer",
+                            psz_modulename, VLC_TRUE );
+    }
+    if( !p_intf->p_sys->p_text->p_module )
+    {
+        p_intf->p_sys->p_text->p_module =
+            module_Need( p_intf->p_sys->p_text, "text renderer", 0, 0 );
+    }
+    if( psz_modulename ) free( psz_modulename );
+
+    if( !p_intf->p_sys->p_text->p_module )
+        return VLC_EGENERIC;
+
+    return VLC_SUCCESS;
+}
+
+static void CloseTextRenderer( intf_thread_t *p_intf )
+{
+    if( p_intf->p_sys->p_text )
+    {
+        if( p_intf->p_sys->p_text->p_module )
+            module_Unneed( p_intf->p_sys->p_text,
+                           p_intf->p_sys->p_text->p_module );
+
+        vlc_object_detach( p_intf->p_sys->p_text );
+        vlc_object_destroy( p_intf->p_sys->p_text );
+    }
+}
+#if 0
+static int OpenScaling( intf_thread_t *p_intf )
+{
+    if( p_intf->p_sys->p_scale ) return VLC_EGENERIC;
+
+    p_intf->p_sys->p_scale =
+            vlc_object_create( p_intf, VLC_OBJECT_FILTER );
+    vlc_object_attach( p_intf->p_sys->p_scale, p_intf );
+    p_intf->p_sys->p_scale->fmt_out.video.i_chroma =
+        p_intf->p_sys->p_scale->fmt_in.video.i_chroma =
+            p_intf->p_sys->fmt_out.i_chroma;
+
+    /* XXX: We'll also be using it for YUVA and RGBA blending ... */
+    p_intf->p_sys->p_scale->fmt_in.video.i_width =
+        p_intf->p_sys->p_scale->fmt_in.video.i_height = 32;
+    p_intf->p_sys->p_scale->fmt_out.video.i_width =
+        p_intf->p_sys->p_scale->fmt_out.video.i_height = 16;
+
+    p_intf->p_sys->p_scale->p_module =
+        module_Need( p_intf->p_sys->p_scale, "video filter2", 0, 0 );
+
+    if( !p_intf->p_sys->p_scale->p_module )
+        return VLC_EGENERIC;
+
+    return VLC_SUCCESS;
+}
+
+static int CloseScaling( intf_thread_t *p_intf )
+{
+    if( p_intf->p_sys->p_scale )
+    {
+        if( p_intf->p_sys->p_scale->p_module )
+            module_Unneed( p_intf->p_sys->p_scale,
+                           p_intf->p_sys->p_scale->p_module );
+
+        vlc_object_detach( p_intf->p_sys->p_scale );
+        vlc_object_destroy( p_intf->p_sys->p_scale );
+    }
+}
+#endif
+
+/*****************************************************************************
+ * AllocatePicture:
+ * allocate a picture buffer for use with the overlay fb.
+ *****************************************************************************/
+static picture_t *AllocatePicture( vlc_object_t *p_this,
+                                   video_format_t *p_fmt )
+{
+    picture_t *p_pic = malloc( sizeof( picture_t ) );
+    if( !p_pic ) return NULL;
+
+    if( !p_fmt->p_palette &&
+        ( p_fmt->i_chroma == VLC_FOURCC('Y','U','V','P') ) )
+    {
+        p_fmt->p_palette = malloc( sizeof(video_palette_t) );
+        if( !p_fmt->p_palette )
+        {
+            free( p_pic );
+            return NULL;
+        }
+    }
+    else p_fmt->p_palette = NULL;
+
+    p_pic->p_data_orig = NULL;
+
+    vout_AllocatePicture( p_this, p_pic, p_fmt->i_chroma,
+                          p_fmt->i_width, p_fmt->i_height, p_fmt->i_aspect );
+
+    if( !p_pic->i_planes )
+    {
+        free( p_pic );
+        free( p_fmt->p_palette );
+        return NULL;
+    }
+    return p_pic;
+}
+
+/*****************************************************************************
+ * DeAllocatePicture:
+ * Deallocate a picture buffer and free all associated memory.
+ *****************************************************************************/
+static void DeAllocatePicture( vlc_object_t *p_this, picture_t *p_pic,
+                               video_format_t *p_fmt )
+{
+    if( p_pic && p_pic->p_data_orig ) free( p_pic->p_data_orig );
+    if( p_pic && p_pic->pf_release ) p_pic->pf_release( p_pic );
+    if( p_fmt && p_fmt->p_palette )
+    {
+        free( p_fmt->p_palette );
+        p_fmt->p_palette = NULL;
+    }
+    p_pic = NULL;
+}
+
+/*****************************************************************************
+ * SetOverlayTransparency: Set the transparency for this overlay fb,
+ * - VLC_TRUE is make transparent
+ * - VLC_FALSE is make non tranparent
+ *****************************************************************************/
+static void SetOverlayTransparency( intf_thread_t *p_intf,
+                                    vlc_bool_t b_transparent )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+    size_t i_size = p_sys->fmt_out.i_width * p_sys->fmt_out.i_height
+                        * p_sys->i_bytes_per_pixel;
+    size_t i_page_size = (p_sys->i_page_size > i_size) ?
+                            i_size : p_sys->i_page_size;
+
+    if( p_sys->p_overlay )
+    {
+        msg_Dbg( p_intf, "Make overlay %s",
+                 b_transparent ? "transparent" : "opaque" );
+        memset( p_sys->p_overlay->p[0].p_pixels, 0x00, i_page_size );
+        if( b_transparent )
+            memset( p_sys->p_overlay->p[0].p_pixels, 0xFF, i_page_size );
+    }
+}
+
+#ifdef FBOSD_BLENDING
+/*****************************************************************************
+ * BlendPicture: Blend two pictures together..
+ *****************************************************************************/
+static int BlendPicture( intf_thread_t *p_intf, video_format_t *p_fmt_src,
+                         video_format_t *p_fmt_dst, picture_t *p_pic_src,
+                         picture_t *p_pic_dst )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+    if( p_sys->p_blend && p_sys->p_blend->p_module )
+    {
+        int i_x_offset = p_sys->i_x;
+        int i_y_offset = p_sys->i_y;
+
+        memcpy( &p_sys->p_blend->fmt_in.video, p_fmt_src, sizeof( video_format_t ) );
+#if 0
+        msg_Dbg( p_intf, "Blending pictures %p %4.4s (%dx%d) %d bits %d planes: 0=%p 1=%p 2=%p 3=%p",
+                 p_pic_src, (char*)&p_fmt_src->i_chroma,
+                 p_sys->p_blend->fmt_in.video.i_width, p_sys->p_blend->fmt_in.video.i_height,
+                 p_fmt_src->i_bits_per_pixel,
+                 p_pic_src->i_planes,
+                 p_pic_src->p[0].p_pixels, p_pic_src->p[1].p_pixels,
+                 p_pic_src->p[2].p_pixels, p_pic_src->p[3].p_pixels );
+        msg_Dbg( p_intf, "Blending pictures %p %4.4s (%dx%d) %d bits %d planes: 0=%p 1=%p 2=%p 3=%p",
+                 p_pic_dst, (char*)&p_fmt_dst->i_chroma,
+                 p_fmt_dst->i_width, p_fmt_dst->i_height,
+                 p_fmt_dst->i_bits_per_pixel,
+                 p_pic_dst->i_planes,
+                 p_pic_dst->p[0].p_pixels, p_pic_dst->p[1].p_pixels,
+                 p_pic_dst->p[2].p_pixels, p_pic_dst->p[3].p_pixels );
+#endif
+        /* Update the output picture size */
+        p_sys->p_blend->fmt_out.video.i_width =
+            p_sys->p_blend->fmt_out.video.i_visible_width =
+                p_fmt_dst->i_width;
+        p_sys->p_blend->fmt_out.video.i_height =
+            p_sys->p_blend->fmt_out.video.i_visible_height =
+                p_fmt_dst->i_height;
+
+        i_x_offset = __MAX( i_x_offset, 0 );
+        i_y_offset = __MAX( i_y_offset, 0 );
+
+        p_sys->p_blend->pf_video_blend( p_sys->p_blend, p_pic_dst,
+            p_pic_src, p_pic_dst, i_x_offset, i_y_offset,
+            p_sys->i_alpha );
+
+        return VLC_SUCCESS;
+    }
+    return VLC_EGENERIC;
+}
+#endif
+
+/*****************************************************************************
+ * RenderPicture: Render the picture into the p_dest buffer.
+ * We don't take transparent pixels into account, so we don't have to blend
+ * the two images together.
+ *****************************************************************************/
+static int RenderPicture( intf_thread_t *p_intf, int i_x_offset, int i_y_offset,
+                          picture_t *p_src, picture_t *p_dest )
+{
+    int i;
+
+    if( !p_dest && !p_src ) return VLC_EGENERIC;
+
+    for( i = 0; i < p_src->i_planes ; i++ )
+    {
+        if( p_src->p[i].i_pitch == p_dest->p[i].i_pitch )
+        {
+            /* There are margins, but with the same width : perfect ! */
+            p_intf->p_libvlc->pf_memcpy(
+                         p_dest->p[i].p_pixels, p_src->p[i].p_pixels,
+                         p_src->p[i].i_pitch * p_src->p[i].i_visible_lines );
+        }
+        else
+        {
+            /* We need to proceed line by line */
+            uint8_t *p_in  = p_src->p[i].p_pixels;
+            uint8_t *p_out = p_dest->p[i].p_pixels;
+
+            int i_x = i_x_offset * p_src->p[i].i_pixel_pitch;
+            int i_x_clip, i_y_clip;
+
+            /* Check boundaries, clip the image if necessary */
+            i_x_clip = ( i_x + p_src->p[i].i_visible_pitch ) - p_dest->p[i].i_visible_pitch;
+            i_x_clip = ( i_x_clip > 0 ) ? i_x_clip : 0;
+
+            i_y_clip = ( i_y_offset + p_src->p[i].i_visible_lines ) - p_dest->p[i].i_visible_lines;
+            i_y_clip = ( i_y_clip > 0 ) ? i_y_clip : 0;
+#if 0
+            msg_Dbg( p_intf, "i_pitch (%d,%d), (%d,%d)/(%d,%d)",
+                     p_dest->p[i].i_visible_pitch, p_src->p[i].i_visible_pitch,
+                     i_x_offset, i_y_offset, i_x, i_x_clip );
+#endif
+            if( ( i_y_offset <= p_dest->p[i].i_visible_lines ) &&
+                ( i_x <= p_dest->p[i].i_visible_pitch ) )
+            {
+                int i_line;
+
+                p_out += ( i_y_offset * p_dest->p[i].i_pitch );
+                for( i_line = 0; i_line < ( p_src->p[i].i_visible_lines - i_y_clip ); i_line++ )
+                {
+                    p_intf->p_libvlc->pf_memcpy( p_out + i_x, p_in,
+                                                 p_src->p[i].i_visible_pitch - i_x_clip );
+                    p_in += p_src->p[i].i_pitch;
+                    p_out += p_dest->p[i].i_pitch;
+                }
+            }
+        }
+    }
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * RenderText - Render text to the desired picture format
+ *****************************************************************************/
+static picture_t *RenderText( intf_thread_t *p_intf, const char *psz_text,
+                              video_format_t *p_fmt )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+    subpicture_region_t *p_region;
+    picture_t *p_dest = NULL;
+
+    if( !psz_text ) return p_dest;
+
+    if( p_sys->p_text && p_sys->p_text->p_module )
+    {
+        p_region = (subpicture_region_t *) malloc( sizeof(subpicture_region_t) );
+        if( !p_region )
+            return p_dest;
+
+        memset( p_region, 0, sizeof(subpicture_region_t) );
+
+        p_region->psz_text = strdup( p_sys->psz_text );
+        p_region->p_style = p_sys->p_style;
+
+        p_region->fmt.i_chroma = VLC_FOURCC('T','E','X','T');
+        p_region->fmt.i_aspect = 0;
+        p_region->fmt.i_width = p_region->fmt.i_visible_width = 0;
+        p_region->fmt.i_height = p_region->fmt.i_visible_height = 0;
+        p_region->fmt.i_x_offset = 0;
+        p_region->fmt.i_y_offset = 0;
+
+        p_region->i_align = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
+
+        if( p_sys->p_text->pf_render_text )
+        {
+            video_format_t fmt_out;
+
+            memset( &fmt_out, 0, sizeof(video_format_t) );
+
+            p_sys->p_text->pf_render_text( p_sys->p_text,
+                                           p_region, p_region );
+
+#ifndef FBOSD_BLENDING
+            fmt_out.i_chroma = p_fmt->i_chroma;
+            p_dest = ConvertImage( p_intf, &p_region->picture,
+                                   &p_region->fmt, &fmt_out );
+#else
+            fmt_out = p_region->fmt;
+            fmt_out.i_bits_per_pixel = 32;
+            memcpy( p_fmt, &fmt_out, sizeof(video_format_t) );
+            p_dest = AllocatePicture( VLC_OBJECT(p_intf), &fmt_out );
+            if( !p_dest )
+            {
+                if( p_region->picture.pf_release )
+                    p_region->picture.pf_release( &p_region->picture );
+                free( p_region->psz_text  );
+                free( p_region );
+                return NULL;
+            }
+            vout_CopyPicture( VLC_OBJECT(p_intf), p_dest, &p_region->picture );
+#endif
+            if( p_region->picture.pf_release )
+                p_region->picture.pf_release( &p_region->picture );
+            free( p_region->psz_text  );
+            free( p_region );
+            return p_dest;
+        }
+        free( p_region->psz_text );
+        free( p_region );
+    }
+    return p_dest;
+}
+
+/*****************************************************************************
+ * LoadImage: Load an image from file into a picture buffer.
+ *****************************************************************************/
+static picture_t *LoadImage( intf_thread_t *p_intf, video_format_t *p_fmt,
+                             char *psz_file )
+{
+    picture_t  *p_pic = NULL;
+
+    if( psz_file && p_intf->p_sys->p_image )
+    {
+        video_format_t fmt_in, fmt_out;
+
+        memset( &fmt_in, 0, sizeof(fmt_in) );
+        memset( &fmt_out, 0, sizeof(fmt_out) );
+
+        fmt_out.i_chroma = p_fmt->i_chroma;
+        p_pic = image_ReadUrl( p_intf->p_sys->p_image, psz_file,
+                               &fmt_in, &fmt_out );
+
+        msg_Dbg( p_intf, "image size %dx%d chroma %4.4s",
+                 fmt_out.i_width, fmt_out.i_height,
+                 (char *)&p_fmt->i_chroma );
+    }
+    return p_pic;
+}
+
+#ifndef FBOSD_BLENDING
+/*****************************************************************************
+ * Convertmage: Convert image to another fourcc
+ *****************************************************************************/
+static picture_t *ConvertImage( intf_thread_t *p_intf, picture_t *p_pic,
+                         video_format_t *p_fmt_in, video_format_t *p_fmt_out )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+    picture_t  *p_old = NULL;
+
+    if( p_sys->p_image )
+    {
+        p_old = image_Convert( p_sys->p_image, p_pic, p_fmt_in, p_fmt_out );
+
+        msg_Dbg( p_intf, "converted image size %dx%d chroma %4.4s",
+                 p_fmt_out->i_width, p_fmt_out->i_height,
+                 (char *)&p_fmt_out->i_chroma );
+    }
+    return p_old;
+}
+#endif
+
+/*****************************************************************************
+ * Init: initialize framebuffer video thread output method
+ *****************************************************************************/
+static int Init( intf_thread_t *p_intf )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+
+    /* Initialize the output structure: RGB with square pixels, whatever
+     * the input format is, since it's the only format we know */
+    switch( p_sys->var_info.bits_per_pixel )
+    {
+    case 8: /* FIXME: set the palette */
+        p_sys->fmt_out.i_chroma = VLC_FOURCC('R','G','B','2'); break;
+    case 15:
+        p_sys->fmt_out.i_chroma = VLC_FOURCC('R','V','1','5'); break;
+    case 16:
+        p_sys->fmt_out.i_chroma = VLC_FOURCC('R','V','1','6'); break;
+    case 24:
+        p_sys->fmt_out.i_chroma = VLC_FOURCC('R','V','2','4'); break;
+    case 32:
+        p_sys->fmt_out.i_chroma = VLC_FOURCC('R','V','3','2'); break;
+    default:
+        msg_Err( p_intf, "unknown screen depth %i",
+                 p_sys->var_info.bits_per_pixel );
+        return VLC_EGENERIC;
+    }
+
+    p_sys->fmt_out.i_bits_per_pixel = p_sys->var_info.bits_per_pixel;
+    p_sys->fmt_out.i_width  = p_sys->i_width;
+    p_sys->fmt_out.i_height = p_sys->i_height;
+
+    /* Assume we have square pixels */
+    if( p_sys->i_aspect < 0 )
+    {
+        p_sys->fmt_out.i_aspect = ( p_sys->i_width
+                                  * VOUT_ASPECT_FACTOR ) / p_sys->i_height;
+    }
+    else p_sys->fmt_out.i_aspect = p_sys->i_aspect;
+
+    p_sys->fmt_out.i_sar_num = p_sys->fmt_out.i_sar_den = 1;
+
+    /* Allocate overlay buffer */
+    p_sys->p_overlay = AllocatePicture( VLC_OBJECT(p_intf),
+                                        &p_sys->fmt_out );
+    if( !p_sys->p_overlay ) return VLC_EGENERIC;
+
+    SetOverlayTransparency( p_intf, VLC_TRUE );
+
+    /* We know the chroma, allocate a buffer which will be used
+     * to write to the overlay framebuffer */
+    p_sys->p_overlay->p->i_pixel_pitch = p_sys->i_bytes_per_pixel;
+    p_sys->p_overlay->p->i_lines = p_sys->var_info.yres;
+    p_sys->p_overlay->p->i_visible_lines = p_sys->var_info.yres;
+
+    if( p_sys->var_info.xres_virtual )
+    {
+        p_sys->p_overlay->p->i_pitch = p_sys->var_info.xres_virtual
+                             * p_sys->i_bytes_per_pixel;
+    }
+    else
+    {
+        p_sys->p_overlay->p->i_pitch = p_sys->var_info.xres
+                             * p_sys->i_bytes_per_pixel;
+    }
+
+    p_sys->p_overlay->p->i_visible_pitch = p_sys->var_info.xres
+                                 * p_sys->i_bytes_per_pixel;
+
+    p_sys->p_overlay->i_planes = 1;
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * End: terminate framebuffer interface
+ *****************************************************************************/
+static void End( intf_thread_t *p_intf )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+
+    /* CleanUp */
+    SetOverlayTransparency( p_intf, VLC_FALSE );
+    if( p_sys->p_overlay )
+    {
+        write( p_sys->i_fd, p_sys->p_overlay->p[0].p_pixels, p_sys->i_page_size );
+    }
+
+    DeAllocatePicture( VLC_OBJECT(p_intf), p_intf->p_sys->p_overlay,
+                       &p_intf->p_sys->fmt_out );
+    p_intf->p_sys->p_overlay = NULL;
+}
+
+/*****************************************************************************
+ * OpenDisplay: initialize framebuffer
+ *****************************************************************************/
+static int OpenDisplay( intf_thread_t *p_intf )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf->p_sys;
+    char *psz_device;                             /* framebuffer device path */
+    struct fb_fix_screeninfo    fix_info;     /* framebuffer fix information */
+
+    /* Open framebuffer device */
+    if( !(psz_device = config_GetPsz( p_intf, "fbosd-dev" )) )
+    {
+        msg_Err( p_intf, "don't know which fb osd/overlay device to open" );
+        return VLC_EGENERIC;
+    }
+
+    p_sys->i_fd = open( psz_device, O_RDWR );
+    if( p_sys->i_fd == -1 )
+    {
+        msg_Err( p_intf, "cannot open %s (%s)", psz_device, strerror(errno) );
+        free( psz_device );
+        return VLC_EGENERIC;
+    }
+    free( psz_device );
+
+    /* Get framebuffer device information */
+    if( ioctl( p_sys->i_fd, FBIOGET_VSCREENINFO, &p_sys->var_info ) )
+    {
+        msg_Err( p_intf, "cannot get fb info (%s)", strerror(errno) );
+        close( p_sys->i_fd );
+        return VLC_EGENERIC;
+    }
+
+    /* Get some info on the framebuffer itself */
+    if( ioctl( p_sys->i_fd, FBIOGET_FSCREENINFO, &fix_info ) == 0 )
+    {
+        p_sys->i_width = p_sys->fmt_out.i_width = p_sys->var_info.xres;
+        p_sys->i_height = p_sys->fmt_out.i_height = p_sys->var_info.yres;
+    }
+
+    /* FIXME: if the image is full-size, it gets cropped on the left
+     * because of the xres / xres_virtual slight difference */
+    msg_Dbg( p_intf, "%ix%i (virtual %ix%i)",
+             p_sys->var_info.xres, p_sys->var_info.yres,
+             p_sys->var_info.xres_virtual,
+             p_sys->var_info.yres_virtual );
+
+    p_sys->fmt_out.i_width = p_sys->i_width;
+    p_sys->fmt_out.i_height = p_sys->i_height;
+
+    p_sys->p_palette = NULL;
+    p_sys->b_pan = ( fix_info.ypanstep || fix_info.ywrapstep );
+
+    switch( p_sys->var_info.bits_per_pixel )
+    {
+    case 8:
+        p_sys->p_palette = malloc( 8 * 256 * sizeof( uint16_t ) );
+        if( !p_sys->p_palette )
+        {
+            msg_Err( p_intf, "out of memory" );
+            close( p_sys->i_fd );
+            return VLC_ENOMEM;
+        }
+        p_sys->fb_cmap.start = 0;
+        p_sys->fb_cmap.len = 256;
+        p_sys->fb_cmap.red = p_sys->p_palette;
+        p_sys->fb_cmap.green  = p_sys->p_palette + 256 * sizeof( uint16_t );
+        p_sys->fb_cmap.blue   = p_sys->p_palette + 2 * 256 * sizeof( uint16_t );
+        p_sys->fb_cmap.transp = p_sys->p_palette + 3 * 256 * sizeof( uint16_t );
+
+        /* Save the colormap */
+        ioctl( p_sys->i_fd, FBIOGETCMAP, &p_sys->fb_cmap );
+
+        p_sys->i_bytes_per_pixel = 1;
+        break;
+
+    case 15:
+    case 16:
+        p_sys->i_bytes_per_pixel = 2;
+        break;
+
+    case 24:
+        p_sys->i_bytes_per_pixel = 3;
+        break;
+
+    case 32:
+        p_sys->i_bytes_per_pixel = 4;
+        break;
+
+    default:
+        msg_Err( p_intf, "screen depth %d is not supported",
+                         p_sys->var_info.bits_per_pixel );
+
+        close( p_sys->i_fd );
+        return VLC_EGENERIC;
+    }
+
+    p_sys->i_page_size = p_sys->i_width * p_sys->i_height
+                         * p_sys->i_bytes_per_pixel;
+
+    msg_Dbg( p_intf, "framebuffer type=%d, visual=%d, ypanstep=%d, "
+             "ywrap=%d, accel=%d", fix_info.type, fix_info.visual,
+             fix_info.ypanstep, fix_info.ywrapstep, fix_info.accel );
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * CloseDisplay: terminate FB interface thread
+ *****************************************************************************/
+static void CloseDisplay( intf_thread_t *p_intf )
+{
+    intf_sys_t *p_sys = (intf_sys_t *) p_intf;
+
+    /* Restore palette */
+    if( p_sys->var_info.bits_per_pixel == 8 )
+    {
+        ioctl( p_sys->i_fd, FBIOPUTCMAP, &p_sys->fb_cmap );
+        free( p_sys->p_palette );
+        p_sys->p_palette = NULL;
+    }
+
+    /* Close fb */
+    close( p_sys->i_fd );
+}
+
+/*****************************************************************************
+ * Run: rc thread
+ *****************************************************************************
+ * This part of the interface is in a separate thread so that we can call
+ * exec() from within it without annoying the rest of the program.
+ *****************************************************************************/
+static void Run( intf_thread_t *p_intf )
+{
+    intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
+
+    while( !intf_ShouldDie( p_intf ) )
+    {
+        if( p_sys->b_render )
+        {
+            if( p_sys->b_image )
+            {
+                picture_t *p_pic;
+                p_pic = LoadImage( p_intf, &p_sys->fmt_out, p_sys->psz_file );
+                if( p_pic )
+                {
+                    RenderPicture( p_intf, p_sys->i_x, p_sys->i_y,
+                                   p_pic, p_sys->p_overlay );
+                    p_pic->pf_release( p_pic );
+                }
+                var_SetString( p_intf, "fbosd-image", "" );
+                p_sys->b_image = VLC_FALSE;
+            }
+
+            if( p_sys->b_text )
+            {
+                picture_t *p_text;
+#ifndef FBOSD_BLENDING
+                p_text = RenderText( p_intf, p_sys->psz_text, &p_sys->fmt_out );
+                if( p_text )
+                {
+                    RenderPicture( p_intf, p_sys->i_x, p_sys->i_y,
+                                   p_text, p_sys->p_overlay );
+                    p_text->pf_release( p_text );
+                }
+#else
+                video_format_t fmt_in;
+                memset( &fmt_in, 0, sizeof(video_format_t) );
+                p_text = RenderText( p_intf, p_sys->psz_text, &fmt_in );
+                if( p_text )
+                {
+                    BlendPicture( p_intf, &fmt_in, &p_sys->fmt_out,
+                                  p_text, p_sys->p_overlay );
+                    msg_Dbg( p_intf, "releasing picture" );
+                    DeAllocatePicture( VLC_OBJECT( p_intf ), p_text, &fmt_in );
+                }
+#endif
+                var_SetString( p_intf, "fbosd-text", "" );
+                p_sys->b_text = VLC_FALSE;
+            }
+            p_sys->b_render = VLC_FALSE;
+        }
+
+        if( p_sys->b_clear )
+        {
+            SetOverlayTransparency( p_intf, VLC_TRUE );
+
+            var_SetString( p_intf, "fbosd-image", "" );
+            var_SetString( p_intf, "fbosd-text", "" );
+
+            p_sys->b_image = VLC_FALSE;
+            p_sys->b_text = VLC_FALSE;
+            p_sys->b_clear = VLC_FALSE;
+            p_sys->b_need_update = VLC_TRUE;
+        }
+
+        if( p_sys->b_need_update && p_sys->p_overlay )
+        {
+            write( p_sys->i_fd, p_sys->p_overlay->p[0].p_pixels,
+                   p_sys->i_page_size );
+            lseek( p_sys->i_fd, 0, SEEK_SET );
+            p_sys->b_need_update = VLC_FALSE;
+        }
+
+        if( vlc_CPU() & CPU_CAPABILITY_FPU )
+            msleep( INTF_IDLE_SLEEP );
+        else
+            msleep( 1000 );
+    }
+
+    End( p_intf );
+}
+
+static int OverlayCallback( vlc_object_t *p_this, char const *psz_cmd,
+                 vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t *) p_this;
+    intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
+
+    if( !strncmp( psz_cmd, "fbosd-image", 11 ) )
+    {
+        free( p_sys->psz_file );
+        p_sys->psz_file = strdup( newval.psz_string );
+        p_sys->b_image = VLC_TRUE;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-text", 10 ) )
+    {
+        free( p_sys->psz_text );
+        p_sys->psz_text = strdup( newval.psz_string );
+        p_sys->b_text = VLC_TRUE;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-x", 7 ) )
+    {
+        p_sys->b_absolute = VLC_FALSE;
+        p_sys->i_x = (newval.i_int < p_sys->i_width) ?
+                        newval.i_int : p_sys->i_width;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-y", 7 ) )
+    {
+        p_sys->b_absolute = VLC_FALSE;
+        p_sys->i_y = (newval.i_int < p_sys->i_height) ?
+                        newval.i_int : p_sys->i_height;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-position", 14 ) )
+    {
+        p_sys->b_absolute = VLC_TRUE;
+        p_sys->i_pos = newval.i_int;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-font-size", 15 ) )
+    {
+        p_sys->p_style->i_font_size = newval.i_int;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-font-color", 16 ) )
+    {
+        p_sys->p_style->i_font_color = newval.i_int;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-font-opacity", 18 ) )
+    {
+        p_sys->p_style->i_font_alpha = 255 - newval.i_int;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-display", 13 ) )
+    {
+        p_sys->b_need_update = VLC_TRUE;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-render", 12 ) )
+    {
+        p_sys->b_render = VLC_TRUE;
+    }
+    else if( !strncmp( psz_cmd, "fbosd-clear", 11 ) )
+    {
+        p_sys->b_clear = VLC_TRUE;
+    }
+#ifdef FBOSD_BLENDING
+    else if( !strncmp( psz_cmd, "fbosd-alpha", 11 ) )
+    {
+        p_sys->i_alpha = newval.i_int;
+    }
+#endif
+    return VLC_SUCCESS;
+}