/*
 * pvr350device.c: 
 *
 * See the README file for copyright information and how to reach the author.
 *
 */
#include <unistd.h>
#include <features.h>		/* Uses _GNU_SOURCE to define getsubopt in stdlib.h */
#include <inttypes.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <sys/utsname.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/user.h>
#include <sys/poll.h>
#include <linux/kernel.h>
#include <linux/fb.h>

#include "pvr350device.h"
#include <vdr/dvbspu.h>

#include <linux/videodev.h>
#include <linux/videodev2.h>
#include <linux/version.h>

#include "pvr350ivtv.h"
#include "pvr350tools.h"
#include "pvr350audio.h"
#include "pvr350setup.h"
typedef unsigned long long u64;

/***************************************************************/
/* An extract from linux/video.h which is now required for the */
/* decoder. Included here since we need an up to date version. */

/* Decoder commands */
#define VIDEO_CMD_PLAY        (0)
#define VIDEO_CMD_STOP        (1)
#define VIDEO_CMD_FREEZE      (2)
#define VIDEO_CMD_CONTINUE    (3)

/* Flags for VIDEO_CMD_FREEZE */
#define VIDEO_CMD_FREEZE_TO_BLACK     	(1 << 0)

/* Flags for VIDEO_CMD_STOP */
#define VIDEO_CMD_STOP_TO_BLACK      	(1 << 0)
#define VIDEO_CMD_STOP_IMMEDIATELY     	(1 << 1)

#define VIDEO_GET_PTS              _IOR('o', 57, __u64)
#define VIDEO_GET_FRAME_COUNT _IOR('o', 58, __u64)
#define VIDEO_COMMAND _IOWR('o', 59, struct video_command)

/* The structure must be zeroed before use by the application
   This ensures it can be extended safely in the future. */
struct video_command {
    __u32 cmd;
    __u32 flags;
    union {
        struct {
            __u64 pts;
        } stop;

        struct {
            __u32 speed;
            __u32 format;
        } play;

        struct {
            __u32 data[16];
        } raw;
    };
};
/* End of extract from linux/video.h                           */
/***************************************************************/

static cPvr350Device  *Instance = 0;
static struct ivtv_speed SpeedDefault;
static char old_ioctl = 0;
static fb_var_screeninfo ivtvfb_var;
static fb_var_screeninfo ivtvfb_var_old;

/*
 * local function IOCTL
 * retries the ioctl given six times before giving up,
 * improves stability if device/driver is actually busy
 * */
static int IOCTL(int fd, int cmd, void *data)
{
	int retry;

	for (retry = 5; retry >= 0; ) {
		if (ioctl(fd, cmd, data) != 0) {
			if (retry) {
				usleep(20000); /* 20msec */
				retry--;
				continue;
			}
			return -1;
		}
		else
			return 0;	/* all went okay :) */
	}
	return 0;	/* should never reach this */
}


cPvr350Device::cPvr350Device(void)
    : fd_out(-1)
{
	Instance = this;
        char videodevName[64];
	sprintf(videodevName, "/dev/video%d", Pvr350Setup.DeviceNumber);
	fd_out = open(videodevName, O_WRONLY | O_NONBLOCK);
	if (fd_out < 0) {
		perror("pvr350: Can't open video decoder device");
		return;
	}
        char vbidevName[64];
	sprintf(vbidevName, "/dev/vbi%d", Pvr350Setup.DeviceNumber);
	fh = open(vbidevName, O_WRONLY);
	if (fh < 0) {
		perror("pvr350: Can't open vbi device for writing VBI data to TV-out");
	}

	// Query capabilities (i.e. test V4L2 support)
	if (GetIvtvVersion(1,fd_out) < KERNEL_VERSION(1, 0, 0)) {
		useV4L2ioctls = false;
		OpenIVTV();
	} else {
		// Assume IVTV Version for V4L2 will be >= 1.0.x
		useV4L2ioctls = true;
		OpenV4L2();
	}
	// Get kernel version number
	struct utsname buf;
	int maj, min, micro, patch;
	if (uname(&buf) < 0) {
		esyslog("pvr350: Getting kernel version failed !");
		// Must set KernelVersion for further use
		KernelVersion = KERNEL_VERSION(2,6,0);
	} else {
		sscanf(buf.release, "%d.%d.%d.%d", &maj, &min, &micro, &patch);
		log(3, "cPvr350Device::Kernelversion=%d.%d.%d.%d", maj, min, micro, patch);
		KernelVersion = KERNEL_VERSION(maj,min,micro);
	}

	m_AC3toMP2Init = false;
	m_AC3DecodeStatePtr = (AC3DecodeState_t *)&m_AC3DecodeState;

}


void cPvr350Device::OpenV4L2()
{
	int ret;
	struct v4l2_framebuffer fbuf;
	int fbi;

	log(1, "cPvr350Device::OpenV4L2()");

	ioctl(fd_out, VIDIOC_G_FBUF, &fbuf);
	for (fbi = 0; fbi < 10; fbi++) {
		struct fb_fix_screeninfo si;
		char buf[10];
		sprintf(buf, "/dev/fb%d", fbi);
		fbfd = open(buf, O_RDWR);
		if (fbfd < 0)
			continue;
		ioctl(fbfd, FBIOGET_FSCREENINFO, &si);
 		if (si.smem_start == (unsigned long)fbuf.base)
			break;
		close(fbfd);
		fbfd = -1;
	}
	if (fbfd == -1) {
		perror("pvr350: Cannot find framebuffer");
		exit(1);
	}

	Format16_9 = true;

	struct v4l2_format fmt;

	if ((ret = ioctl(fd_out, VIDIOC_G_FBUF, &fbuf)) < 0) {
		esyslog("pvr350: VIDIOC_G_FBUF error=%d", errno);
	}
	fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;

	if ((ret = ioctl(fd_out, VIDIOC_G_FMT, &fmt)) < 0) {
		esyslog("pvr350: VIDIOC_G_FMT error=%d", errno);
	}

	log(2, "cPvr350Device::OpenV4L2() %d %d %d %x",
		fbuf.flags & V4L2_FBUF_FLAG_CHROMAKEY,
			fbuf.flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA,
			fbuf.flags & V4L2_FBUF_FLAG_LOCAL_ALPHA,
			fmt.fmt.win.global_alpha);


	fbuf.flags = V4L2_FBUF_FLAG_LOCAL_ALPHA;
	if ((ret = ioctl(fd_out, VIDIOC_S_FBUF, &fbuf)) < 0) {
		esyslog("pvr350: VIDIOC_S_FBUF error=%d", errno);
	}
	
	fmt.fmt.win.global_alpha = 0;
	if ((ret = ioctl(fd_out, VIDIOC_S_FMT, &fmt)) < 0) {
		esyslog("pvr350: VIDIOC_S_FMT error=%d", errno);
	}
 
	struct fb_fix_screeninfo ivtvfb_fix;
	memset(&ivtvfb_fix, 0, sizeof(ivtvfb_fix));

	if (ioctl(fbfd, FBIOGET_FSCREENINFO, &ivtvfb_fix) < 0) {
		perror("FBIOGET_FSCREENINFO");
		return;
	} else {
		// Take snapshot of current mode
		ioctl(fbfd, FBIOGET_VSCREENINFO, &ivtvfb_var_old);
		// Now set the display to 720x576x32
		memset(&ivtvfb_var, 0, sizeof(ivtvfb_var));
		ivtvfb_var.xres = ivtvfb_var.xres_virtual = 720;
		ivtvfb_var.yres = ivtvfb_var.yres_virtual = 576;
		ivtvfb_var.bits_per_pixel = 32;
		ivtvfb_var.activate = FB_ACTIVATE_NOW;
		if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &ivtvfb_var) < 0) {
			// Mode change failed. Maybe NTSC, so try again
			ivtvfb_var.yres = ivtvfb_var.yres_virtual = 480;
			if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &ivtvfb_var) < 0) {
				perror("FBIOPUT_VSCREENINFO");
				return;
			}
		}
		osdbufsize = ivtvfb_fix.smem_len;
		stride = ivtvfb_var.xres * (ivtvfb_var.bits_per_pixel / 8);
	}

	osdbuffer = new unsigned char[osdbufsize + PAGE_SIZE];
	osdbuf_aligned = (unsigned char *)((int)osdbuffer + (PAGE_SIZE - 1));
	osdbuf_aligned = (unsigned char *)((int)osdbuf_aligned & PAGE_MASK);
	memset(osdbuf_aligned, 0x00, osdbufsize);

	struct ivtvfb_dma_frame prep;
	memset(&prep, 0, sizeof(prep));
	prep.source = osdbuf_aligned;
	prep.dest_offset = 0;
	prep.count = ivtvfb_var.xres * ivtvfb_var.yres * (ivtvfb_var.bits_per_pixel / 8);
	memset(osdbuf_aligned, 0x00, osdbufsize);
	   if (ioctl(fbfd, IVTVFB_IOC_DMA_FRAME, &prep) < 0) {
	      esyslog("pvr350: IVTVFB_IOC_DMA_FRAME error=%d", errno);
	   }

	spuDecoder = NULL;
	ResetVideoSize();
}

void cPvr350Device::OpenIVTV()
{
	log(1, "cPvr350Device::OpenIVTV()");

	if (ioctl(fd_out, IVTV_IOC_G_SPEED, &SpeedDefault) < 0) {
		esyslog("pvr350: Get default speed values error=%d", errno);
	}

	log(3, "cPvr350Device::OpenIVTV()",
		"	smooth=%d\n",
		"	speed=%d\n",
		"	direction=%d\n",
		"	fr_mask=%d\n",
		"	b_per_gop=%d\n",
		"	aud_mute=%d\n",
		"	fr_field=%d\n",
		"	mute=%d",
		SpeedDefault.smooth,
		SpeedDefault.speed,
		SpeedDefault.direction,
		SpeedDefault.fr_mask,
		SpeedDefault.b_per_gop,
		SpeedDefault.aud_mute,
		SpeedDefault.fr_field,
		SpeedDefault.mute);

	int fbno;
	char  fbdev[FILENAME_MAX+1];
	if (ioctl(fd_out, IVTV_IOC_GET_FB, &fbno) < 0) {
		esyslog("pvr350: IVTV_IOC_GET_FB error=%d", errno);
		return ;
	}

	if (fbno < 0) {
		esyslog("pvr350: Invalid fb, are you using the ivtv-fb module ?");
		return ;
	}

	Format16_9 = true;

	snprintf(fbdev, sizeof(fbdev), "/dev/fb%d", fbno);
	fbfd = open(fbdev, O_RDWR);
	if (fbfd < 0) {
		perror("pvr350: Can't open framebuffer device");
		return;
	}

	struct ivtvfb_ioctl_state_info fbstate;
	memset(&fbstate, 0, sizeof(fbstate));

	if (ioctl(fbfd, IVTVFB_IOCTL_GET_STATE, &fbstate) < 0) {
		esyslog("pvr350: IVTVFB_IOCTL_GET_STATE error=%d", errno);
		return;
	}

	initglobalalpha = fbstate.status;
	storedglobalalpha = fbstate.alpha;

	fbstate.status &= ~IVTVFB_STATUS_GLOBAL_ALPHA;
	fbstate.status |= IVTVFB_STATUS_LOCAL_ALPHA;
	fbstate.alpha = 0;

	if (ioctl(fbfd, IVTVFB_IOCTL_SET_STATE, &fbstate) < 0) {
		esyslog("pvr350: IVTVFB_IOCTL_SET_STATE error=%d", errno);
		return ;
	}

	struct ivtvfb_ioctl_get_frame_buffer igfb;
	memset(&igfb, 0, sizeof(igfb));

	if (ioctl(fbfd, IVTVFB_IOCTL_GET_FRAME_BUFFER, &igfb) < 0) {
		if (errno == EINVAL) {
			struct fb_fix_screeninfo ivtvfb_fix;
			memset(&ivtvfb_fix, 0, sizeof(ivtvfb_fix));

			if (ioctl(fbfd, FBIOGET_FSCREENINFO, &ivtvfb_fix) < 0) {
				esyslog("pvr350: FBIOGET_FSCREENINFO error=%d", errno);
				return;
			}

			// Take snapshot of current mode
			if (ioctl(fbfd, FBIOGET_VSCREENINFO, &ivtvfb_var_old) < 0) {
				esyslog("pvr350: FBIOGET_VSCREENINFO error=%d", errno);
			}
			// Now set the display to 720x576x32
			memset(&ivtvfb_var, 0, sizeof(ivtvfb_var));
			ivtvfb_var.xres = ivtvfb_var.xres_virtual = 720;
			ivtvfb_var.yres = ivtvfb_var.yres_virtual = 576;
			ivtvfb_var.bits_per_pixel = 32;
			ivtvfb_var.activate = FB_ACTIVATE_NOW;
			if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &ivtvfb_var) < 0) {
				// Mode change failed. Maybe NTSC, so try again
				ivtvfb_var.yres = ivtvfb_var.yres_virtual = 480;
				if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &ivtvfb_var) < 0) {
					esyslog("pvr350: FBIOPUT_VSCREENINFO error=%d", errno);
					return;
				}
			}
			osdbufsize = ivtvfb_fix.smem_len;
			stride = ivtvfb_var.xres * (ivtvfb_var.bits_per_pixel / 8);
		} else {
			esyslog("pvr350: IVTVFB_IOCTL_GET_FRAME_BUFFER error=%d", errno);
			return;
		}
	} else {
		old_ioctl = 1;
		osdbufsize = igfb.size;
		stride = igfb.sizex * 4;
	}

	osdbuffer = new unsigned char[osdbufsize + PAGE_SIZE];
	osdbuf_aligned = (unsigned char *)((int)osdbuffer + (PAGE_SIZE - 1));
	osdbuf_aligned = (unsigned char *)((int)osdbuf_aligned & PAGE_MASK);
	memset(osdbuf_aligned, 0x00, osdbufsize);

	struct ivtvfb_ioctl_dma_host_to_ivtv_args prep;
	memset(&prep, 0, sizeof(prep));

	prep.source = osdbuf_aligned;
	prep.dest_offset = 0;

	// If new ioctl, we've already done this
	if (old_ioctl) {
		struct ivtv_osd_coords osdcoords;
		memset(&osdcoords, 0, sizeof(osdcoords));
		if (ioctl(fbfd, IVTVFB_IOCTL_GET_ACTIVE_BUFFER, &osdcoords) < 0) {
			esyslog("pvr350: IVTVFB_IOCTL_GET_ACTIVE_BUFFER error=%d", errno);
		}
		prep.count = osdcoords.max_offset;
		osdcoords.lines = 576;
		osdcoords.offset = 0;
		osdcoords.pixel_stride = 720 * 2;
		if (ioctl(fbfd, IVTVFB_IOCTL_SET_ACTIVE_BUFFER, &osdcoords) < 0) {
			esyslog("pvr350: IVTVFB_IOCTL_SET_ACTIVE_BUFFER error=%d", errno);
		}
	} else {
		prep.count = ivtvfb_var.xres * ivtvfb_var.yres * (ivtvfb_var.bits_per_pixel / 8);
	}

	if (ioctl(fbfd, IVTVFB_IOCTL_PREP_FRAME, &prep) < 0) {
		esyslog("pvr350: IVTVFB_IOCTL_PREP_FRAME error=%d", errno);
	}

	spuDecoder = NULL;
	ResetVideoSize();
}


cPvr350Device::~cPvr350Device()
{
	log(1, "pvr350: ~cPvr350Device()");
	delete spuDecoder;
	if (osdbuffer != NULL) {
		log(2, "pvr350: ~cPvr350Device() - delete osdbuffer");
		delete[] osdbuffer;
	}
	// If new ioctl, then restore old screen mode
	if (!old_ioctl)
		if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &ivtvfb_var_old) < 0) {
			esyslog("pvr350: ~cPvr350Device() FBIOPUT_VSCREENINFO error=%d", errno);
		}
	// We're not explicitly closing any device files here, since this sometimes
	// caused segfaults. Besides, the program is about to terminate anyway...
	// (taken from dvbdevice.c)
}

void cPvr350Device::MakePrimaryDevice(bool On)
{
	log(1, "cPvr350Device::MakePrimaryDevice(%s)", On ? "true" : "false");
	if (HasDecoder())
	   new cPvr350OsdProvider(fbfd, osdbuffer);
}

bool cPvr350Device::HasDecoder(void) const
{
	return fd_out >= 0; // We can decode MPEG2
}

bool cPvr350Device::CanReplay(void) const 
{
	return true;  // We can replay
}

bool cPvr350Device::SetPlayMode(ePlayMode PlayMode)
{
	log(1, "cPvr350Device::SetPlayMode() [pmNone=%d] %d",
		pmNone, PlayMode);

	m_PlayMode = PlayMode;

	if (PlayMode != pmExtern_THIS_SHOULD_BE_AVOIDED && fd_out < 0 && fh < 0) {
		// reopen the devices
        	char videodevName[64];
		sprintf(videodevName, "/dev/video%d", Pvr350Setup.DeviceNumber);
		fd_out = open(videodevName, O_WRONLY | O_NONBLOCK);
		if (fd_out < 0) {
			perror("pvr350: Can't reopen video decoder device");
			return false;
		}
        	char vbidevName[64];
		sprintf(vbidevName, "/dev/vbi%d", Pvr350Setup.DeviceNumber);
		fh = open(vbidevName, O_WRONLY);
		if (fh < 0) {
			perror("pvr350: Can't reopen vbi device for writing VBI data to TV-out");
			return false;
		}
		SetVideoFormat(Setup.VideoFormat);
	}
	switch (PlayMode) {
	case pmNone:		// audio/video from decoder
		wss_data = 0;
		Stop_Decode_Black();
		break;
	case  pmExtern_THIS_SHOULD_BE_AVOIDED:
		if (Pvr350Setup.WSS_169_for_pmExtern == 1) {
			wss_data = 7;
			Set_wss_mode();
		}
		close(fd_out);
		close(fh);
		fd_out = fh = -1;
		break;
	case pmAudioOnlyBlack:	// audio only from player, no video (black screen)
	case pmAudioOnly: 	// audio only from player, video from decoder
	case pmAudioVideo:	// audio/video from player
	default:
		Start_Decode();
	}
	return true;
}

static int get_control(int fd, int id)
{
	struct v4l2_ext_control ctrl;
	struct v4l2_ext_controls ctrls;

	memset(&ctrl, 0, sizeof(ctrl));
	memset(&ctrls, 0, sizeof(ctrls));
	ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(id);
	ctrls.count = 1;
	ctrls.controls = &ctrl;
	ctrl.id = id;
	if (IOCTL(fd, VIDIOC_G_EXT_CTRLS, &ctrls) < 0) {
		esyslog("pvr350: get_control error=%d", errno);
	}
	return ctrl.value;
}

/*
	speed == 0 || speed == 1000: normal speed
	speed == 1: single step forward
	speed == -1: single step backward
	1 < speed < 1000: slow forward
	speed > 1000: fast forward
	speed == -1000: reverse play at normal speed
	-1000 < speed < -1: slow reverse
	speed < -1000: fast reverse.
 	
	other description:

	0 or 1000 specifies normal speed,
	1 specifies forward single stepping,
	-1 specifies backward single stepping,
	>1: playback at speed/1000 of the normal speed,
	<-1: reverse playback at (-speed/1000) of the normal speed. 
*/

void cPvr350Device::TrickSpeed(int Speed)
{
	int SetSpeed = 0;
	struct video_command cmd;
	struct ivtv_speed speed;

	log(1, "cPvr350Device::TrickSpeed() - Set speed %d", Speed);
	if (useV4L2ioctls) {
		memset(&cmd, 0, sizeof(cmd));
		cmd.cmd = VIDEO_CMD_PLAY;

		switch (Speed) {
		case 1 : // FastForward [3>>] or FastBackward [<<3]
			SetSpeed = 1000;
			break; 
		case 3 : // FastForward [2>>] or FastBackward [<<2]
			SetSpeed = 500;
			break;
		case 6 : // FastForward [1>>] or FastBackward [<<1]
			SetSpeed = 250;
			break;
		case 8 : // SlowForward [1|>]
			SetSpeed = 40;
			break; 
		case 4 : // SlowForward [2|>]
			SetSpeed = 80;
			break; 
		case 2 : // SlowForward [3|>]
			SetSpeed = 160;
			break;
		case 63 : // SlowReverse [<|1]
			SetSpeed = 40;
			break; 
		case 48 : // SlowReverse [<|2]
			SetSpeed = 80;
			break; 
		case 24 : // SlowReverse [<|3]
			SetSpeed = 160;
			break;
		}

		cmd.play.speed = SetSpeed;

		if (IOCTL(fd_out, VIDEO_COMMAND, &cmd) < 0) {
			esyslog("pvr350: Speed error=%d", errno);
		}
	} else {
		memset(&speed, 0, sizeof(speed));
		speed.fr_mask = 2;
		speed.b_per_gop = get_control(fd_out, V4L2_CID_MPEG_VIDEO_B_FRAMES);

		switch (Speed) {
		case 1 : // FastForward [3>>] or FastBackward [<<3]
			speed.scale = 0;
			speed.speed = 1;
			speed.fr_field = 0;
			break; 
		case 3 : // FastForward [2>>] or FastBackward [<<2]
			speed.scale = 3;
			speed.speed = 0;
			speed.fr_field = 0;
			break;
		case 6 : // FastForward [1>>] or FastBackward [<<1]
			speed.scale = 6;
			speed.speed = 0;
			speed.fr_field = 0;
			break;
		case 8 : // SlowForward [1|>]
			speed.scale = 10;
			speed.speed = 0;
			speed.fr_field = 1;
			break; 
		case 4 : // SlowForward [2|>]
			speed.scale = 6;
			speed.speed = 0;
			speed.fr_field = 1;
			break; 
		case 2 : // SlowForward [3|>]
			speed.scale = 3;
			speed.speed = 0;
			speed.fr_field = 1;
			break;
		case 63 : // SlowReverse [<|1]
			speed.scale = 10;
			speed.speed = 0;
			speed.fr_field = 1;
			break; 
		case 48 : // SlowReverse [<|2]
			speed.scale = 6;
			speed.speed = 0;
			speed.fr_field = 1;
			break; 
		case 24 : // SlowReverse [<|3]
			speed.scale = 3;
			speed.speed = 0;
			speed.fr_field = 1;
			break;
		}
		if (IOCTL(fd_out, IVTV_IOC_S_SPEED, &speed) < 0) {
			esyslog("pvr350: Speed error=%d", errno);
		}
	}
}

void cPvr350Device::SetVideoFormat(bool VideoFormat16_9)
{
	log(1, "(cPvr350Device::SetVideoFormat(VideoFormat16_9=%s)",
		VideoFormat16_9 ? "true" : "false");
	Format16_9 = VideoFormat16_9;
}

void cPvr350Device::Stop_Decode_Black(void)
{
	struct video_command cmd;
	struct ivtv_cfg_stop_decode stop;

	log(1, "cPvr350Device::Stop_Decode_Black()");
	if (useV4L2ioctls) {
		memset(&cmd, 0, sizeof(cmd));
		cmd.cmd = VIDEO_CMD_STOP;
		cmd.flags = VIDEO_CMD_STOP_TO_BLACK | VIDEO_CMD_STOP_IMMEDIATELY;

		if (IOCTL(fd_out, VIDEO_COMMAND, &cmd) < 0) {
			esyslog("pvr350: Stop_Decode_Black error=%d", errno);
		}
	} else {
		stop.pts = 0;
		stop.flags = IVTV_STOP_FL_HIDE_FRAME;

		if (IOCTL(fd_out, IVTV_IOC_STOP_DECODE, &stop) < 0) {
			esyslog("pvr350: Stop_Decode_Black error=%d", errno);
		}
	}
}


void cPvr350Device::Start_Decode(void)
{
	struct video_command cmd;
	struct ivtv_cfg_start_decode startd;

	log(1, "cPvr350Device::Start_Decode()");
	if (useV4L2ioctls) {
		memset(&cmd, 0, sizeof(cmd));
		cmd.cmd = VIDEO_CMD_PLAY;

		if (ioctl(fd_out, VIDEO_COMMAND, &cmd) < 0) {
			esyslog("pvr350: Start_Decode error=%d", errno);
		}
	} else {
		startd.gop_offset = 0;
		startd.muted_audio_frames = 1;

		if (IOCTL(fd_out, IVTV_IOC_START_DECODE, &startd) < 0) {
			esyslog("pvr350: Start_Decode error=%d", errno);
		}
	}
}

void cPvr350Device::Clear(void)
{
	struct video_command cmd;
	struct ivtv_cfg_stop_decode stop;

	log(1, "cPvr350Device::Clear()");
	/*Stop the decoder immidiately (but show last frame instead of a black screen). This clears the decoder buffer. After this,
	restart the decoder (although an incoming stream should do this automatically)
	*/
	if (useV4L2ioctls) {
		memset(&cmd, 0, sizeof(cmd));
		cmd.cmd = VIDEO_CMD_STOP;
		cmd.flags = VIDEO_CMD_STOP_IMMEDIATELY;

		if (ioctl(fd_out, VIDEO_COMMAND, &cmd) < 0) {
			esyslog("pvr350: VIDEO_CMD_STOP error=%d during Clear()", errno);
		}
	} else {
		stop.pts = 0;
		stop.flags = 0;

		if (IOCTL(fd_out, IVTV_IOC_STOP_DECODE, &stop) < 0) {
			esyslog("pvr350: IVTV_IOC_STOP_DECODE error=%d during Clear()", errno);
		}
	}
	Start_Decode();
	cDevice::Clear();
       ///< A derived class must call the base class function to make sure
       ///< all registered cAudio objects are notified.
}

void cPvr350Device::Play(void)
{
	struct video_command cmd;
	struct ivtv_speed speed;

	log(1, "cPvr350Device::(Resume) Playback");
	if (useV4L2ioctls) {
		memset(&cmd, 0, sizeof(cmd));
		cmd.cmd = VIDEO_CMD_PLAY; 
		//we cannot use VIDEO_CMD_CONTINUE: Leaving slow speed (trickmode) must reset to normal speed
		cmd.play.speed = 1000;
		if (ioctl(fd_out, VIDEO_COMMAND, &cmd) < 0) {
			esyslog("pvr350: Play error=%d", errno);
		}
		if ((KernelVersion <= KERNEL_VERSION(2,6,22)) &&
		    (GetIvtvVersion(0,0) == KERNEL_VERSION(1, 0, 0))) {
			/* A bugfix for pause/resume (http://linuxtv.org/hg/v4l-dvb/rev/5541b65b4b19) didn`t make it into 2.6.22
			   Let`s check if we have a 2.6.22 with original ivtv (1.0.0). If ivtv driver version is higher than 1.0.0, 
			   we already have an updated driver from v4l-dvb hg. In this case the following workaround is not necessary.
			   We can not only check the ivtv driver version, because in Kernel 2.6.23 the ivtv driver version 
			   (although it includes the fix) has also version number 1.0.0
			*/ 
			log(1, "cPvr350Device::(Continue) Playback");
			cmd.cmd = VIDEO_CMD_CONTINUE;
			if (ioctl(fd_out, VIDEO_COMMAND, &cmd) < 0) {
				esyslog("pvr350: Play (Continue) error=%d", errno);
			}
		}
	} else {
		#if 0 //see http://www.gossamer-threads.com/lists/ivtv/devel/34895
		if (IOCTL(fd_out, IVTV_IOC_PLAY, 0) < 0) {
			esyslog("pvr350: Play error=%d", errno);
		}
		#endif
		memset(&speed, 0, sizeof(speed));
		speed.fr_mask = SpeedDefault.fr_mask;
		speed.b_per_gop = SpeedDefault.b_per_gop;
		if (IOCTL(fd_out, IVTV_IOC_S_SPEED, &speed) < 0) {
			esyslog("pvr350: Play error=%d", errno);
		}
	}
}

void cPvr350Device::Freeze(void)
{
	struct video_command cmd;

	log(1, "cPvr350Device::Freeze()");
	if (useV4L2ioctls) {
		memset(&cmd, 0, sizeof(cmd));
		cmd.cmd = VIDEO_CMD_FREEZE;
		if (ioctl(fd_out, VIDEO_COMMAND, &cmd) < 0) {
			esyslog("pvr350: Freeze error=%d", errno);
		}
	} else {
		if (IOCTL(fd_out, IVTV_IOC_PAUSE, 0) < 0) {
			esyslog("pvr350: Freeze error=%d", errno);
		}
	}
}

void cPvr350Device::Mute(void)
{
	log(1, "cPvr350Device::Mute()");
	cDevice::Mute();
       ///< A derived class must call the base class function to make sure
       ///< all registered cAudio objects are notified.
}

int cPvr350Device::PlayAudio(const uchar *Data, int Length, uchar Id)
{
	int PESHeader_len;
	uint8_t *PESPacketData;
	int PESPacketData_len;
	int PES_Packet_Length;
	int len;
	static uchar Last_Id = 0;

	log(4, "cPvr350Device::PlayAudio()");

	if (Id != Last_Id) {
		Last_Id = Id;
		log(2, "Length=%d, Id=0x%02x", Length, Id);
	}

	/*
	 * PES Packet Length:	Specifies the number of bytes remaining
	 * 			in the packet after this field.
	 */
	len = Data[PESPACKETLEN1];
	PES_Packet_Length = (len << 8) + Data[PESPACKETLEN2];
	PES_Packet_Length += PESPACKETLEN2 + 1;
	if (Length != PES_Packet_Length) {
		esyslog("pvr350: PlayAudio() PES Packet seems to be corrupted");
		log(2, "Length=%d, len=%d", Length, PES_Packet_Length);
	}

	PESHeader_len = Data[PESHEADERLENGTH] + PESFIXHEADERLEN;
#ifdef DEBUG
	if (Length <= PESHeader_len) {
		log(3, "Length=%d <= PESHeader_len=%d", Length, PESHeader_len);
		return Length;
	}
#endif
	PESPacketData = (uint8_t *)&Data[PESHeader_len];
	PESPacketData_len = Length - PESHeader_len;

	switch (Id) {
	case 0x80 ... 0x87 :		// AC3
		if (!m_AC3toMP2Init) {
			// Create a new decode/encode audio thread
			m_AC3toMP2 = new cAC3toMP2(m_AC3DecodeStatePtr);
			m_AC3toMP2Init = true;
		}

		ProcessAC3Audio((uint8_t *)Data, PESHeader_len, Length);

		len = Length;	// Return success to VDR
		break;

	case 0xA0 ... 0xA7 :		// LPCM
		if (!m_AC3toMP2Init) {
			// Create a new decode/encode audio thread
			m_AC3toMP2 = new cAC3toMP2(m_AC3DecodeStatePtr);
			m_AC3toMP2Init = false;
		}

		// skip the AC3 identifier bytes
		PESPacketData += PCM_IDENTIFIER_BYTES;
		PESPacketData_len -= PCM_IDENTIFIER_BYTES;

		// ToDo: PCM to MP2 conversion

		len = Length;	// Return success to VDR
		break;

	case 0xC0 ... 0xDF :				// MP2
		if (m_AC3toMP2Init) {
			// Delete decode/encode audio thread
			delete m_AC3toMP2;
			m_AC3toMP2Init = false;
		}

		len = WriteAllOrNothing(fd_out, Data, Length, 1000, 10);
		if (len <= 0) {
			esyslog("pvr350: PlayAudio[MP2] written=%d error=%d", len, errno);
		}
		break;

	default:
		esyslog("pvr350: PlayAudio unknown audio format, Id=0x%02x", Id);
		len = Length;	// Return success to VDR
		break;
	}
	return len;
}

int cPvr350Device::PlayVideo(const uchar *Data, int Length)
{
	int len;

	log(4, "cPvr350Device::PlayVideo(Length=%d)", Length);
	if (useV4L2ioctls) {
		SetVidInfoV4L2(Data, Length);
	 } else {
		SetVidInfoIVTV(Data, Length);
	}
	
	len = WriteAllOrNothing(fd_out, Data, Length, 1000, 10);

	if (len <= 0) {
		esyslog("pvr350: PlayVideo written=%d error=%d", len, errno);
	}
	return len;
}

void cPvr350Device::SetVolumeDevice(int Volume)
{
}

void cPvr350Device::StillPicture(const uchar *Data, int Length)
{
	log(1, "cPvr350Device::StillPicture(Length=%d)", Length);
#define MIN_IFRAME 400000
	for (int i = MIN_IFRAME / Length + 1; i > 0; i--) {
		WriteAllOrNothing(fd_out, Data, Length, 1000, 10);
		usleep(1);	// allows the buffer to be displayed in
				// case the progress display is active
	}
}

bool cPvr350Device::Poll(cPoller &Poller, int TimeoutMs)
{
	log(4, "cPvr350Device::Poll(TimeoutMs=%d)", TimeoutMs);
	Poller.Add(fd_out,true);
	return Poller.Poll(TimeoutMs);
}

cSpuDecoder *cPvr350Device::GetSpuDecoder(void)
{
	log(1, "cPvr350Device::GetSpuDecoder()");
	if (!spuDecoder && IsPrimaryDevice())
		spuDecoder = new cDvbSpuDecoder();
	return spuDecoder;
}


void cPvr350Device::SetVidInfoV4L2(const uchar *mbuf, int count)
{
	const uchar *headr;
	int found = 0;
	int sw;
	int c = 0;

	struct v4l2_crop crop;
	memset (&crop, 0, sizeof (crop));
	crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

	log(4, "cPvr350Device::SetVidInfoV4L2()");
	while (found < 4 && c+4 < count){
		const uchar *b;

		b = mbuf + c;
		if (b[0] == 0x00 && b[1] == 0x00 &&
		    b[2] == 0x01 && b[3] == 0xb3) {
			found = 4;
		} else {
			c++;
		}
	}

	if (! found) return;
	c += 4;
	if (c+12 >= count) return;
	headr = mbuf+c; 

	int horizontal_size = ((headr[1] &0xF0) >> 4) | (headr[0] << 4);
	int vertical_size   = ((headr[1] &0x0F) << 8) | (headr[2]);
	sw = (int)((headr[3]&0xF0) >> 4) ;
	log(4, "sw=%d", sw);	 // Are there ever other values than 2 or 3 ?
	if ((horizontal_size > 720) || (vertical_size > 576))
		return;

	switch (sw) {
	case 1:
		break;
	case 2:  /* 4:3  aspect ratio */
		if (wss_data != 8) {
			wss_data = 8;
			Set_wss_mode();
		}
		if (Format16_9 == false) {	/* 4:3 TV */
			crop.c.width = width;
			crop.c.height= height;
			crop.c.left  = lx;
			crop.c.top   = ly;
		} else {			/* 16:9 TV */

			crop.c.width = width;
			crop.c.height= height;
			crop.c.left  = lx;
			crop.c.top   = ly;
		}
		if (crop.c.width  != (int)current_horiz ||
		    crop.c.height != (int)current_vertical ) {
			current_horiz    = crop.c.width;
			current_vertical = crop.c.height;
			log(2, "Trying size set (4:3): %dx%d %dx%d",
				crop.c.width, crop.c.height,
				crop.c.left, crop.c.top);
			if (IOCTL(fd_out, VIDIOC_S_CROP,&crop) < 0) {
				esyslog("pvr350: SetVidInfo (4:3) error=%d", errno);
			
			}
		}
		break;
	case 3:  /* 16:9 aspect ratio */
		if (Format16_9) {  /* 16:9 TV */
			crop.c.width = width;
			crop.c.height= height;
			crop.c.left  = lx;
			crop.c.top   = ly;
			if (wss_data != 7) {
				wss_data = 7;
				Set_wss_mode();
			}
		} else {  /* 4:3 TV */
			if (wss_data != 8) {
				wss_data = 8;
				Set_wss_mode();
			}
			if (horizontal_size <= vertical_size) {
				crop.c.width  = width;
				crop.c.height = (width/ 16) * 9;
				crop.c.left   = lx + (width - crop.c.width) / 2;
				crop.c.top    = ly + (height- crop.c.height) / 2;
			} else {
				crop.c.width = width < horizontal_size ? width : horizontal_size;
				//crop.c.width.height = ( crop.c.width.width ) / 16 * 9;
				crop.c.height = ( vertical_size ) / 4 * 3;
				crop.c.left = lx + (width - crop.c.width) / 2;
				crop.c.top = ly + (height - crop.c.height) / 2;
			}
		}
		if (crop.c.width != (int)current_horiz ||
		    crop.c.height != (int)current_vertical || sizechanged ) {
			sizechanged = 0;
			current_horiz = crop.c.width;
			current_vertical = crop.c.height;
			log(2, "Trying size set (16:9): %dx%d %dx%d",
 				crop.c.width,crop.c.height,
				crop.c.left, crop.c.top);
			if (IOCTL(fd_out, VIDIOC_S_CROP,&crop) < 0) {
				esyslog("pvr350: SetVidInfo (16:9) error=%d", errno);
			
			}
		}
		break;
	case 4:
		break;
	case 5 ... 15:
		break;
	default:
		break;
	}
}

void cPvr350Device::SetVidInfoIVTV(const uchar *mbuf, int count)
{
	const uchar *headr;
	int found = 0;
	int sw;
	int c = 0;

	struct ivtvfb_ioctl_set_window fb_ss;

	log(4, "cPvr350Device::SetVidInfoIVTV()");
	while (found < 4 && c+4 < count){
		const uchar *b;

		b = mbuf + c;
		if (b[0] == 0x00 && b[1] == 0x00 &&
		    b[2] == 0x01 && b[3] == 0xb3) {
			found = 4;
		} else {
			c++;
		}
	}

	if (! found) return;
	c += 4;
	if (c+12 >= count) return;
	headr = mbuf + c;

	int horizontal_size = ((headr[1] & 0xF0) >> 4) | (headr[0] << 4);
	int vertical_size   = ((headr[1] & 0x0F) << 8) | (headr[2]);
	sw = (int)((headr[3] & 0xF0) >> 4) ;
	log(4, "sw=%d", sw);	 // Are there ever other values than 2 or 3 ?
	if ((horizontal_size > 720) || (vertical_size > 576))
		return;
	
	switch ( sw ) {
	case 1:
		break;

	case 2:	/* 4:3 */
		if (wss_data != 8) {
			wss_data = 8;
			Set_wss_mode();
		}
		if (Format16_9 == false) {	/* 4:3 TV */
			fb_ss.width = width;
			fb_ss.height= height;
			fb_ss.left  = lx;
			fb_ss.top   = ly;
		} else {			/* 16:9 TV */
			fb_ss.width = width;
			fb_ss.height= height;
			fb_ss.left  = lx;
			fb_ss.top   = ly;
		}

		if (fb_ss.width  != current_horiz ||
		    fb_ss.height != current_vertical) {
			current_horiz    = fb_ss.width;
			current_vertical = fb_ss.height;
			log(2, "Trying size set (4:3): %dx%d %dx%d",
				 fb_ss.width, fb_ss.height,
				 fb_ss.left, fb_ss.top);
			if (IOCTL(fbfd, IVTVFB_IOCTL_SET_WINDOW, &fb_ss) < 0) {
				esyslog("pvr350: SetVidInfo (4:3) error=%d", errno);
			}
		}
		break;

	case 3:	/* 16:9 */
		if (Format16_9) {		/* 16:9 TV */
			fb_ss.width = width;
			fb_ss.height= height;
			fb_ss.left  = lx;
			fb_ss.top   = ly;
			if (wss_data != 7) {
				wss_data = 7;
				Set_wss_mode();
			}
		} else {			/* 4:3 TV */
			if (wss_data != 8) {
				wss_data = 8;
				Set_wss_mode();
			}
			if (horizontal_size <= vertical_size) {
				fb_ss.width  = width;
				fb_ss.height = (width / 16) * 9;
				fb_ss.left   = lx + (width - fb_ss.width) / 2;
				fb_ss.top    = ly + (height- fb_ss.height) / 2;
			} else {		
				fb_ss.width = width < horizontal_size ? width : horizontal_size;
				//fb_ss.height = (fb_ss.width) / 16 * 9;
				fb_ss.height = (vertical_size) / 4 * 3;
				fb_ss.left = lx + (width - fb_ss.width) / 2;
				fb_ss.top = ly + (height - fb_ss.height) / 2;
			}
		}

		if (fb_ss.width != current_horiz ||
		    fb_ss.height != current_vertical || sizechanged) {
			sizechanged = 0;
			current_horiz = fb_ss.width;
			current_vertical = fb_ss.height;
			log(2, "Trying size set (16:9): %dx%d %dx%d",
				 fb_ss.width, fb_ss.height,
				 fb_ss.left, fb_ss.top);
			if (IOCTL(fbfd, IVTVFB_IOCTL_SET_WINDOW, &fb_ss) < 0) {
				esyslog("pvr350: SetVidInfo (16:9) error=%d", errno);
			}
		}
		break;
	case 4:
		break;
	case 5 ... 15:
		break;
	default:
		break;
	}
}
int64_t cPvr350Device::GetSTC(void)
{
	int64_t pts = 0;

	if (useV4L2ioctls) {
		if (ioctl(fd_out, VIDEO_GET_PTS, &pts) < 0) {
			esyslog("pvr350: GetSTC error=%d", errno);
			return -1;
		}	
	} else {
		if (ioctl(fd_out, IVTV_IOC_G_PTS, &pts) < 0) {
			esyslog("pvr350: GetSTC error=%d", errno);
			return -1;
		}	
	}
	log(3, "cPvr350Device::GetSTC(): PTS=%lld", pts);
	return pts;
}

void cPvr350Device::SetVideoSize(int x, int y, int w, int d)
{
	log(1, "cPvr350Device::SetVideoSize(x=%d,y=%d,w=%d,d=%d)", x, y, w, d);
	width = w;
	height = d;
	lx = x;
	ly = y;
	sizechanged = 1;
}

void cPvr350Device::Set_wss_mode()
{
	struct v4l2_format fmt;
	struct v4l2_sliced_vbi_data data;
	
	log(1, "cPvr350Device::Set_wss_mode()");
	if ((fh < 0) || (Pvr350Setup.UseWssBits == 0)) {
		return;
	}

	fmt.fmt.sliced.service_set = V4L2_SLICED_WSS_625;
        fmt.type = V4L2_BUF_TYPE_SLICED_VBI_OUTPUT;
	if (ioctl(fh, VIDIOC_S_FMT, &fmt) != 0) {
		esyslog("pvr350: Set VBI mode failed");
	}
	if (ioctl(fh, VIDIOC_G_FMT, &fmt) != 0) {
		esyslog("pvr350: VIDIOC_G_FMT failed");
	}
	data.id = V4L2_SLICED_WSS_625;
	data.line = 23;
	data.field = 0;
	data.data[0] = wss_data;
	data.data[1] = 0;

	if (wss_data == 7) {
		log(2, "Set wss mode 16:9 (anarmorph)");
	}
	if (wss_data == 8) {
		log(2, "Set wss mode 4:3");
	}
	write(fh, &data, sizeof data);
}

void SetVideoSize(int x, int y, int w, int d)
{
	Instance->SetVideoSize(x, y, w, d);
}

void ResetVideoSize()
{
	/* FIXME: Think NTSC */
	Instance->SetVideoSize(0, 0, 720, 576);
}

void cPvr350Device::ProcessAC3Audio(uint8_t *PESPacket, int PESHeader_len, int Length)
{
	int len;
	int bytes_left;
	int pcm_bytes;
	int mp2_len;
	uint8_t *PESPacketData;
	int PESPacketData_len;
	uint8_t *MP2PESDataPtr;
	int PES_Packet_Length;
	uint8_t *DataPtr;
	int16_t *PCMBufferPtr;
	int PCMBuffer_len;
	int AC3_FrameCount, AC3_StartingIDX;

	if (!m_AC3toMP2Init) {
		// audio thread not running
		return;
	}

	log(4, "cPvr350Device::ProcessAC3Audio()");

	PESPacketData = (uint8_t *)&PESPacket[PESHeader_len];
	PESPacketData_len = Length - PESHeader_len;

	AC3_FrameCount = PESPacketData[SUBSTREAM_AC3_FRAMECNT];
	AC3_StartingIDX = (PESPacketData[SUBSTREAM_AC3_FIRSTACC1] << 8);
	AC3_StartingIDX += PESPacketData[SUBSTREAM_AC3_FIRSTACC2];

#ifdef DEBUG
	if (AC3_StartingIDX < AC3_IDENTIFIER_BYTES) {
		log(3, "PlayAudio() StartingIDX=%d < %d",
			AC3_StartingIDX, AC3_IDENTIFIER_BYTES);
	} else {
		int i;
		if (PESHeader_len > PESFIXHEADERLEN) {
			log(3, "PES Header Fields:");
			for (i = PESFIXHEADERLEN; i <= PESHeader_len; i++)
				log(3, " %02x", PESPacket[i]);
		}
		log(3, "AC3-SubStream: %02x %02x,%d, %02x, %02x, %02x, %02x, %02x",
			PESPacketData[SUBSTREAM_AC3_STREAMID],
			AC3_FrameCount,
			AC3_StartingIDX,
			PESPacketData[4],
			PESPacketData[5],
			PESPacketData[6],
			PESPacketData[7],
			PESPacketData[8]);
	}
#endif

	// skip the AC3 identifier bytes
	PESPacketData += AC3_IDENTIFIER_BYTES;
	PESPacketData_len -= AC3_IDENTIFIER_BYTES;

	bytes_left = PESPacketData_len;
	DataPtr = PESPacketData;
	PCMBufferPtr = (int16_t *)m_PCMBuffer;
	PCMBuffer_len = 0;

	while (bytes_left > 0) {
		len = m_AC3toMP2->A52DecodeFrame(m_AC3DecodeStatePtr,
						PCMBufferPtr, &pcm_bytes,
						DataPtr, bytes_left);
		DataPtr += len;
		bytes_left -= len;
		if (pcm_bytes) {
			// copy 'original' PES header to MP2 PES buffer
			memcpy(m_MP2PESBuffer, PESPacket, PESHeader_len);

			MP2PESDataPtr = (uint8_t *)&m_MP2PESBuffer[PESHeader_len];

			mp2_len = m_AC3toMP2->MP2EncodeFrame(PCMBufferPtr,
								pcm_bytes,
								MP2PESDataPtr);

			// change PES Stream ID
			m_MP2PESBuffer[PESSTREAMID] = MPEG_AUDIO_STREAM_ID;

			// adjust PES Packet Length
			PES_Packet_Length = PESHeader_len - (PESPACKETLEN2 + 1) + mp2_len;
			m_MP2PESBuffer[PESPACKETLEN1] = (uint8_t)(PES_Packet_Length >> 8);
			m_MP2PESBuffer[PESPACKETLEN2] = (uint8_t)(PES_Packet_Length & 0xff);

			// calculate length of PES Packet Frame
			len = PES_Packet_Length + PESPACKETLEN2 + 1;


			len = WriteAllOrNothing(fd_out, m_MP2PESBuffer, len, 1000, 10);
			if (len <= 0) {
				esyslog("pvr350: ProcessAC3Audio written=%d error=%d", len, errno);
			}

		}
	}
	return;
}
