/* * video driver for framebuffer device * copyright (C) 2003 Joey Parrish * * This file is part of MPlayer. * * MPlayer 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. * * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include "config.h" #include "video_out.h" #include "video_out_internal.h" #include "fastmemcpy.h" #include "sub/sub.h" #include "mp_msg.h" #include "aspect.h" #include "libavutil/common.h" static const vo_info_t info = { "Framebuffer Device", "fbdev2", "Joey Parrish ", "" }; const LIBVO_EXTERN(fbdev2) static void set_bpp(struct fb_var_screeninfo *p, int bpp) { p->bits_per_pixel = (bpp + 1) & ~1; p->red.msb_right = p->green.msb_right = p->blue.msb_right = p->transp.msb_right = 0; p->transp.offset = p->transp.length = 0; p->blue.offset = 0; switch (bpp) { case 32: p->transp.offset = 24; p->transp.length = 8; case 24: p->red.offset = 16; p->red.length = 8; p->green.offset = 8; p->green.length = 8; p->blue.length = 8; break; case 16: p->red.offset = 11; p->green.length = 6; p->red.length = 5; p->green.offset = 5; p->blue.length = 5; break; case 15: p->red.offset = 10; p->green.length = 5; p->red.length = 5; p->green.offset = 5; p->blue.length = 5; break; case 12: p->red.offset = 8; p->green.length = 4; p->red.length = 4; p->green.offset = 4; p->blue.length = 4; break; } } static char *fb_dev_name = NULL; // such as /dev/fb0 static int fb_dev_fd; // handle for fb_dev_name static uint8_t *frame_buffer = NULL; // mmap'd access to fbdev static uint8_t *center = NULL; // where to begin writing our image (centered?) static struct fb_fix_screeninfo fb_finfo; // fixed info static struct fb_var_screeninfo fb_vinfo; // variable info static struct fb_var_screeninfo fb_orig_vinfo; // variable info to restore later static unsigned short fb_ored[256], fb_ogreen[256], fb_oblue[256]; static struct fb_cmap fb_oldcmap = { 0, 256, fb_ored, fb_ogreen, fb_oblue }; static int fb_cmap_changed = 0; // to restore map static int fb_pixel_size; // 32: 4 24: 3 16: 2 15: 2 static int fb_bpp; // 32: 32 24: 24 16: 16 15: 15 static size_t fb_size; // size of frame_buffer static int fb_line_len; // length of one line in bytes static void (*draw_alpha_p)(int w, int h, unsigned char *src, unsigned char *srca, int stride, unsigned char *dst, int dstride); static uint8_t *next_frame = NULL; // for double buffering static int in_width; static int in_height; static struct fb_cmap *make_directcolor_cmap(struct fb_var_screeninfo *var) { int i, cols, rcols, gcols, bcols; uint16_t *red, *green, *blue; struct fb_cmap *cmap; rcols = 1 << var->red.length; gcols = 1 << var->green.length; bcols = 1 << var->blue.length; /* Make our palette the length of the deepest color */ cols = FFMAX3(rcols, gcols, bcols); red = malloc(3 * cols * sizeof(red[0])); if(!red) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't allocate red palette with %d entries.\n", cols); return NULL; } green = red + cols; blue = green + cols; for (i = 0; i < cols; i++) { red[i] = (65535/(rcols-1)) * i; green[i] = (65535/(gcols-1)) * i; blue[i] = (65535/(bcols-1)) * i; } cmap = malloc(sizeof(struct fb_cmap)); if(!cmap) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't allocate color map\n"); free(red); return NULL; } cmap->start = 0; cmap->transp = 0; cmap->len = cols; cmap->red = red; cmap->blue = blue; cmap->green = green; cmap->transp = NULL; return cmap; } static int fb_preinit(int reset) { static int fb_preinit_done = 0; static int fb_err = -1; if (reset) { fb_preinit_done = 0; return 0; } if (fb_preinit_done) return fb_err; fb_preinit_done = 1; if (!fb_dev_name && !(fb_dev_name = getenv("FRAMEBUFFER"))) fb_dev_name = strdup("/dev/fb0"); mp_msg(MSGT_VO, MSGL_V, "[fbdev2] Using device %s\n", fb_dev_name); if ((fb_dev_fd = open(fb_dev_name, O_RDWR)) == -1) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't open %s: %s\n", fb_dev_name, strerror(errno)); goto err_out; } if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't get VSCREENINFO: %s\n", strerror(errno)); goto err_out; } fb_orig_vinfo = fb_vinfo; fb_bpp = fb_vinfo.bits_per_pixel; /* 16 and 15 bpp is reported as 16 bpp */ if (fb_bpp == 16) fb_bpp = fb_vinfo.red.length + fb_vinfo.green.length + fb_vinfo.blue.length; fb_err = 0; return 0; err_out: if (fb_dev_fd >= 0) close(fb_dev_fd); fb_dev_fd = -1; fb_err = -1; return -1; } static int preinit(const char *subdevice) { if (subdevice) { free(fb_dev_name); fb_dev_name = strdup(subdevice); } return fb_preinit(0); } static int config(uint32_t width, uint32_t height, uint32_t d_width, uint32_t d_height, uint32_t flags, char *title, uint32_t format) { struct fb_cmap *cmap; int fs = flags & VOFLAG_FULLSCREEN; int x_offset = vo_dx + (d_width - width ) / 2; int y_offset = vo_dy + (d_height - height) / 2; x_offset = av_clip(x_offset, 0, fb_vinfo.xres - width); y_offset = av_clip(y_offset, 0, fb_vinfo.yres - height); in_width = width; in_height = height; if (fb_vinfo.xres < in_width || fb_vinfo.yres < in_height) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Screensize is smaller than video size (%dx%d < %dx%d)\n", fb_vinfo.xres, fb_vinfo.yres, in_width, in_height); return 1; } switch (fb_bpp) { case 32: draw_alpha_p = vo_draw_alpha_rgb32; break; case 24: draw_alpha_p = vo_draw_alpha_rgb24; break; case 16: draw_alpha_p = vo_draw_alpha_rgb16; break; case 15: draw_alpha_p = vo_draw_alpha_rgb15; break; case 12: draw_alpha_p = vo_draw_alpha_rgb12; break; default: return 1; } if (vo_config_count == 0) { if (ioctl(fb_dev_fd, FBIOGET_FSCREENINFO, &fb_finfo)) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't get FSCREENINFO: %s\n", strerror(errno)); return 1; } if (fb_finfo.type != FB_TYPE_PACKED_PIXELS) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] type %d not supported\n", fb_finfo.type); return 1; } switch (fb_finfo.visual) { case FB_VISUAL_TRUECOLOR: break; case FB_VISUAL_DIRECTCOLOR: mp_msg(MSGT_VO, MSGL_V, "[fbdev2] creating cmap for directcolor\n"); if (ioctl(fb_dev_fd, FBIOGETCMAP, &fb_oldcmap)) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] can't get cmap: %s\n", strerror(errno)); return 1; } if (!(cmap = make_directcolor_cmap(&fb_vinfo))) return 1; if (ioctl(fb_dev_fd, FBIOPUTCMAP, cmap)) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] can't put cmap: %s\n", strerror(errno)); free(cmap->red); free(cmap); return 1; } fb_cmap_changed = 1; free(cmap->red); free(cmap); break; default: mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] visual: %d not yet supported\n", fb_finfo.visual); return 1; } fb_size = fb_finfo.smem_len; fb_line_len = fb_finfo.line_length; if ((frame_buffer = (uint8_t *) mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_dev_fd, 0)) == (uint8_t *) -1) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't mmap %s: %s\n", fb_dev_name, strerror(errno)); return 1; } } center = frame_buffer + x_offset * fb_pixel_size + y_offset * fb_line_len; #ifndef USE_CONVERT2FB if (!(next_frame = realloc(next_frame, in_width * in_height * fb_pixel_size))) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't malloc next_frame: %s\n", strerror(errno)); return 1; } #endif if (fs) memset(frame_buffer, '\0', fb_line_len * fb_vinfo.yres); return 0; } static int query_format(uint32_t format) { // open the device, etc. if (fb_preinit(0)) return 0; if ((format & IMGFMT_BGR_MASK) == IMGFMT_BGR) { int fb_target_bpp = format & 0xff; set_bpp(&fb_vinfo, fb_target_bpp); fb_vinfo.xres_virtual = fb_vinfo.xres; fb_vinfo.yres_virtual = fb_vinfo.yres; if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_vinfo)) // Needed for Intel framebuffer with 32 bpp fb_vinfo.transp.length = fb_vinfo.transp.offset = 0; if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_vinfo)) { mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't put VSCREENINFO: %s\n", strerror(errno)); return 0; } fb_pixel_size = fb_vinfo.bits_per_pixel / 8; fb_bpp = fb_vinfo.bits_per_pixel; if (fb_bpp == 16) fb_bpp = fb_vinfo.red.length + fb_vinfo.green.length + fb_vinfo.blue.length; if (fb_bpp == fb_target_bpp) return VFCAP_CSP_SUPPORTED|VFCAP_CSP_SUPPORTED_BY_HW|VFCAP_ACCEPT_STRIDE; } return 0; } static void draw_alpha(int x0, int y0, int w, int h, unsigned char *src, unsigned char *srca, int stride) { unsigned char *dst; int dstride; #ifdef USE_CONVERT2FB dst = center + (fb_line_len * y0) + (x0 * fb_pixel_size); dstride = fb_line_len; #else dst = next_frame + (in_width * y0 + x0) * fb_pixel_size; dstride = in_width * fb_pixel_size; #endif (*draw_alpha_p)(w, h, src, srca, stride, dst, dstride); } static void draw_osd(void) { vo_draw_text(in_width, in_height, draw_alpha); } // all csp support stride static int draw_frame(uint8_t *src[]) { return 1; } static int draw_slice(uint8_t *src[], int stride[], int w, int h, int x, int y) { uint8_t *in = src[0]; #ifdef USE_CONVERT2FB uint8_t *dest = center + (fb_line_len * y) + (x * fb_pixel_size); int next = fb_line_len; #else uint8_t *dest = next_frame + (in_width * y + x) * fb_pixel_size; int next = in_width * fb_pixel_size; #endif memcpy_pic(dest, in, w * fb_pixel_size, h, next, stride[0]); return 0; } static void check_events(void) { } static void flip_page(void) { #ifndef USE_CONVERT2FB int out_offset = 0, in_offset = 0; memcpy_pic(center + out_offset, next_frame + in_offset, in_width * fb_pixel_size, in_height, fb_line_len, in_width * fb_pixel_size); #endif } static void uninit(void) { if (fb_cmap_changed) { if (ioctl(fb_dev_fd, FBIOPUTCMAP, &fb_oldcmap)) mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't restore original cmap\n"); fb_cmap_changed = 0; } free(next_frame); if (fb_dev_fd >= 0) { if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_orig_vinfo)) mp_msg(MSGT_VO, MSGL_ERR, "[fbdev2] Can't reset original fb_var_screeninfo: %s\n", strerror(errno)); close(fb_dev_fd); fb_dev_fd = -1; } if(frame_buffer) munmap(frame_buffer, fb_size); next_frame = frame_buffer = NULL; fb_preinit(1); // so that later calls to preinit don't fail } static int control(uint32_t request, void *data) { switch (request) { case VOCTRL_QUERY_FORMAT: return query_format(*((uint32_t*)data)); case VOCTRL_UPDATE_SCREENINFO: vo_screenwidth = fb_vinfo.xres; vo_screenheight = fb_vinfo.yres; aspect_save_screenres(vo_screenwidth, vo_screenheight); return VO_TRUE; } return VO_NOTIMPL; }