view libvo/vo_fbdev.c @ 391:15fb291915fa

- changed -bpp again: if vo driver obeys depth value set by user, then the codec should do the same ...
author acki2
date Fri, 13 Apr 2001 17:50:13 +0000
parents 086c66d570d8
children e847396dc49f
line wrap: on
line source

/*
 * Video driver for Framebuffer device
 * by Szabolcs Berecz <szabi@inf.elte.hu>
 * (C) 2001
 * 
 * Some idea and code borrowed from Chris Lawrence's ppmtofb-0.27
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>

#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <linux/vt.h>

#include "config.h"
#include "video_out.h"
#include "video_out_internal.h"

#include "yuv2rgb.h"
extern void rgb15to16_mmx(char *s0, char *d0, int count);

#include "fastmemcpy.h"

LIBVO_EXTERN(fbdev)

static vo_info_t vo_info = {
	"Framebuffer Device",
	"fbdev",
	"Szabolcs Berecz <szabi@inf.elte.hu>",
	""
};

extern int verbose;

/******************************
*	fb.modes parser       *
******************************/

/*
 * read the fb.modes manual page!
 */

typedef struct {
	char *name;
	uint32_t xres, yres, vxres, vyres, depth;
	uint32_t pixclock, left, right, upper, lower, hslen, vslen;
	uint32_t sync;
	uint32_t vmode;
} fb_mode_t;

#define PRINT_LINENUM printf(" at line %d\n", line_num)

#define MAX_NR_TOKEN	16

#define MAX_LINE_LEN	1000

#define RET_EOF		-1
#define RET_EOL		-2

static int validate_mode(fb_mode_t *m)
{
	if (!m->xres) {
		printf("needs geometry ");
		return 0;
	}
	if (!m->pixclock) {
		printf("needs timings ");
		return 0;
	}
	return 1;
}

static FILE *fp;
static int line_num = 0;
static char *line;
static char *token[MAX_NR_TOKEN];

static int get_token(int num)
{
	static int read_nextline = 1;
	static int line_pos;
	int i;
	char c;

	if (num >= MAX_NR_TOKEN) {
		printf("get_token(): max >= MAX_NR_TOKEN!");
		goto out_eof;
	}

	if (read_nextline) {
		if (!fgets(line, MAX_LINE_LEN, fp))
			goto out_eof;
		line_pos = 0;
		++line_num;
		read_nextline = 0;
	}
	for (i = 0; i < num; i++) {
		while (isspace(line[line_pos]))
			++line_pos;
		if (line[line_pos] == '\0' || line[line_pos] == '#') {
			read_nextline = 1;
			if (i == num)
				goto out_ok;
			goto out_eol;
		}
		token[i] = line + line_pos;
		c = line[line_pos];
		if (c == '"' || c == '\'') {
			token[i]++;
			while (line[++line_pos] != c && line[line_pos])
				/* NOTHING */;
		} else {
			for (/* NOTHING */; !isspace(line[line_pos]) &&
					line[line_pos]; line_pos++)
				/* NOTHING */;
		}
		if (!line[line_pos]) {
			read_nextline = 1;
			if (i == num - 1)
				goto out_ok;
			goto out_eol;
		}
		line[line_pos] = '\0';
		line_pos++;
	}
out_ok:
	return i;
out_eof:
	return RET_EOF;
out_eol:
	return RET_EOL;
}

static fb_mode_t *fb_modes = NULL;
static int nr_modes = 0;

static int parse_fbmode_cfg(char *cfgfile)
{
	fb_mode_t *mode = NULL;
	char *endptr;	// strtoul()...
	int tmp, i;

#ifdef DEBUG
	assert(cfgfile != NULL);
#endif

	printf("Reading %s: ", cfgfile);

	if ((fp = fopen(cfgfile, "r")) == NULL) {
		printf("can't open '%s': %s\n", cfgfile, strerror(errno));
		return -1;
	}

	if ((line = (char *) malloc(MAX_LINE_LEN + 1)) == NULL) {
		printf("can't get memory for 'line': %s\n", strerror(errno));
		return -2;
	}

	/*
	 * check if the cfgfile starts with 'mode'
	 */
	while ((tmp = get_token(1)) == RET_EOL)
		/* NOTHING */;
	if (tmp == RET_EOF)
		goto out;
	if (!strcmp(token[0], "mode"))
		goto loop_enter;
	goto err_out_parse_error;

	while ((tmp = get_token(1)) != RET_EOF) {
		if (tmp == RET_EOL)
			continue;
		if (!strcmp(token[0], "mode")) {
			if (!validate_mode(mode))
				goto err_out_not_valid;
		loop_enter:
		        if (!(fb_modes = (fb_mode_t *) realloc(fb_modes,
				sizeof(fb_mode_t) * (nr_modes + 1)))) {
			    printf("can't realloc 'fb_modes': %s\n", strerror(errno));
			    goto err_out;
		        }
			mode=fb_modes + nr_modes;
			++nr_modes;
                        memset(mode,0,sizeof(fb_mode_t));

			if (get_token(1) < 0)
				goto err_out_parse_error;
			for (i = 0; i < nr_modes - 1; i++) {
				if (!strcmp(token[0], fb_modes[i].name)) {
					printf("mode name '%s' isn't unique", token[0]);
					goto err_out_print_linenum;
				}
			}
			if (!(mode->name = strdup(token[0]))) {
				printf("can't strdup -> 'name': %s\n", strerror(errno));
				goto err_out;
			}
		} else if (!strcmp(token[0], "geometry")) {
			if (get_token(5) < 0)
				goto err_out_parse_error;
			mode->xres = strtoul(token[0], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->yres = strtoul(token[1], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->vxres = strtoul(token[2], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->vyres = strtoul(token[3], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->depth = strtoul(token[4], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
		} else if (!strcmp(token[0], "timings")) {
			if (get_token(7) < 0)
				goto err_out_parse_error;
			mode->pixclock = strtoul(token[0], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->left = strtoul(token[1], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->right = strtoul(token[2], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->upper = strtoul(token[3], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->lower = strtoul(token[4], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->hslen = strtoul(token[5], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
			mode->vslen = strtoul(token[6], &endptr, 0);
			if (*endptr)
				goto err_out_parse_error;
		} else if (!strcmp(token[0], "endmode")) {
			/* NOTHING for now*/
		} else if (!strcmp(token[0], "accel")) {
			if (get_token(1) < 0)
				goto err_out_parse_error;
			/* NOTHING for now*/
		} else if (!strcmp(token[0], "hsync")) {
			if (get_token(1) < 0)
				goto err_out_parse_error;
			if (!strcmp(token[0], "low"))
				mode->sync &= ~FB_SYNC_HOR_HIGH_ACT;
			else if(!strcmp(token[0], "high"))
				mode->sync |= FB_SYNC_HOR_HIGH_ACT;
			else
				goto err_out_parse_error;
		} else if (!strcmp(token[0], "vsync")) {
			if (get_token(1) < 0)
				goto err_out_parse_error;
			if (!strcmp(token[0], "low"))
				mode->sync &= ~FB_SYNC_VERT_HIGH_ACT;
			else if(!strcmp(token[0], "high"))
				mode->sync |= FB_SYNC_VERT_HIGH_ACT;
			else
				goto err_out_parse_error;
		} else if (!strcmp(token[0], "csync")) {
			if (get_token(1) < 0)
				goto err_out_parse_error;
			if (!strcmp(token[0], "low"))
				mode->sync &= ~FB_SYNC_COMP_HIGH_ACT;
			else if(!strcmp(token[0], "high"))
				mode->sync |= FB_SYNC_COMP_HIGH_ACT;
			else
				goto err_out_parse_error;
		} else if (!strcmp(token[0], "extsync")) {
			if (get_token(1) < 0)
				goto err_out_parse_error;
			if (!strcmp(token[0], "false"))
				mode->sync &= ~FB_SYNC_EXT;
			else if(!strcmp(token[0], "true"))
				mode->sync |= FB_SYNC_EXT;
			else
				goto err_out_parse_error;
		} else if (!strcmp(token[0], "laced")) {
			if (get_token(1) < 0)
				goto err_out_parse_error;
			if (!strcmp(token[0], "false"))
				mode->vmode = FB_VMODE_NONINTERLACED;
			else if (!strcmp(token[0], "true"))
				mode->vmode = FB_VMODE_INTERLACED;
			else
				goto err_out_parse_error;
		} else if (!strcmp(token[0], "dblscan")) {
			if (get_token(1) < 0)
				goto err_out_parse_error;
			if (!strcmp(token[0], "false"))
				;
			else if (!strcmp(token[0], "true"))
				mode->vmode = FB_VMODE_DOUBLE;
			else
				goto err_out_parse_error;
		} else
			goto err_out_parse_error;
	}
	if (!validate_mode(mode))
		goto err_out_not_valid;
out:
	printf("%d modes\n", nr_modes);
	free(line);
	fclose(fp);
	return nr_modes;
err_out_parse_error:
	printf("parse error");
err_out_print_linenum:
	PRINT_LINENUM;
err_out:
	if (fb_modes)
		free(fb_modes);
	free(line);
	free(fp);
	return -2;
err_out_not_valid:
	printf("mode is not definied correctly");
	goto err_out_print_linenum;
}

static fb_mode_t *find_mode_by_name(char *name)
{
	int i;

	for (i = 0; i < nr_modes; i++) {
		if (!strcmp(name, fb_modes[i].name))
			return fb_modes + i;
	}
	return NULL;
}

/******************************
*	    vo_fbdev	      *
******************************/

static int fb_init_done = 0;
static int fb_works = 0;
static int fb_dev_fd;
static size_t fb_size;
static uint8_t *frame_buffer;
static struct fb_fix_screeninfo fb_finfo;
static struct fb_var_screeninfo fb_orig_vinfo;
static struct fb_var_screeninfo fb_vinfo;
static struct fb_cmap *fb_oldcmap = NULL;
static int fb_pixel_size;	// 32:  4  24:  3  16:  2  15:  2
static int fb_real_bpp;		// 32: 24  24: 24  16: 16  15: 15
static int fb_bpp;		// 32: 32  24: 24  16: 16  15: 15
static int fb_screen_width;

char *fb_dev_name = NULL;
char *fb_mode_cfgfile = "/etc/fb.modes";
char *fb_mode_name = NULL;
int fb_mode_depth = 0;
static fb_mode_t *fb_mode = NULL;
static int fb_switch_mode = 0;

static uint8_t *next_frame;
static int in_width;
static int in_height;
static int out_width;
static int out_height;
static uint32_t pixel_format;

/*
 * Note: this function is completely cut'n'pasted from
 * Chris Lawrence's code.
 * (modified a bit to fit in my code...)
 */
struct fb_cmap *make_directcolor_cmap(struct fb_var_screeninfo *var)
{
  /* Hopefully any DIRECTCOLOR device will have a big enough palette
   * to handle mapping the full color depth.
   * e.g. 8 bpp -> 256 entry palette
   *
   * We could handle some sort of gamma here
   */
  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 = (rcols > gcols ? rcols : gcols);
  cols = (cols > bcols ? cols : bcols);
  
  red = malloc(cols * sizeof(red[0]));
  if(!red) {
	  printf("Can't allocate red palette with %d entries.\n", cols);
	  return NULL;
  }
  for(i=0; i< rcols; i++)
    red[i] = (65535/(rcols-1)) * i;
  
  green = malloc(cols * sizeof(green[0]));
  if(!green) {
	  printf("Can't allocate green palette with %d entries.\n", cols);
	  free(red);
	  return NULL;
  }
  for(i=0; i< gcols; i++)
    green[i] = (65535/(gcols-1)) * i;
  
  blue = malloc(cols * sizeof(blue[0]));
  if(!blue) {
	  printf("Can't allocate blue palette with %d entries.\n", cols);
	  free(red);
	  free(green);
	  return NULL;
  }
  for(i=0; i< bcols; i++)
    blue[i] = (65535/(bcols-1)) * i;
  
  cmap = malloc(sizeof(struct fb_cmap));
  if(!cmap) {
	  printf("Can't allocate color map\n");
	  free(red);
	  free(green);
	  free(blue);
	  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 void set_rgb_fields(struct fb_var_screeninfo *p, int depth)
{
	switch (depth) {
		case 32:
		case 24:
			p->red.offset = 16;
			p->red.length = 8;
			p->red.msb_right = 0;
			p->green.offset = 8;
			p->green.length = 8;
			p->green.msb_right = 0;
			p->blue.offset = 0;
			p->blue.length = 8;
			p->blue.msb_right = 0;
			break;
		case 16:
			p->red.offset = 11;
			p->red.length = 5;
			p->red.msb_right = 0;
			p->green.offset = 5;
			p->green.length = 6;
			p->green.msb_right = 0;
			p->blue.offset = 0;
			p->blue.length = 5;
			p->blue.msb_right = 0;
			break;
		case 15:
			p->red.offset = 10;
			p->red.length = 5;
			p->red.msb_right = 0;
			p->green.offset = 5;
			p->green.length = 5;
			p->green.msb_right = 0;
			p->blue.offset = 0;
			p->blue.length = 5;
			p->blue.msb_right = 0;
			break;
	}
}

static int fb_init(void)
{
	int fd;
	struct fb_cmap *cmap;

	if (fb_mode_name) {
		if (parse_fbmode_cfg(fb_mode_cfgfile) < 0)
			goto err_out;
		if (!(fb_mode = find_mode_by_name(fb_mode_name))) {
			printf("fb: can't find requested video mode\n");
			goto err_out;
		}
		fb_switch_mode = 1;
	} else if (fb_mode_depth) {
		printf("fb: Do _not_ use the 'fbdepth' parameter! "
				"this parameter will be removed\n");
		if (fb_mode_depth != 15 && fb_mode_depth != 16 &&
				fb_mode_depth != 24 && fb_mode_depth != 32) {
			printf("fb: can't switch to %d bpp\n", fb_mode_depth);
			goto err_out;
		}
	}

	if (!fb_dev_name && !(fb_dev_name = getenv("FRAMEBUFFER")))
		fb_dev_name = "/dev/fb0";
	if (verbose > 0)
		printf("fb: using %s\n", fb_dev_name);

	if ((fb_dev_fd = open(fb_dev_name, O_RDWR)) == -1) {
		printf("fb: Can't open %s: %s\n", fb_dev_name, strerror(errno));
		goto err_out;
	}

	if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) {
		printf("fb: Can't get VSCREENINFO: %s\n", strerror(errno));
		goto err_out_fd;
	}

	fb_orig_vinfo = fb_vinfo;
	if (fb_switch_mode) {
		fb_vinfo.xres = fb_mode->xres;
		fb_vinfo.yres = fb_mode->yres;
		fb_vinfo.xres_virtual = fb_mode->vxres;
		fb_vinfo.yres_virtual = fb_mode->vyres;
		fb_vinfo.bits_per_pixel = fb_mode->depth;
		set_rgb_fields(&fb_vinfo, fb_mode->depth);
		fb_vinfo.pixclock = fb_mode->pixclock;
		fb_vinfo.left_margin = fb_mode->left;
		fb_vinfo.right_margin = fb_mode->right;
		fb_vinfo.upper_margin = fb_mode->upper;
		fb_vinfo.lower_margin = fb_mode->lower;
		fb_vinfo.hsync_len = fb_mode->hslen;
		fb_vinfo.vsync_len = fb_mode->vslen;
		fb_vinfo.sync = fb_mode->sync;
		fb_vinfo.vmode = fb_mode->vmode;
	} else if (fb_mode_depth) {
		fb_vinfo.bits_per_pixel = fb_mode_depth;
		set_rgb_fields(&fb_vinfo, fb_mode_depth);
	}
	fb_vinfo.xres_virtual = fb_vinfo.xres;
	fb_vinfo.yres_virtual = fb_vinfo.yres;

	if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_vinfo)) {
		printf("fb: Can't put VSCREENINFO: %s\n", strerror(errno));
		goto err_out_fd;
	}

	if (ioctl(fb_dev_fd, FBIOGET_FSCREENINFO, &fb_finfo)) {
		printf("fb: Can't get VSCREENINFO: %s\n", strerror(errno));
		goto err_out_fd;
		return 1;
	}
	switch (fb_finfo.type) {
		case FB_TYPE_VGA_PLANES:
			printf("fb: FB_TYPE_VGA_PLANES not supported.\n");
			goto err_out_fd;
			break;
		case FB_TYPE_PLANES:
			printf("fb: FB_TYPE_PLANES not supported.\n");
			goto err_out_fd;
			break;
		case FB_TYPE_INTERLEAVED_PLANES:
			printf("fb: FB_TYPE_INTERLEAVED_PLANES not supported.\n");
			goto err_out_fd;
			break;
#ifdef FB_TYPE_TEXT
		case FB_TYPE_TEXT:
			printf("fb: FB_TYPE_TEXT not supported.\n");
			goto err_out_fd;
			break;
#endif
		case FB_TYPE_PACKED_PIXELS:
			/* OK */
			if (verbose > 0)
				printf("fb: FB_TYPE_PACKED_PIXELS: OK\n");
			break;
		default:
			printf("fb: unknown FB_TYPE: %d\n", fb_finfo.type);
			goto err_out_fd;
	}
	switch (fb_finfo.visual) {
	case FB_VISUAL_TRUECOLOR:
		break;
	case FB_VISUAL_DIRECTCOLOR:
		if (verbose > 0)
			printf("fb: creating cmap for directcolor\n");
		if (ioctl(fb_dev_fd, FBIOGETCMAP, fb_oldcmap)) {
			printf("fb: can't get cmap: %s\n",
					strerror(errno));
			goto err_out_fd;
		}
		if (!(cmap = make_directcolor_cmap(&fb_vinfo)))
			goto err_out_fd;
		if (ioctl(fb_dev_fd, FBIOPUTCMAP, cmap)) {
			printf("fb: can't put cmap: %s\n",
					strerror(errno));
			goto err_out_fd;
		}
		free(cmap->red);
		free(cmap->green);
		free(cmap->blue);
		free(cmap);
		break;
	case FB_VISUAL_PSEUDOCOLOR:
		printf("fb: visual is FB_VISUAL_PSEUDOCOLOR. it's not tested!\n");
		break;
	default:
		printf("fb: visual: %d not yet supported\n",
				fb_finfo.visual);
		goto err_out_fd;
	}

	fb_pixel_size = fb_vinfo.bits_per_pixel / 8;
	fb_real_bpp = fb_vinfo.red.length + fb_vinfo.green.length +
		fb_vinfo.blue.length;
	fb_bpp = (fb_pixel_size == 4) ? 32 : fb_real_bpp;
	fb_screen_width = fb_finfo.line_length;
	fb_size = fb_finfo.smem_len;
	if ((frame_buffer = (uint8_t *) mmap(0, fb_size, PROT_READ | PROT_WRITE,
				MAP_SHARED, fb_dev_fd, 0)) == (uint8_t *) -1) {
		printf("fb: Can't mmap %s: %s\n", fb_dev_name, strerror(errno));
		goto err_out_fd;
	}

	if (verbose > 0 && verbose < 4)
		printf("fb: use verbose level >= 4 to get some info (you will get _lots_ of info)\n");
	if (verbose >= 4) {
		printf("fb: fix info:\n");
		printf("fb: id: %.16s\n", fb_finfo.id);
		printf("fb: smem_start: %p\n", fb_finfo.smem_start);
		printf("fb: framebuffer size: %d bytes\n", fb_size);
		printf("fb: type: %lu\n", fb_finfo.type);
		printf("fb: type_aux: %lu\n", fb_finfo.type_aux);
		printf("fb: visual: %lu\n", fb_finfo.visual);
		printf("fb: xpanstep: %u\n", fb_finfo.xpanstep);
		printf("fb: ypanstep: %u\n", fb_finfo.ypanstep);
		printf("fb: ywrapstep: %u\n", fb_finfo.ywrapstep);
		printf("fb: line_length: %lu bytes\n", fb_finfo.line_length);
		printf("fb: mmio_start: %p\n", fb_finfo.mmio_start);
		printf("fb: mmio_len: %ul bytes\n", fb_finfo.mmio_len);
		printf("fb: accel: %ul\n", fb_finfo.accel);
		printf("fb: var info:\n");
		printf("fb: xres: %ul\n", fb_vinfo.xres);
		printf("fb: yres: %ul\n", fb_vinfo.yres);
		printf("fb: xres_virtual: %ul\n", fb_vinfo.xres_virtual);
		printf("fb: yres_virtual: %ul\n", fb_vinfo.yres_virtual);
		printf("fb: xoffset: %ul\n", fb_vinfo.xoffset);
		printf("fb: yoffset: %ul\n", fb_vinfo.yoffset);
		printf("fb: bits_per_pixel: %ul\n", fb_vinfo.bits_per_pixel);
		printf("fb: grayscale: %ul\n", fb_vinfo.grayscale);
		printf("fb: red: %lu %lu %lu\n", fb_vinfo.red.offset,
				fb_vinfo.red.length, fb_vinfo.red.msb_right);
		printf("fb: green: %lu %lu %lu\n", fb_vinfo.green.offset,
				fb_vinfo.green.length, fb_vinfo.green.msb_right);
		printf("fb: blue: %lu %lu %lu\n", fb_vinfo.blue.offset,
				fb_vinfo.blue.length, fb_vinfo.blue.msb_right);
		printf("fb: transp: %lu %lu %lu\n", fb_vinfo.transp.offset,
				fb_vinfo.transp.length, fb_vinfo.transp.msb_right);
		printf("fb: nonstd: %ul\n", fb_vinfo.nonstd);
		printf("fb: activate: %ul\n", fb_vinfo.activate);
		printf("fb: height: %ul\n", fb_vinfo.height);
		printf("fb: width: %ul\n", fb_vinfo.width);
		printf("fb: accel_flags: %ul\n", fb_vinfo.accel_flags);
		printf("fb: timing:\n");
		printf("fb: pixclock: %ul\n", fb_vinfo.pixclock);
		printf("fb: left_margin: %ul\n", fb_vinfo.left_margin);
		printf("fb: right_margin: %ul\n", fb_vinfo.right_margin);
		printf("fb: upper_margin: %ul\n", fb_vinfo.upper_margin);
		printf("fb: lower_margin: %ul\n", fb_vinfo.lower_margin);
		printf("fb: hsync_len: %ul\n", fb_vinfo.hsync_len);
		printf("fb: vsync_len: %ul\n", fb_vinfo.vsync_len);
		printf("fb: sync: %ul\n", fb_vinfo.sync);
		printf("fb: vmode: %ul\n", fb_vinfo.vmode);
		printf("fb: other:\n");
		printf("fb: frame_buffer @ %p\n", frame_buffer);
		printf("fb: fb_bpp: %d\n", fb_bpp);
		printf("fb: fb_real_bpp: %d\n", fb_real_bpp);
		printf("fb: fb_pixel_size: %d bytes\n", fb_pixel_size);
		printf("fb: pixel per line: %d\n", fb_screen_width / fb_pixel_size);
	}

	fb_init_done = 1;
	fb_works = 1;
	return 0;
err_out_fd:
	close(fb_dev_fd);
	fb_dev_fd = -1;
err_out:
	fb_init_done = 1;
	return 1;
}

static uint32_t init(uint32_t width, uint32_t height, uint32_t d_width,
		uint32_t d_height, uint32_t fullscreen, char *title,
		uint32_t format)
{
	if (!fb_init_done)
		if (fb_init())
			return 1;
	if (!fb_works)
		return 1;

	in_width = width;
	in_height = height;
	out_width = width;
	out_height = height;
	pixel_format = format;
	if (!(next_frame = (uint8_t *) malloc(in_width * in_height * fb_pixel_size))) {
		printf("Can't malloc next_frame: %s\n", strerror(errno));
		return 1;
	}

	if (format == IMGFMT_YV12)
		yuv2rgb_init(fb_bpp, MODE_RGB);
	return 0;
}

static uint32_t query_format(uint32_t format)
{
	if (!fb_init_done)
		if (fb_init())
			return 0;
	if (!fb_works)
		return 0;

	if (verbose > 0)
		printf("fb: query_format(%#lx(%.4s))\n", format, &format);
	if ((format & IMGFMT_BGR_MASK) == IMGFMT_BGR) {
		int bpp = format & 0xff;
		if (bpp == fb_bpp)
			return 1;
		else if (bpp == 15 && fb_bpp == 16)
			return 1;
		else if (bpp == 24 && fb_bpp == 32)
			return 1;
	}
	if (format == IMGFMT_YV12)
		return 1;
	return 0;
}

static const vo_info_t *get_info(void)
{
	return &vo_info;
}

static void draw_alpha(int x0, int y0, int w, int h, unsigned char *src,
		unsigned char *srca, int stride)
{
	uint8_t *dst = next_frame + (in_width * y0 + x0) * fb_pixel_size;
	int dstride = in_width * fb_pixel_size;

	switch (fb_real_bpp) {
	case 24:
		vo_draw_alpha_rgb24(w, h, src, srca, stride, dst, dstride);
		break;
	case 32:
		vo_draw_alpha_rgb32(w, h, src, srca, stride, dst, dstride);
		break;
	case 15:
		vo_draw_alpha_rgb15(w, h, src, srca, stride, dst, dstride);
		break;
	case 16:
		vo_draw_alpha_rgb16(w, h, src, srca, stride, dst, dstride);
		break;
	}
}

static uint32_t draw_frame(uint8_t *src[])
{
	if (pixel_format == IMGFMT_YV12) {
		yuv2rgb(next_frame, src[0], src[1], src[2], in_width,
				in_height, in_width * fb_pixel_size,
				in_width, in_width / 2);
	} else {
		int sbpp = ((pixel_format & 0xff) + 7) / 8;
		char *d = next_frame;
		char *s = src[0];
		if (sbpp == fb_pixel_size) {
			if (fb_real_bpp == 16 && pixel_format == (IMGFMT_BGR|15)) {
#ifdef HAVE_MMX
				rgb15to16_mmx(s, d, 2 * in_width * in_height);
#else
				unsigned short *s1 = (unsigned short *) s;
				unsigned short *d1 = (unsigned short *) d;
				unsigned short *e = s1 + in_width * in_height;
				while (s1<e) {
					register x = *(s1++);
					*(d1++) = (x&0x001f)|((x&0x7fe0)<<1);
				}
#endif
			} else
				memcpy(d, s, sbpp * in_width * in_height);
		}
	}
	return 0;
}

static uint32_t draw_slice(uint8_t *src[], int stride[], int w, int h, int x,
		int y)
{
	uint8_t *dest;

	dest = next_frame + (in_width * y + x) * fb_pixel_size;
	yuv2rgb(dest, src[0], src[1], src[2], w, h, in_width * fb_pixel_size,
			stride[0], stride[1]);
	return 0;
}

static void check_events(void)
{
}

static void put_frame(void)
{
	int i, out_offset = 0, in_offset = 0;

	for (i = 0; i < in_height; i++) {
		memcpy(frame_buffer + out_offset, next_frame + in_offset,
				in_width * fb_pixel_size);
		out_offset += fb_screen_width;
		in_offset += in_width * fb_pixel_size;
	}
}

static void flip_page(void)
{
	vo_draw_text(in_width, in_height, draw_alpha);
	check_events();
	put_frame();
}

static void uninit(void)
{
	if (verbose > 0)
		printf("fbdev: uninit\n");
	if (fb_oldcmap) {
		if (ioctl(fb_dev_fd, FBIOPUTCMAP, fb_oldcmap))
			printf("fbdev: Can't restore original cmap\n");
		fb_oldcmap = NULL;
	}
	if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_orig_vinfo))
		printf("fbdev: Can't set virtual screensize to original value: %s\n", strerror(errno));
	close(fb_dev_fd);
	memset(next_frame, '\0', in_height * in_width * fb_pixel_size);
	put_frame();
	free(next_frame);
	munmap(frame_buffer, fb_size);
}