view vidix/ivtv_vid.c @ 26457:9ba6d4487292

Use standard GPL header.
author diego
date Tue, 22 Apr 2008 09:32:30 +0000
parents 334d55a31174
children 9feba3abc178
line wrap: on
line source

/**
    VIDIX driver for Hauppauge PVR 350.

    Copyright 2007 Lutz Koschorreck.

    Based on genfb_vid.c and ivtv_xv.c

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    09.05.2007 Lutz Koschorreck
    First version: Tested with ivtv-0.10.1, xine-ui-0.99.5, xine-lib-1.1.6
    20.05.2007 Lutz Koschorreck
    Some Scaling and zooming problems fixed. By default the vidix driver now 
    controlls the setting of alphablending. So there is no need to use
    ivtvfbctl anymore. To disable this feature set the following environment
    variable:VIDIXIVTVALPHA=disable. Special thanx to Ian Armstrong.
    23.07.2007 Lutz Koschorreck
    Support for 2.6.22 kernel added. PCI scan added.
    07.10.2007 Lutz Koschorreck
    Restore old alpha value correctly. Fix capability struct values. 
**/

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <inttypes.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
#include <linux/videodev2.h>
#endif
#include <linux/ivtv.h>
#include <linux/fb.h>

#include "vidix.h"
#include "vidixlib.h"
#include "fourcc.h"
#include "dha.h"
#include "pci_ids.h"
#include "pci_names.h"

#define VIDIX_STATIC ivtv_

#define IVTV_MSG "[ivtv-vid] "
#define MAXLINE 128
#define IVTVMAXWIDTH 720
#define IVTVMAXHEIGHT 576

static int fbdev = -1;
static int yuvdev = -1;
static void *memBase = NULL;
static int frameSize = 0;
static int probed = 0;
static int ivtv_verbose;
static vidix_rect_t destVideo;
static vidix_rect_t srcVideo;
static unsigned char *outbuf = NULL;
double fb_width;
double fb_height;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
static struct ivtvfb_ioctl_state_info fb_state_old;
static struct ivtvfb_ioctl_state_info fb_state_hide;
#else
struct v4l2_format format_old, format_hide;
#endif
int alpha_disable = 0;

/* VIDIX exports */

static vidix_capability_t ivtv_cap =
{
	"Hauppauge PVR 350 YUV Video",
	"Lutz Koschorreck",
	TYPE_OUTPUT,
	{ 0, 0, 0, 0 },
	IVTVMAXWIDTH,
	IVTVMAXHEIGHT,
	4,
	4,
	-1,
	FLAG_UPSCALER|FLAG_DOWNSCALER,
	-1,
	-1,
	{ 0, 0, 0, 0 }
};

static void de_macro_y(unsigned char *src, unsigned char *dst,
	unsigned int w, unsigned int h, int src_x, int src_y, int height __attribute__ ((unused)), int width)
{
	unsigned int x, y, i;
	unsigned char *dst_2;
	unsigned int h_tail, w_tail;
	unsigned int h_size, w_size;

	// Always round the origin, but compensate by increasing the size
	if (src_x & 15) {
		w += src_x & 15;
		src_x &= ~15;
	}

	if (src_y & 15) {
		h += src_y & 15;
		src_y &= ~15;
	}

	// The right / bottom edge might not be a multiple of 16
	h_tail = h & 15;
	w_tail = w & 15;

	// One block is 16 pixels high
	h_size = 16;

	// descramble Y plane
	for (y = 0; y < h; y += 16) {

		// Clip if we've reached the bottom & the size isn't a multiple of 16
		if (y + 16 > h) h_size = h_tail;

		for (x = 0; x < w; x += 16) {
			if (x + 16 > w)
				w_size = w_tail;
			else
				w_size = 16;

			dst_2 = dst + (720 * y) + (720 * src_y) + (256 * (src_x>>4)) + (x * 16);

			for (i = 0; i < h_size; i++) {
				memcpy(dst_2, src + src_x + x + (y + i) * width + (src_y * width), w_size);
				dst_2 += 16;
			}
		}
	}
}

static void de_macro_uv(unsigned char *srcu, unsigned char *srcv,
	unsigned char *dst, unsigned int w, unsigned int h, int src_x, int src_y,
	int height, int width)
{
	unsigned int x, y, i, f;
	unsigned char *dst_2;
	unsigned int h_tail, w_tail;
	unsigned int h_size;
	
	// The uv plane is half the size of the y plane, so 'correct' all dimensions.
	w /= 2;
	h /= 2;
	src_x /= 2;
	src_y /= 2;
	height /= 2;
	width /= 2;

	// Always round the origin, but compensate by increasing the size
	if (src_x & 7) {
		w += src_x & 7;
		src_x &= ~7;
	}

	if (src_y & 15) {
		h += src_y & 15;
		src_y &= ~15;
	}

	// The right / bottom edge may not be a multiple of 16
	h_tail = h & 15;
	w_tail = w & 7;

	h_size = 16;

	// descramble U/V plane
	for (y = 0; y < h; y += 16) {
		if ( y + 16 > h ) h_size = h_tail;
		for (x = 0; x < w; x += 8) {
			dst_2 = dst + (720 * y) + (720 * src_y) + (256 * (src_x>>3)) + (x * 32);
			if (x + 8 <= w) {
				for (i = 0; i < h_size; i++) {
					int idx = src_x + x + ((y + i) * width) + (src_y * width);
					dst_2[0] = srcu[idx + 0];
					dst_2[1] = srcv[idx + 0];
					dst_2[2] = srcu[idx + 1];
					dst_2[3] = srcv[idx + 1];
					dst_2[4] = srcu[idx + 2];
					dst_2[5] = srcv[idx + 2];
					dst_2[6] = srcu[idx + 3];
					dst_2[7] = srcv[idx + 3];
					dst_2[8] = srcu[idx + 4];
					dst_2[9] = srcv[idx + 4];
					dst_2[10] = srcu[idx + 5];
					dst_2[11] = srcv[idx + 5];
					dst_2[12] = srcu[idx + 6];
					dst_2[13] = srcv[idx + 6];
					dst_2[14] = srcu[idx + 7];
					dst_2[15] = srcv[idx + 7];
					dst_2 += 16;
				}
			}
			else {
				for (i = 0; i < h_size; i ++) {
					int idx = src_x + x + ((y + i) * width) + (src_y * width);
					for (f = 0; f < w_tail; f++) {
						dst_2[0] = srcu[idx + f];
						dst_2[1] = srcv[idx + f];
						dst_2 += 2;
					}
/*
					// Used for testing edge cutoff. Sets colour to Green
					for (f = w_tail;f < 8;f ++) {
						dst_2[0] = 0;
						dst_2[1] = 0;
						dst_2 += 2;
					}
*/
					dst_2 += 16 - (w_tail << 1);
				}
			}
		}
	}
}

int ivtv_probe(int verbose,int force __attribute__ ((unused)))
{
	unsigned char fb_number = 0;
	char *device_name = NULL;
	char *alpha = NULL;
	struct fb_var_screeninfo vinfo;
	char fb_dev_name[] = "/dev/fb0\0";
	pciinfo_t lst[MAX_PCI_DEVICES];
	int err = 0;
	unsigned int i, num_pci = 0;

	if(verbose)
		printf(IVTV_MSG"probe\n");

	ivtv_verbose = verbose;

	err = pci_scan(lst, &num_pci);
	if(err)	{
		printf(IVTV_MSG"Error occured during pci scan: %s\n", strerror(err));
		return err;
	}

	if(ivtv_verbose)
		printf(IVTV_MSG"Found %d pci devices\n", num_pci);

	for(i = 0; i < num_pci; i++) {
		if(2 == ivtv_verbose)
			printf(IVTV_MSG"Found chip [%04X:%04X] '%s' '%s'\n"
				,lst[i].vendor
				,lst[i].device
				,pci_vendor_name(lst[i].vendor)
				,pci_device_name(lst[i].vendor,lst[i].device));
		if(VENDOR_INTERNEXT == lst[i].vendor) {
			switch(lst[i].device)
			{
			case DEVICE_INTERNEXT_ITVC15_MPEG_2_ENCODER:
				if(ivtv_verbose)
					printf(IVTV_MSG"Found PVR 350\n");
				goto card_found;
			}
		}
	}
	
	if(ivtv_verbose)
		printf(IVTV_MSG"Can't find chip\n");
	return(ENXIO);

card_found:

	device_name = getenv("FRAMEBUFFER");
	if(NULL == device_name) {
		device_name = fb_dev_name;
	}

	fb_number = atoi(device_name+strlen("/dev/fb"));

	fbdev = open(device_name, O_RDWR);
	if(-1 != fbdev) {
		if(ioctl(fbdev, FBIOGET_VSCREENINFO, &vinfo) < 0) {
			printf(IVTV_MSG"Unable to read screen info\n");
			close(fbdev);
			return(ENXIO);
		} else {
			fb_width = vinfo.xres;
			fb_height = vinfo.yres;
			if(2 == ivtv_verbose) {
				printf(IVTV_MSG"framebuffer width : %3.0f\n",fb_width);
				printf(IVTV_MSG"framebuffer height: %3.0f\n",fb_height);
			}
		}
		if(NULL != (alpha = getenv("VIDIXIVTVALPHA"))) {
			if(0 == strcmp(alpha, "disable")) {
				alpha_disable = 1;
			}
		}
	} else {
		printf(IVTV_MSG"Failed to open /dev/fb%u\n", fb_number);
		return(ENXIO);
	}

	/* Try to find YUV device */
	unsigned char yuv_device_number = 48, yuv_device = 48 + fb_number;
	char yuv_device_name[] = "/dev/videoXXX\0";
	
	do {
		sprintf(yuv_device_name, "/dev/video%u", yuv_device);
		yuvdev = open(yuv_device_name, O_RDWR);
		if(-1 != yuvdev) {
			if(ivtv_verbose)
				printf(IVTV_MSG"YUV device found /dev/video%u\n", yuv_device);
			goto yuv_found;
		} else {
			if(ivtv_verbose)
				printf(IVTV_MSG"YUV device not found: /dev/video%u\n", yuv_device);
		}
	} while(yuv_device-- > yuv_device_number);
	return(ENXIO);

yuv_found:
	if(0 == alpha_disable) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
		if(ioctl(fbdev, IVTVFB_IOCTL_GET_STATE, &fb_state_old) < 0) {
			printf(IVTV_MSG"Unable to read fb state\n");
			close(yuvdev);
			close(fbdev);
			return(ENXIO);
		} else {
			if(ivtv_verbose) {
				printf(IVTV_MSG"old alpha : %ld\n",fb_state_old.alpha);
				printf(IVTV_MSG"old status: 0x%lx\n",fb_state_old.status);
			}
			fb_state_hide.alpha = 0;
			fb_state_hide.status = fb_state_old.status | IVTVFB_STATUS_GLOBAL_ALPHA;
		}
#else
		memset(&format_old, 0, sizeof(format_old));
		format_old.type = format_hide.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;
		if(ioctl(yuvdev, VIDIOC_G_FMT , &format_old) < 0) {
			printf(IVTV_MSG"Unable to read fb state\n");
			close(yuvdev);
			close(fbdev);
			return(ENXIO);
		} else {
			if(ivtv_verbose) {
				printf(IVTV_MSG"old alpha : %d\n",format_old.fmt.win.global_alpha);
			}
			memcpy(&format_hide, &format_old, sizeof(format_old));
			format_hide.fmt.win.global_alpha = 0;
		}
#endif
	}
	probed = 1;
	return(0);
}

int ivtv_init(const char *args __attribute__ ((unused)))
{
	if(ivtv_verbose)
		printf(IVTV_MSG"init\n");

	if (!probed) {
		if(ivtv_verbose)
			printf(IVTV_MSG"Driver was not probed but is being initialized\n");
		return(EINTR);
	}
	outbuf = malloc((IVTVMAXHEIGHT * IVTVMAXWIDTH) + (IVTVMAXHEIGHT * IVTVMAXWIDTH / 2));
	if(NULL == outbuf) {
		if(ivtv_verbose)
			printf(IVTV_MSG"Not enough memory availabe!\n");
		return(EINTR);
	}
	return(0);
}

void ivtv_destroy(void)
{
	if(ivtv_verbose)
		printf(IVTV_MSG"destroy\n");
	if(-1 != yuvdev)
		close(yuvdev);	
	if(-1 != fbdev)
		close(fbdev);
	if(NULL != outbuf)
		free(outbuf);
	if(NULL != memBase)
		free(memBase);
}

int ivtv_get_caps(vidix_capability_t *to)
{
	if(ivtv_verbose)
		printf(IVTV_MSG"GetCap\n");
	memcpy(to, &ivtv_cap, sizeof(vidix_capability_t));
	return(0);
}

int ivtv_query_fourcc(vidix_fourcc_t *to)
{
	if(ivtv_verbose)
		printf(IVTV_MSG"query fourcc (%x)\n", to->fourcc);
	
	int supports = 0;
	switch(to->fourcc)
	{
	case IMGFMT_YV12:
		supports = 1;
		break;
	default:
		supports = 0;
	}
	
	if(!supports) {
		to->depth = to->flags = 0;
		return(ENOTSUP);
	}
	to->depth = VID_DEPTH_12BPP |
		VID_DEPTH_15BPP | VID_DEPTH_16BPP |
		VID_DEPTH_24BPP | VID_DEPTH_32BPP;
	to->flags = 0;
	return(0);
}

int ivtv_config_playback(vidix_playback_t *info)
{
	if(ivtv_verbose)
		printf(IVTV_MSG"config playback\n");

	if(2 == ivtv_verbose){
		printf(IVTV_MSG"src : x:%d y:%d w:%d h:%d\n", 
			info->src.x, info->src.y, info->src.w, info->src.h);
		printf(IVTV_MSG"dest: x:%d y:%d w:%d h:%d\n", 
			info->dest.x, info->dest.y, info->dest.w, info->dest.h);
	}
	
	memcpy(&destVideo, &info->dest, sizeof(vidix_rect_t));
	memcpy(&srcVideo, &info->src, sizeof(vidix_rect_t));

	info->num_frames = 2;
	info->frame_size = frameSize = info->src.w*info->src.h+(info->src.w*info->src.h)/2;
	info->dest.pitch.y = 1;
	info->dest.pitch.u = info->dest.pitch.v = 2;
	info->offsets[0] = 0;
	info->offsets[1] = info->frame_size;
	info->offset.y = 0;
	info->offset.u = info->src.w * info->src.h;
	info->offset.v = info->offset.u + ((info->src.w * info->src.h)/4);
	info->dga_addr = memBase = malloc(info->num_frames*info->frame_size);   
	if(ivtv_verbose)
		printf(IVTV_MSG"frame_size: %d, dga_addr: %p\n",
	info->frame_size, info->dga_addr);
	return(0);
}

int ivtv_playback_on(void)
{
	if(ivtv_verbose)
		printf(IVTV_MSG"playback on\n");

	if(0 == alpha_disable) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
		if (-1 != fbdev) {
			if (ioctl(fbdev, IVTVFB_IOCTL_SET_STATE, &fb_state_hide) < 0)
				printf (IVTV_MSG"Failed to set fb state\n");
		}
#else
		if (-1 != yuvdev) {
			if (ioctl(yuvdev, VIDIOC_S_FMT, &format_hide) < 0)
				printf (IVTV_MSG"Failed to set fb state\n");
		}
#endif
	}
	return(0);
}

int ivtv_playback_off(void)
{
	if(ivtv_verbose) 
		printf(IVTV_MSG"playback off\n");
	
	if(0 == alpha_disable) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
		if (-1 != fbdev) {
			if (ioctl(fbdev, IVTVFB_IOCTL_SET_STATE, &fb_state_old) < 0)
				printf (IVTV_MSG"Failed to restore fb state\n");
		}
#else
		if (-1 != yuvdev) {
			if (ioctl(yuvdev, VIDIOC_S_FMT, &format_old) < 0)
				printf (IVTV_MSG"Failed to restore fb state\n");
		}
#endif
	}
	return(0);
}

int ivtv_frame_sel(unsigned int frame)
{

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
	struct ivtvyuv_ioctl_dma_host_to_ivtv_args args;
#else
	struct ivtv_dma_frame args;
#endif
	unsigned char *curMemBase = (unsigned char *)memBase + (frame * frameSize);

	de_macro_y(curMemBase, outbuf, srcVideo.w, srcVideo.h, srcVideo.x, srcVideo.y, srcVideo.h, srcVideo.w);
	de_macro_uv(curMemBase + (srcVideo.w * srcVideo.h) + srcVideo.w * srcVideo.h / 4,
		curMemBase + (srcVideo.w * srcVideo.h), outbuf + (IVTVMAXHEIGHT * IVTVMAXWIDTH),
		srcVideo.w,  srcVideo.h, srcVideo.x, srcVideo.y, srcVideo.h, srcVideo.w);

	args.y_source = outbuf;
	args.uv_source = outbuf + (IVTVMAXHEIGHT * IVTVMAXWIDTH);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
	args.src_x = srcVideo.x;
	args.src_y = srcVideo.y;
	args.dst_x = destVideo.x;
	args.dst_y = destVideo.y;
	args.src_w = srcVideo.w;
	args.dst_w = destVideo.w;
	args.srcBuf_width = srcVideo.w;
	args.src_h = srcVideo.h;
	args.dst_h = destVideo.h;
	args.srcBuf_height = srcVideo.h;
	args.yuv_type = 0;
#else
	args.src.left = srcVideo.x;
	args.src.top = srcVideo.y;
	args.dst.left = destVideo.x;
	args.dst.top = destVideo.y;
	args.src.width = srcVideo.w;
	args.dst.width = destVideo.w;
	args.src_width = srcVideo.w;
	args.src.height = srcVideo.h;
	args.dst.height = destVideo.h;
	args.src_height = srcVideo.h;
	args.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
	if(ioctl(yuvdev, IVTV_IOC_PREP_FRAME_YUV, &args) == -1) {
#else
	if(ioctl(yuvdev, IVTV_IOC_DMA_FRAME, &args) == -1) {
#endif
		printf("Ioctl IVTV_IOC_DMA_FRAME returned failed Error\n");
	}
	return(0);
}

VDXDriver ivtv_drv = {
	"ivtv",
	NULL,
	.probe = ivtv_probe,
	.get_caps = ivtv_get_caps,
	.query_fourcc = ivtv_query_fourcc,
	.init = ivtv_init,
	.destroy = ivtv_destroy,
	.config_playback = ivtv_config_playback,
	.playback_on = ivtv_playback_on,
	.playback_off = ivtv_playback_off,
	.frame_sel = ivtv_frame_sel,
};