#include <stdio.h>
#include <sys/ioctl.h>

#include <string>

#include <vdr/channels.h>
#include <vdr/sources.h>

#include <linux/videodev.h>

extern "C" {
#include "videodev2.h"
#include "ivtv.h"
}

#include "common.h"
#include "device.h"
#include "global.h"
#include "setup.h"

#define BUFFSIZE 4096 * 32

const short kVideoPid = 301;
const short kAudioPid = 300;
const short kTeletextPid = 305;

typedef enum
{
	videoNormNTSC,
	videoNormSECAM,
	videoNormPAL
} eVideoNorm;

const unsigned char kInvTab[256] =
{
	0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
	0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
	0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
	0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
	0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
	0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
	0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
	0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
	0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
	0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
	0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
	0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
	0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
	0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
	0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
	0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
	0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
	0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
	0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
	0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
	0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
	0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
	0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
	0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
	0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
	0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
	0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
	0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
	0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
	0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
	0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
	0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};


class cPvrReadThread : public cThread
{
private:
	bool active;
	int video_fd;
	int vbi_fd;
	cRingBufferLinear * tsBuffer;
	cMutex * mutex;

	uint8_t ts_buffer[188];
	uint8_t video_counter;
	uint8_t audio_counter;
	uint8_t text_counter;

	uint32_t pes_offset;
	uint8_t pes_buffer[64 * 1024];
	uint8_t pes_stream_id;
	uint32_t pes_length;
	uint32_t pes_tmp;

	void ParseProgramStream(uint8_t * Data, uint32_t Length);
	void PesToTs(uint8_t * Data, uint32_t Length);
	int PutData(const unsigned char * Data, int Count);
protected:
	virtual void Action(void);
public:
	cPvrReadThread(int VideoDevice, int VbiDevice, cRingBufferLinear * TsBuffer, cMutex * Mutex);
	virtual ~cPvrReadThread(void);
};

cPvrReadThread::cPvrReadThread(int VideoDevice, int VbiDevice, cRingBufferLinear * TsBuffer, cMutex * Mutex)
:	active(false),
	video_fd(VideoDevice),
	vbi_fd(VbiDevice),
	tsBuffer(TsBuffer),
	mutex(Mutex),
	video_counter(0),
	audio_counter(0),
	text_counter(0),
	pes_offset(0),
	pes_stream_id(0),
	pes_length(0),
	pes_tmp(0)
{
	Start();
}

cPvrReadThread::~cPvrReadThread(void)
{
	active = false;
	Cancel(3);
}

int cPvrReadThread::PutData(const unsigned char * Data, int Count)
{
	cMutexLock mutexLock(mutex);
	if (tsBuffer->Free() < Count)
		return 0;

//	FILE * fp = fopen("/stream.ts", "ab");
//	fwrite(Data, Count, 1, fp);
//	fclose(fp);

	return tsBuffer->Put(Data, Count);
}

void cPvrReadThread::PesToTs(uint8_t * Data, uint32_t Length)
{
	uint8_t stream_id;
	bool first = true;
	uint32_t i;
	uint32_t rest;

	stream_id = Data[3];
	if (stream_id >= 0xE0 && stream_id <= 0xEF)
	{
		for (i = 0; i < Length / 184; i++)
		{
			ts_buffer[0] = 0x47;
			ts_buffer[1] = first ? 0x40 : 0x00;
			ts_buffer[1] |= kVideoPid >> 8;
			ts_buffer[2] = kVideoPid & 0xFF;
			ts_buffer[3] = 0x10 | video_counter;
			memcpy(ts_buffer + 4, Data + i * 184, 184);
			PutData(ts_buffer, 188);
			video_counter = (video_counter + 1) & 15;
			first = false;
		}
		rest = Length % 184;
		if (rest > 0)
		{
			ts_buffer[0] = 0x47;
			ts_buffer[1] = first ? 0x40 : 0x00;
			ts_buffer[1] |= kVideoPid >> 8;
			ts_buffer[2] = kVideoPid & 0xFF;
			ts_buffer[3] = 0x30 | video_counter;
			ts_buffer[4] = 183 - rest;
			if (ts_buffer[4] > 0)
			{
				ts_buffer[5] = 0x00;
				memset(ts_buffer + 6, 0xFF, ts_buffer[4] - 1);
			}
			memcpy(ts_buffer + 5 + ts_buffer[4], Data + i * 184, rest);
			PutData(ts_buffer, 188);
			video_counter = (video_counter + 1) & 15;
			first = false;
		}
	}
	else if (stream_id >= 0xC0 && stream_id <= 0xDF)
	{
		for (i = 0; i < Length / 184; i++)
		{
			ts_buffer[0] = 0x47;
			ts_buffer[1] = first ? 0x40 : 0x00;
			ts_buffer[1] |= kAudioPid >> 8;
			ts_buffer[2] = kAudioPid & 0xFF;
			ts_buffer[3] = 0x10 | audio_counter;
			memcpy(ts_buffer + 4, Data + i * 184, 184);
			PutData(ts_buffer, 188);
			audio_counter = (audio_counter + 1) & 15;
			first = false;
		}
		rest = Length % 184;
		if (rest > 0)
		{
			ts_buffer[0] = 0x47;
			ts_buffer[1] = first ? 0x40 : 0x00;
			ts_buffer[1] |= kAudioPid >> 8;
			ts_buffer[2] = kAudioPid & 0xFF;
			ts_buffer[3] = 0x30 | audio_counter;
			ts_buffer[4] = 183 - rest;
			if (ts_buffer[4] > 0)
			{
				ts_buffer[5] = 0x00;
				memset(ts_buffer + 6, 0xFF, ts_buffer[4] - 1);
			}
			memcpy(ts_buffer + 5 + ts_buffer[4], Data + i * 184, rest);
			PutData(ts_buffer, 188);
			audio_counter = (audio_counter + 1) & 15;
			first = false;
		}
	}
	else if (stream_id == 0xBD)
	{
		uint8_t * payload_data;
		uint16_t payload_length;
		uint32_t pos = 0;
		uint32_t ts_pos = 0;

		memset(ts_buffer, 0, 188);
		ts_buffer[0] = 0x47;
		ts_buffer[1] = kTeletextPid >> 8;
		ts_buffer[2] = kTeletextPid;
		ts_buffer[3] = 0x10 | text_counter;

		payload_length = Length - 9 - Data[8];
		payload_data = Data + 9 + Data[8];
		if (memcmp(payload_data, "itv0", 4) == 0)
			pos = 12;
		else if (memcmp(payload_data, "ITV0", 4) == 0)
			pos = 4;
		else
			return;
		while (pos + 43 <= payload_length)
		{
			if ((payload_data[pos] & 0x0F) == 0x01)
			{
				ts_buffer[4+ts_pos*46] = 0x02; // data_unit_id
				ts_buffer[5+ts_pos*46] = 0x2C; // data_unit_length
				ts_buffer[6+ts_pos*46] = 0x00; // field_parity, line_offset
				ts_buffer[7+ts_pos*46] = 0xE4; // framing_code
				for (int j = 0; j < 42; j++)
				{
					ts_buffer[8 + ts_pos * 46 + j] = kInvTab[payload_data[pos + 1 + j]];
				}
				ts_pos++;
				if (ts_pos == 4)
				{
					ts_pos = 0;
					PutData(ts_buffer, 188);
					text_counter = (text_counter + 1) & 15;
					memset(ts_buffer, 0, 188);
					ts_buffer[0] = 0x47;
					ts_buffer[1] = kTeletextPid >> 8;
					ts_buffer[2] = kTeletextPid;
					ts_buffer[3] = 0x10 | text_counter;
				}
			}
			pos += 43;
		}
		if (ts_pos > 0)
		{
			PutData(ts_buffer, 188);
			text_counter = (text_counter + 1) & 15;
		}
	}
}

void cPvrReadThread::ParseProgramStream(uint8_t * Data, uint32_t Length)
{
	uint32_t pos;

	pos = 0;
	while (pos < Length)
	{
		if (pes_offset == 0 || pes_offset == 1)
		{
			if (Data[pos] == 0x00)
				pes_offset++;
			else
				pes_offset = 0;
			pos++;
		}
		else if (pes_offset == 2)
		{
			if (Data[pos] == 0x01)
			{
				pes_offset++;
				pos++;
			}
			else
				pes_offset = 0;
		}
		else if (pes_offset == 3)
		{
			pes_stream_id = Data[pos];
			pes_offset++;
			pos++;
		}
		else
		{
			if (pes_stream_id == 0xBA)
			{
				if (pes_offset < 13)
				{
					pes_offset++;
					pos++;
				}
				else if (pes_offset == 13)
				{
					pes_tmp = Data[pos] & 7;
					pes_offset++;
					pos++;
				}
				else
				{
					if (pes_tmp > 0)
					{
						pes_tmp--;
						pos++;
					}
					else
						pes_offset = 0;
						
				}
			}
			else if (pes_stream_id == 0xBB)
			{
				if (pes_offset < 12)
				{
					pes_offset++;
					pos++;
				}
				else
				{
					if (pes_tmp > 0)
					{
						pes_tmp--;
						pos++;
					}
					else if (Data[pos] & 0x80)
					{
						pes_tmp = 3;
					}
					else
					{
						pes_offset = 0;
					}
				}
			}
			else if (pes_stream_id >= 0xE0 && pes_stream_id <= 0xEF ||
			         pes_stream_id >= 0xC0 && pes_stream_id <= 0xDF ||
			         pes_stream_id == 0xBE ||
			         pes_stream_id == 0xBD)
			{
				if (pes_offset == 4)
				{
					pes_length = Data[pos] << 8;
					pes_offset++;
					pos++;
				}
				else if (pes_offset == 5)
				{
					pes_length += Data[pos];
					pes_buffer[0] = 0x00;
					pes_buffer[1] = 0x00;
					pes_buffer[2] = 0x01;
					pes_buffer[3] = pes_stream_id;
					pes_buffer[4] = pes_length >> 8;
					pes_buffer[5] = pes_length & 0xFF;
					pes_length += 6;
					pes_offset++;
					pos++;
				}
				else
				{
					uint32_t rest = pes_length - pes_offset;
					if (pos + rest <= Length)
					{
						memcpy(pes_buffer + pes_offset, Data + pos, rest);
						pos += rest;
						PesToTs(pes_buffer, pes_length);
						pes_offset = 0;
					}
					else
					{
						memcpy(pes_buffer + pes_offset, Data + pos, Length - pos);
						pes_offset += Length - pos;
						pos += Length - pos;
					}
				}
			}
			else
			{
				printf("id=%02X\n", pes_stream_id);
				usleep(100000);
			}
		}
	}
}

void cPvrReadThread::Action(void)
{
	active = true;

	unsigned char buffer[BUFFSIZE];
	int r;

	if (vbi_fd > 0)
	{
		int vbi_insert = 1;
		if (ioctl(vbi_fd, IVTV_IOC_S_VBI_EMBED, &vbi_insert) < 0)
		{
			log(0, "Error setting vbi embedded mode, %d:%s", errno, strerror(errno));
		}
    struct v4l2_format vbi_fmt;
    vbi_fmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
    vbi_fmt.fmt.sliced.service_set = V4L2_SLICED_TELETEXT_B;
		if (ioctl(vbi_fd, VIDIOC_S_FMT, &vbi_fmt) < 0)
		{
			log(0, "Error setting vbi mode, %d:%s", errno, strerror(errno));
		}
	}

	while (active)
	{
		r = read(video_fd, buffer, BUFFSIZE);
		if (r == -1)
		{
			log(0, "Error reading from video device, %d:%s", errno, strerror(errno));
			break;
		}
		if (r > 0)
		{
			ParseProgramStream(buffer, r);
		}
	}
}


int IOCTL(int fd, int cmd, void * data)
{
	int result;
	
	do 
		result = ioctl(fd, cmd, data);
	while (result == -1L && errno == EINTR);
	return result;
}

cPvrDevice * PvrDevices[kMaxPvrDevices];

cPvrDevice::cPvrDevice(int DeviceNumber)
:	number(DeviceNumber),
	video_fd(-1),
	radio_fd(-1),
	vbi_fd(-1),
	tunerInput(-1),
	frequency(-1),
	vpid(-1),
	apid(-1),
	tpid(-1),
	lastInput(-1),
	dvrOpen(false),
	dvrOpenTime(0),
	delivered(false)
{
	char devName[64];
	struct v4l2_input input;
	int i;

	sprintf(devName, "/dev/video%d", DeviceNumber);
    video_fd = open(devName, O_RDWR);
	if (video_fd < 0)
	{
		log(0, "Error opening video device %s: %s", devName, strerror(errno));
	}

	for (i = 0;; i++)
	{
		memset(&input, 0, sizeof(input));
		input.index = i;
		if (IOCTL(video_fd, VIDIOC_ENUMINPUT, &input) == -1)
			break;
		log(3, "Detected input %d: %s", i, input.name);
		if (strncmp((const char *) input.name, "Tuner", 5) == 0)
		{
			tunerInput = i;
			break;
		}
	}
	if (tunerInput == -1)
	{
		log(0, "Error: device has no tuner");
	}
	sprintf(devName, "/dev/vbi%d", DeviceNumber);
    vbi_fd = open(devName, O_RDONLY);
	if (vbi_fd < 0)
	{
		log(0, "Error opening vbi device %s: %s", devName, strerror(errno));
	}
	tsBuffer = new cRingBufferLinear(MEGABYTE(3), TS_SIZE, false, "PVRTS");
	tsBuffer->SetTimeouts(100, 100);
	readThread = new cPvrReadThread(video_fd, vbi_fd, tsBuffer, &mutex);
	SetVideoNorm(videoNormPAL);
	SetCodec();
	SetVideoSize(720, 576);
	SetInput(tunerInput);
	SetPicture(PvrSetup.Brightness, PvrSetup.Contrast, PvrSetup.Saturation, PvrSetup.Hue);
	SetVolume(PvrSetup.AudioVolume);
}

cPvrDevice::~cPvrDevice()
{
	delete readThread;
	delete tsBuffer;
	close(vbi_fd);
	close(radio_fd);
	close(video_fd);
}

bool cPvrDevice::Probe(int DeviceNumber)
{
	char fileName[256];
	char tempLine[256];
	FILE * fp;
	std::string line;
	bool found;

	sprintf(fileName, "/proc/video/dev/video%d", DeviceNumber);
	fp = fopen(fileName, "r");
	if (fp != NULL)
	{
		found = false;
		while (fgets(tempLine, sizeof(tempLine), fp))
		{
			line = tempLine;
			if (line.find("name") == 0)
			{
				if (line.find("Vanilla iTVC15 card") != std::string::npos)
				{
					found = true;
				}
        else if (line.find("ivtv") != std::string::npos && line.find("encoder MPEG") != std::string::npos)
        {
          found = true;
        }
			}
			else if (line.find("type") == 0)
			{
				if (line.find("VID_TYPE_CAPTURE") == std::string::npos)
				{
					found = false;
				}
				if (line.find("VID_TYPE_TUNER") == std::string::npos)
				{
					found = false;
				}
				if (line.find("VID_TYPE_MPEG_ENCODER") == std::string::npos)
				{
					found = false;
				}
			}
		}
		fclose(fp);
		return found;
	}

	sprintf(fileName, "/sys/class/video4linux/video%d/name", DeviceNumber);
	fp = fopen(fileName, "r");
	if (fp != NULL)
	{
		found = false;
		while (fgets(tempLine, sizeof(tempLine), fp))
		{
			line = tempLine;
			if (line.find("Vanilla iTVC15 card") != std::string::npos)
			{
				found = true;
			}
      else if (line.find("ivtv") == 0 && line.find("encoder MPEG") != std::string::npos)
			{
				found = true;
			}
		}
		fclose(fp);
		return found;
	}
	return false;
}

bool cPvrDevice::Initialize(void)
{
	int found = 0;
	int i;
	for (i = 0; i < kMaxPvrDevices; i++)
	{
		PvrDevices[i] = NULL;
		if (Probe(i))
		{
			PvrDevices[i] = new cPvrDevice(i);
			found++;
		}
	}
	if (found > 0)
		log(1, "found %d PVR device%s", found, found > 1 ? "s" : "");
	else
		log(1, "no PVR device found");
	return found > 0;
}

void cPvrDevice::ReInitAll(void)
{
	int i;
	for (i = 0; i < kMaxPvrDevices; i++)
	{
		if (PvrDevices[i])
		{
			PvrDevices[i]->ReInit();
		}
	}
}

int cPvrDevice::Count()
{
	int count = 0;
	int i;
	for (i = 0; i < kMaxPvrDevices; i++)
	{
		if (PvrDevices[i])
			count++;
	}
	return count;
}

cPvrDevice * cPvrDevice::Get(int index)
{
	int count = 0;
	int i;
	for (i = 0; i < kMaxPvrDevices; i++)
	{
		if (PvrDevices[i])
		{
			if (count == index)
				return PvrDevices[i];
			count++;
		}
	}
	return NULL;
}

void cPvrDevice::ReInit(void)
{
	SetVideoNorm(lastNorm);
	SetCodec();
	SetVideoSize(720, 576);
	SetInput(lastInput);
	SetPicture(PvrSetup.Brightness, PvrSetup.Contrast, PvrSetup.Saturation, PvrSetup.Hue);
	SetVolume(PvrSetup.AudioVolume);
}

bool cPvrDevice::Tune(int freq)
{
	struct v4l2_frequency vf;

	memset(&vf, 0, sizeof(vf));
	vf.tuner = 0;
	vf.type = V4L2_TUNER_ANALOG_TV;
	vf.frequency = (int) ((double) freq * 16.0 / 1000.0 + 0.5);

	if (IOCTL(video_fd, VIDIOC_S_FREQUENCY, &vf) != 0)
	{
		log(0, "Error tuning to %d: %s", freq, strerror(errno));
		return false;
	}
	frequency = freq;
	return true;
}

bool cPvrDevice::SetInput(int input)
{
	if (IOCTL(video_fd, VIDIOC_S_INPUT, &input))
	{
		log(0, "VIDIOC_S_INPUT failed, %d:%s", errno, strerror(errno));
		return false;
	}
	return true;
}

bool cPvrDevice::SetVideoNorm(int norm)
{
	v4l2_std_id std;

	switch (norm)
	{
		case videoNormPAL:
			std = V4L2_STD_PAL;
			break;
		case videoNormNTSC:
			std = V4L2_STD_NTSC;
			break;
		case videoNormSECAM:
			std = V4L2_STD_SECAM;
			break;
	}

	if (IOCTL(video_fd, VIDIOC_S_STD, &std) != 0)
	{
		log(0, "VIDIOC_S_STD failed, %d:%s", errno, strerror(errno));
		return false;
	}
	return true;
}

bool cPvrDevice::SetVideoSize(int width, int height)
{
	struct v4l2_format vfmt;

	vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (IOCTL(video_fd, VIDIOC_G_FMT, &vfmt) != 0)
	{
		log(0, "VIDIOC_G_FMT failed, %d:%s", errno, strerror(errno));
		return false;
	}
	vfmt.fmt.pix.width = width;
	vfmt.fmt.pix.height = height;
	if (IOCTL(video_fd, VIDIOC_S_FMT, &vfmt) != 0)
	{
		log(0, "VIDIOC_S_FMT failed, %d:%s", errno, strerror(errno));
		return false;
	}
	return true;
}

bool cPvrDevice::SetCodec(void)
{
	struct ivtv_ioctl_codec codec;

	if (IOCTL(video_fd, IVTV_IOC_G_CODEC, &codec) != 0)
	{
		log(0, "IVTV_IOC_G_CODEC failed, %d:%s", errno, strerror(errno));
		return false;
	}
	codec.stream_type = IVTV_STREAM_PS; // IVTV_STREAM_DVD_S2
	codec.aspect = PvrSetup.AspectRatio + 1;
	codec.pulldown = PvrSetup.Pulldown;
	codec.bitrate = PvrSetup.VideoBitrate * 1000;
	codec.bitrate_peak = 15000000;
	codec.bitrate_mode = bitrateCBR;

	codec.framespergop = PvrSetup.FramesPerGop; // 15 for NTSC
	codec.bframes = PvrSetup.BFrames + 1;
	codec.gop_closure = PvrSetup.GopClosure;

	codec.dnr_mode     = PvrSetup.DnrMode;
	codec.dnr_type     = PvrSetup.DnrType;
	codec.dnr_spatial  = PvrSetup.DnrSpatial;
	codec.dnr_temporal = PvrSetup.DnrTemporal;

	codec.audio_bitmask = 0x0009 | ((PvrSetup.AudioBitrate + 1) << 4);
	codec.framerate = 1;

	if (IOCTL(video_fd, IVTV_IOC_S_CODEC, &codec) != 0)
	{
		log(0, "IVTV_IOC_S_CODEC failed, %d:%s", errno, strerror(errno));
		return false;
	}
	return true;
}

bool cPvrDevice::SetPicture(int brightness, int contrast, int saturation, int hue)
{
	struct v4l2_control ctrl;

	ctrl.id = V4L2_CID_BRIGHTNESS;
	ctrl.value = (int) (brightness * 7.5 + 0.5);
	if (IOCTL(video_fd, VIDIOC_S_CTRL, &ctrl) != 0)
	{
		log(0, "VIDIOC_S_CTRL brightness failed, %d:%s", errno, strerror(errno));
		return false;
	}

	ctrl.id = V4L2_CID_CONTRAST;
	ctrl.value = (int) (contrast * 3.76 + 0.5);
	if (IOCTL(video_fd, VIDIOC_S_CTRL, &ctrl) != 0)
	{
		log(0, "VIDIOC_S_CTRL contrast failed, %d:%s", errno, strerror(errno));
		return false;
	}

	ctrl.id = V4L2_CID_SATURATION;
	ctrl.value = (int) (saturation * 3.76 + 0.5);
	if (IOCTL(video_fd, VIDIOC_S_CTRL, &ctrl) != 0)
	{
		log(0, "VIDIOC_S_CTRL saturation failed, %d:%s", errno, strerror(errno));
		return false;
	}

	ctrl.id = V4L2_CID_HUE;
	ctrl.value = (int) (hue * 7.5 + 0.5) - 128;
	if (IOCTL(video_fd, VIDIOC_S_CTRL, &ctrl) != 0)
	{
		log(0, "VIDIOC_S_CTRL hue failed, %d:%s", errno, strerror(errno));
		return false;
	}
	return true;
}

bool cPvrDevice::SetVolume(int volume)
{
	struct v4l2_control ctrl;

	ctrl.id = V4L2_CID_AUDIO_VOLUME;
	ctrl.value = (int) (volume * 655.35); // 0..100 -> 0..65535
	if (IOCTL(video_fd, VIDIOC_S_CTRL, &ctrl) != 0)
	{
		log(0, "VIDIOC_S_CTRL volume failed, %d:%s", errno, strerror(errno));
		return false;
	}
	
	ctrl.id = V4L2_CID_AUDIO_MUTE;
	ctrl.value = 0;
	if (IOCTL(video_fd, VIDIOC_S_CTRL, &ctrl) != 0)
	{
		log(0, "VIDIOC_S_CTRL mute failed, %d:%s", errno, strerror(errno));
		return false;
	}
	return true;
}

bool cPvrDevice::SetChannelDevice(const cChannel * Channel, bool LiveView)
{
	log(3, "cPvrDevice::SetChannelDevice %d %s %d %d", Channel->Number(), Channel->Name(), Channel->Frequency(), LiveView);
	if (dvrOpen && Channel->Frequency() == frequency)
		return true;

	mutex.Lock();
	tsBuffer->Clear();
	mutex.Unlock();

	int input;
	int norm;

	vpid = Channel->Vpid();
	apid = Channel->Apid(0);
	tpid = Channel->Tpid();

	input = Channel->CoderateH();
	norm = Channel->Inversion();
	if (norm < 0 || norm > 2)
		norm = videoNormPAL;

	if (norm != lastNorm)
	{
		if (SetVideoNorm(norm) == false)
			return false;
		lastNorm = norm;
	}

	if (Channel->Frequency() > 10000) // use tuner
	{
		if (tunerInput != -1)
		{
			if (tunerInput != lastInput)
			{
				if (SetInput(tunerInput) == false)
					return false;
				lastInput = tunerInput;
			}
		}
		else
		{
			if (input != lastInput)
			{
				if (SetInput(input) == false)
					return false;
				lastInput = input;
			}
		}
		if (vpid == 0)
		{
			if (radio_fd < 0)
			{
				char devName[64];

				sprintf(devName, "/dev/radio%d", number);
				radio_fd = open(devName, O_RDONLY);
				if (radio_fd < 0)
				{
					log(0, "Error opening radio device %s: %s", devName, strerror(errno));
				}
			}
		}
		else
		{
			if (radio_fd >= 0)
			{
				close(radio_fd);
				radio_fd = -1;
			}
		}
		if (Tune(Channel->Frequency()) == false)
		{
			return false;
		}
	}
	else // don't use tuner
	{
		if (radio_fd >= 0)
		{
			close(radio_fd);
			radio_fd = -1;
		}
		if (input != lastInput)
		{
			if (SetInput(input) == false)
				return false;
			lastInput = input;
		}
	}

	return true;
}

bool cPvrDevice::SetPid(cPidHandle * Handle, int Type, bool On)
{
	log(3, "cPvrDevice::SetPid %d %d %d", Handle->pid, Type, On);
	return true;
}

bool cPvrDevice::OpenDvr(void)
{
	log(3, "cPvrDevice::OpenDvr");
	delivered = false;
	dvrOpen = true;
	dvrOpenTime = cTimeMs::Now();
	return true;
}

void cPvrDevice::CloseDvr(void)
{
	log(3, "cPvrDevice::CloseDvr");
	dvrOpenTime = 0;
	dvrOpen = false;
}

bool cPvrDevice::GetTSPacket(uchar *&Data)
{
	if (dvrOpenTime > 0)
	{
		if (dvrOpenTime + PvrSetup.WaitAfterTuning > cTimeMs::Now())
		{
			cCondWait::SleepMs(100);
			Data = NULL;
			return true;
		}
		else
			dvrOpenTime = 0;
	}

	int Count = 0;
	if (delivered)
	{
		tsBuffer->Del(TS_SIZE);
		delivered = false;
	}
	uchar *p = tsBuffer->Get(Count);
	if (p && Count >= TS_SIZE)
	{
		if (*p != TS_SYNC_BYTE)
		{
			for (int i = 1; i < Count; i++)
			{
				if (p[i] == TS_SYNC_BYTE)
				{
					Count = i;
					break;
				}
			}
			tsBuffer->Del(Count);
			log(0, "ERROR: skipped %d bytes to sync on TS packet\n", Count);
			return false;
		}
		delivered = true;
		Data = p;
		return true;
	}
	Data = NULL;
	return true;
}

int cPvrDevice::ProvidesCa(const cChannel *Channel) const
{
	log(3, "cPvrDevice::ProvidesCa %d %s %d", Channel->Number(), Channel->Name(), Channel->Ca());
	return Channel->Ca() == kPvrCa;
}

bool cPvrDevice::ProvidesSource(int Source) const
{
	log(3, "cPvrDevice::ProvidesSource %s", (const char *) cSource::ToString(Source));
	return Source == cSource::FromString(kPvrSource);
}

bool cPvrDevice::ProvidesTransponder(const cChannel * Channel) const
{
	log(3, "cPvrDevice::ProvidesTransponder %d %s %d", Channel->Number(), Channel->Name(), Channel->Frequency());
	return false;
}

bool cPvrDevice::ProvidesChannel(const cChannel * Channel, int Priority, bool * NeedsDetachReceivers) const
{
	log(3, "cPvrDevice::ProvidesChannel %d %s %d %d %08X", Channel->Number(), Channel->Name(), Channel->Frequency(), Priority, NeedsDetachReceivers);

	bool result = false;
	bool hasPriority = Priority < 0 || Priority > this->Priority();
	bool needsDetachReceivers = false;

	if (ProvidesSource(Channel->Source()) && ProvidesCa(Channel))
	{
		result = hasPriority;
		if (Priority >= 0 && Receiving(true))
		{
			if (frequency == Channel->Frequency())
			{
				needsDetachReceivers = false;
				result = true;
			}
			else
			{
				needsDetachReceivers = true;
			}
		}
	}
	if (NeedsDetachReceivers)
		*NeedsDetachReceivers = needsDetachReceivers;
	return result;
}

