1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00
vdr/dvbapi.c

2098 lines
57 KiB
C
Raw Normal View History

2000-02-19 13:36:48 +01:00
/*
* dvbapi.c: Interface to the DVB driver
*
2000-04-24 09:46:05 +02:00
* See the main source file 'vdr.c' for copyright information and
2000-02-19 13:36:48 +01:00
* how to reach the author.
*
2000-12-09 11:13:00 +01:00
* $Id: dvbapi.c 1.42 2000/12/09 11:04:07 kls Exp $
2000-02-19 13:36:48 +01:00
*/
#include "dvbapi.h"
#include <errno.h>
2000-02-19 13:36:48 +01:00
#include <fcntl.h>
2000-09-17 11:53:35 +02:00
extern "C" {
#include <jpeglib.h>
}
#include <stdlib.h>
2000-02-19 13:36:48 +01:00
#include <sys/ioctl.h>
2000-09-17 11:53:35 +02:00
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
2000-02-19 13:36:48 +01:00
#include <unistd.h>
2000-10-08 16:18:23 +02:00
#include "config.h"
2000-02-19 13:36:48 +01:00
#include "interface.h"
#include "tools.h"
#include "videodir.h"
2000-02-19 13:36:48 +01:00
#define VIDEODEVICE "/dev/video"
2000-10-29 13:17:22 +01:00
#define VBIDEVICE "/dev/vbi"
2000-02-19 13:36:48 +01:00
// The size of the array used to buffer video data:
#define VIDEOBUFSIZE (1024*1024)
2000-02-19 13:36:48 +01:00
// The minimum amount of video data necessary to identify frames
// (must be smaller than VIDEOBUFSIZE!):
2000-12-08 16:23:32 +01:00
#define MINVIDEODATA (256*1024) // just a safe guess (max. size of any frame block, plus some safety)
// The maximum time the buffer is allowed to write data to disk when recording:
#define MAXRECORDWRITETIME 50 // ms
// Picture types:
#define NO_PICTURE 0
#define I_FRAME 1
#define P_FRAME 2
#define B_FRAME 3
2000-12-08 16:23:32 +01:00
// Start codes:
#define SC_PICTURE 0x00
#define SC_BLOCK 0xBA
#define SC_AUDIO 0xC0
#define FRAMESPERSEC 25
// The maximum file size is limited by the range that can be covered
// with 'int'. 4GB might be possible (if the range is considered
// 'unsigned'), 2GB should be possible (even if the range is considered
// 'signed'), so let's use 1GB for absolute safety (the actual file size
// may be slightly higher because we stop recording only before the next
// 'I' frame, to have a complete Group Of Pictures):
#define MAXVIDEOFILESIZE (1024*1024*1024) // Byte
#define MAXFILESPERRECORDING 255
#define MINFREEDISKSPACE (512) // MB
#define DISKCHECKINTERVAL 100 // seconds
#define INDEXFILESUFFIX "/index.vdr"
#define RESUMEFILESUFFIX "/resume.vdr"
#define RECORDFILESUFFIX "/%03d.vdr"
#define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
// The number of frames to back up when resuming an interrupted replay session:
#define RESUMEBACKUP (10 * FRAMESPERSEC)
typedef unsigned char uchar;
2000-12-08 16:23:32 +01:00
static void SetPlayMode(int VideoDev, int Mode)
{
if (VideoDev >= 0) {
struct video_play_mode pmode;
pmode.mode = Mode;
ioctl(VideoDev, VIDIOCSPLAYMODE, &pmode);
}
}
2000-12-09 11:13:00 +01:00
const char *IndexToStr(int Index, bool WithFrame)
{
static char buffer[16];
int f = (Index % FRAMESPERSEC) + 1;
int s = (Index / FRAMESPERSEC);
int m = s / 60 % 60;
int h = s / 3600;
s %= 60;
snprintf(buffer, sizeof(buffer), WithFrame ? "%d:%02d:%02d.%02d" : "%d:%02d:%02d", h, m, s, f);
return buffer;
}
// --- cResumeFile ------------------------------------------------------------
cResumeFile::cResumeFile(const char *FileName)
{
fileName = new char[strlen(FileName) + strlen(RESUMEFILESUFFIX) + 1];
if (fileName) {
strcpy(fileName, FileName);
strcat(fileName, RESUMEFILESUFFIX);
}
else
esyslog(LOG_ERR, "ERROR: can't allocate memory for resume file name");
}
cResumeFile::~cResumeFile()
{
delete fileName;
}
int cResumeFile::Read(void)
{
int resume = -1;
if (fileName) {
int f = open(fileName, O_RDONLY);
if (f >= 0) {
if (read(f, &resume, sizeof(resume)) != sizeof(resume)) {
resume = -1;
LOG_ERROR_STR(fileName);
}
close(f);
}
else if (errno != ENOENT)
LOG_ERROR_STR(fileName);
}
return resume;
}
bool cResumeFile::Save(int Index)
{
if (fileName) {
int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP);
if (f >= 0) {
if (write(f, &Index, sizeof(Index)) != sizeof(Index))
LOG_ERROR_STR(fileName);
close(f);
return true;
}
}
return false;
}
// --- cIndexFile ------------------------------------------------------------
class cIndexFile {
private:
struct tIndex { int offset; uchar type; uchar number; short reserved; };
int f;
char *fileName, *pFileExt;
int size, last;
tIndex *index;
cResumeFile resumeFile;
bool CatchUp(void);
public:
cIndexFile(const char *FileName, bool Record = false);
~cIndexFile();
void Write(uchar PictureType, uchar FileNumber, int FileOffset);
bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL);
int GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length = NULL);
int Get(uchar FileNumber, int FileOffset);
int Last(void) { return last; }
int GetResume(void) { return resumeFile.Read(); }
bool StoreResume(int Index) { return resumeFile.Save(Index); }
};
cIndexFile::cIndexFile(const char *FileName, bool Record)
:resumeFile(FileName)
2000-02-19 13:36:48 +01:00
{
f = -1;
fileName = pFileExt = NULL;
size = 0;
last = -1;
index = NULL;
if (FileName) {
fileName = new char[strlen(FileName) + strlen(INDEXFILESUFFIX) + 1];
if (fileName) {
strcpy(fileName, FileName);
pFileExt = fileName + strlen(fileName);
strcpy(pFileExt, INDEXFILESUFFIX);
int delta = 0;
if (access(fileName, R_OK) == 0) {
struct stat buf;
if (stat(fileName, &buf) == 0) {
delta = buf.st_size % sizeof(tIndex);
if (delta) {
delta = sizeof(tIndex) - delta;
esyslog(LOG_ERR, "ERROR: invalid file size (%d) in '%s'", buf.st_size, fileName);
}
last = (buf.st_size + delta) / sizeof(tIndex) - 1;
if (!Record && last >= 0) {
size = last + 1;
index = new tIndex[size];
if (index) {
f = open(fileName, O_RDONLY);
if (f >= 0) {
if ((int)read(f, index, buf.st_size) != buf.st_size) {
esyslog(LOG_ERR, "ERROR: can't read from file '%s'", fileName);
delete index;
index = NULL;
close(f);
f = -1;
}
// we don't close f here, see CatchUp()!
}
else
LOG_ERROR_STR(fileName);
}
else
esyslog(LOG_ERR, "ERROR: can't allocate %d bytes for index '%s'", size * sizeof(tIndex), fileName);
}
}
else
LOG_ERROR;
}
if (Record) {
if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP)) >= 0) {
if (delta) {
esyslog(LOG_ERR, "ERROR: padding index file with %d '0' bytes", delta);
while (delta--)
writechar(f, 0);
}
}
else
LOG_ERROR_STR(fileName);
delete fileName;
fileName = pFileExt = NULL;
}
}
2000-02-19 13:36:48 +01:00
else
esyslog(LOG_ERR, "ERROR: can't copy file name '%s'", FileName);
}
}
cIndexFile::~cIndexFile()
{
if (f >= 0)
close(f);
delete fileName;
}
bool cIndexFile::CatchUp(void)
{
if (index && f >= 0) {
struct stat buf;
if (fstat(f, &buf) == 0) {
int newLast = buf.st_size / sizeof(tIndex) - 1;
if (newLast > last) {
if (size <= newLast) {
size *= 2;
if (size <= newLast)
size = newLast + 1;
}
index = (tIndex *)realloc(index, size * sizeof(tIndex));
if (index) {
int offset = (last + 1) * sizeof(tIndex);
int delta = (newLast - last) * sizeof(tIndex);
if (lseek(f, offset, SEEK_SET) == offset) {
if (read(f, &index[last + 1], delta) != delta) {
esyslog(LOG_ERR, "ERROR: can't read from index");
delete index;
index = NULL;
close(f);
f = -1;
}
last = newLast;
return true;
}
else
LOG_ERROR;
}
else
esyslog(LOG_ERR, "ERROR: can't realloc() index");
}
}
else
LOG_ERROR;
}
return false;
}
void cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset)
{
if (f >= 0) {
tIndex i = { FileOffset, PictureType, FileNumber, 0 };
if (write(f, &i, sizeof(i)) != sizeof(i)) {
esyslog(LOG_ERR, "ERROR: can't write to index file");
close(f);
f = -1;
return;
}
last++;
}
}
bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType)
{
if (index) {
CatchUp();
if (Index >= 0 && Index <= last) {
*FileNumber = index[Index].number;
*FileOffset = index[Index].offset;
if (PictureType)
*PictureType = index[Index].type;
2000-02-19 13:36:48 +01:00
return true;
}
2000-02-19 13:36:48 +01:00
}
return false;
}
int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length)
{
if (index) {
if (Forward)
CatchUp();
int d = Forward ? 1 : -1;
for (;;) {
Index += d;
if (Index >= 0 && Index <= last - 100) { // '- 100': need to stay off the end!
if (index[Index].type == I_FRAME) {
*FileNumber = index[Index].number;
*FileOffset = index[Index].offset;
if (Length) {
// all recordings end with a non-I_FRAME, so the following should be safe:
int fn = index[Index + 1].number;
int fo = index[Index + 1].offset;
if (fn == *FileNumber)
*Length = fo - *FileOffset;
else {
esyslog(LOG_ERR, "ERROR: 'I' frame at end of file #%d", *FileNumber);
*Length = -1;
}
}
return Index;
}
}
else
break;
}
}
return -1;
}
int cIndexFile::Get(uchar FileNumber, int FileOffset)
{
if (index) {
CatchUp();
//TODO implement binary search!
int i;
for (i = 0; i < last; i++) {
if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset)
break;
}
return i;
}
return -1;
}
2000-02-19 13:36:48 +01:00
// --- cRingBuffer -----------------------------------------------------------
/* cRingBuffer reads data from an input file, stores it in a buffer and writes
it to an output file upon request. The Read() and Write() functions should
be called only when the associated file is ready to provide or receive data
(use the 'select()' function to determine that), and the files should be
opened in non-blocking mode.
The '...Limit' parameters define safety limits. If they are exceeded a log entry
will be made.
*/
class cRingBuffer {
private:
uchar *buffer;
int size, head, tail, freeLimit, availLimit;
int countLimit, countOverflow;
int minFree;
bool eof;
int *inFile, *outFile;
protected:
int Free(void) { return ((tail >= head) ? size + head - tail : head - tail) - 1; }
2000-11-19 16:49:14 +01:00
public:
int Available(void) { return (tail >= head) ? tail - head : size - head + tail; }
2000-11-19 16:49:14 +01:00
protected:
int Readable(void) { return (tail >= head) ? size - tail - (head ? 0 : 1) : head - tail - 1; } // keep a 1 byte gap!
2000-07-30 14:34:07 +02:00
int Writeable(void) { return (tail >= head) ? tail - head : size - head; }
int Byte(int Offset);
2000-12-08 16:23:32 +01:00
void Set(int Offset, int Length, int Value);
public:
cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit = 0, int AvailLimit = 0);
virtual ~cRingBuffer();
virtual int Read(int Max = -1);
virtual int Write(int Max = -1);
bool EndOfFile(void) { return eof; }
bool Empty(void) { return Available() == 0; }
void Clear(void) { head = tail = 0; }
void Skip(int n);
};
cRingBuffer::cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit, int AvailLimit)
{
inFile = InFile;
outFile = OutFile;
size = Size;
Clear();
freeLimit = FreeLimit;
availLimit = AvailLimit;
eof = false;
countLimit = countOverflow = 0;
minFree = size - 1;
buffer = new uchar[size];
if (!buffer)
esyslog(LOG_ERR, "ERROR: can't allocate ring buffer (size=%d)", size);
}
cRingBuffer::~cRingBuffer()
2000-02-19 13:36:48 +01:00
{
dsyslog(LOG_INFO, "buffer stats: %d free, %d overflows, limit exceeded %d times", minFree, countOverflow, countLimit);
delete buffer;
}
int cRingBuffer::Byte(int Offset)
{
if (buffer && Offset < Available()) {
Offset += head;
if (Offset >= size)
Offset -= size;
return buffer[Offset];
}
return -1;
}
2000-12-08 16:23:32 +01:00
void cRingBuffer::Set(int Offset, int Length, int Value)
{
2000-12-08 16:23:32 +01:00
if (buffer && Offset + Length <= Available() ) {
Offset += head;
while (Length--) {
if (Offset >= size)
Offset -= size;
buffer[Offset] = Value;
Offset++;
}
}
2000-12-08 16:23:32 +01:00
}
void cRingBuffer::Skip(int n)
{
if (n > 0) {
if (head < tail) {
head += n;
if (head > tail)
head = tail;
}
else if (head > tail) {
head += n;
if (head >= size)
head -= size;
if (head > tail)
head = tail;
}
}
}
int cRingBuffer::Read(int Max)
{
if (buffer) {
eof = false;
int free = Free();
if (free < minFree)
minFree = free;
if (freeLimit) {
if (free == 0) {
esyslog(LOG_ERR, "ERROR: buffer overflow (size=%d)", size);
countOverflow++;
}
else if (free < freeLimit) {
dsyslog(LOG_INFO, "free buffer space dipped into limit (%d < %d)", free, freeLimit);
countLimit++;
}
}
if (free == 0)
return 0; // the buffer is full
int readin = 0;
for (int i = 0; i < 2; i++) {
// If we read in exactly as many bytes as are immediately
// "readable" we have to do it again, because that means we
// were at the very end of the physical buffer and possibly only
// read in very few bytes.
int immediate = Readable();
int n = immediate;
if (Max > 0 && n > Max)
n = Max;
if (n > 0) {
int r = read(*inFile, buffer + tail, n);
if (r > 0) {
readin += r;
tail += r;
if (tail > size)
esyslog(LOG_ERR, "ERROR: ooops: buffer tail (%d) exceeds size (%d)", tail, size);
if (tail >= size)
tail = 0;
}
2000-12-08 16:23:32 +01:00
else if (r < 0) {
if (errno != EAGAIN) {
LOG_ERROR;
return -1;
}
}
else
eof = true;
if (r == immediate && Max != immediate && tail == 0)
Max -= immediate;
else
break;
}
}
return readin;
}
return -1;
}
int cRingBuffer::Write(int Max)
{
if (buffer) {
int avail = Available();
if (availLimit) {
//XXX stats???
if (avail == 0)
//XXX esyslog(LOG_ERR, "ERROR: buffer empty!");
{//XXX
esyslog(LOG_ERR, "ERROR: buffer empty! %d", Max);
return Max > 0 ? Max : 0;
}//XXX
else if (avail < availLimit)
;//XXX dsyslog(LOG_INFO, "available buffer data dipped into limit (%d < %d)", avail, availLimit);
}
if (avail == 0)
return 0; // the buffer is empty
int n = Writeable();
if (Max > 0 && n > Max)
n = Max;
int w = write(*outFile, buffer + head, n);
if (w > 0) {
head += w;
if (head > size)
esyslog(LOG_ERR, "ERROR: ooops: buffer head (%d) exceeds size (%d)", head, size);
if (head >= size)
head = 0;
}
else if (w < 0) {
if (errno != EAGAIN)
LOG_ERROR;
else
w = 0;
}
return w;
}
return -1;
}
// --- cFileBuffer -----------------------------------------------------------
class cFileBuffer : public cRingBuffer {
protected:
cIndexFile *index;
uchar fileNumber;
char *fileName, *pFileNumber;
2000-12-08 16:23:32 +01:00
int GetStartCode(int Offset) { return (Byte(Offset) == 0x00 && Byte(Offset + 1) == 0x00 && Byte(Offset + 2) == 0x01) ? Byte(Offset + 3) : -1; }
int GetPictureType(int Offset) { return (Byte(Offset + 5) >> 3) & 0x07; }
2000-12-08 16:23:32 +01:00
int FindStartCode(uchar Code, int Offset = 0);
int FindPacketLength(int Offset = 0);
public:
cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0);
virtual ~cFileBuffer();
};
cFileBuffer::cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0)
:cRingBuffer(InFile, OutFile, Size, FreeLimit, AvailLimit)
{
index = NULL;
fileNumber = 0;
// Prepare the file name:
fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN];
if (!fileName) {
esyslog(LOG_ERR, "ERROR: can't copy file name '%s'", fileName);
return;
}
strcpy(fileName, FileName);
pFileNumber = fileName + strlen(fileName);
// Create the index file:
index = new cIndexFile(FileName, Recording);
if (!index)
esyslog(LOG_ERR, "ERROR: can't allocate index");
// let's continue without index, so we'll at least have the recording
}
cFileBuffer::~cFileBuffer()
{
delete index;
delete fileName;
}
2000-12-08 16:23:32 +01:00
int cFileBuffer::FindStartCode(uchar Code, int Offset)
{
2000-12-08 16:23:32 +01:00
// Searches for a start code (beginning at Offset) and returns the number
// of bytes from Offset to the start code.
2000-12-08 16:23:32 +01:00
int n = Available() - Offset;
for (int i = 0; i < n; i++) {
int c = GetStartCode(Offset + i);
if (c == Code)
return i;
if (i > 0 && c == SC_BLOCK)
break; // found another block start while looking for a different code
}
return -1;
}
2000-12-08 16:23:32 +01:00
int cFileBuffer::FindPacketLength(int Offset)
{
2000-12-08 16:23:32 +01:00
// Determines the length of the packet starting at offset.
int n = Available() - Offset;
for (int i = 1; i < n; i++) { // starts at 1 to exclude the current packet start code!
int c = GetStartCode(Offset + i);
if (c >= 0)
return i;
}
2000-12-08 16:23:32 +01:00
return n;
}
// --- cRecordBuffer ---------------------------------------------------------
2000-12-08 16:23:32 +01:00
class cRecordBuffer : public cFileBuffer, public cThread {
private:
uchar pictureType;
int fileSize;
2000-12-08 16:23:32 +01:00
int videoDev;
int recordFile;
2000-12-08 16:23:32 +01:00
bool ok, synced, stop;
time_t lastDiskSpaceCheck;
bool RunningLowOnDiskSpace(void);
int Synchronize(void);
bool NextFile(void);
virtual int Write(int Max = -1);
2000-12-08 16:23:32 +01:00
bool WriteWithTimeout(void);
protected:
virtual void Action(void);
public:
cRecordBuffer(int *InFile, const char *FileName);
virtual ~cRecordBuffer();
};
cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName)
:cFileBuffer(InFile, &recordFile, FileName, true, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0)
{
pictureType = NO_PICTURE;
fileSize = 0;
2000-12-08 16:23:32 +01:00
videoDev = *InFile;
recordFile = -1;
2000-12-08 16:23:32 +01:00
ok = synced = stop = false;
lastDiskSpaceCheck = time(NULL);
if (!fileName)
return;//XXX find a better way???
// Find the highest existing file suffix:
for (;;) {
sprintf(pFileNumber, RECORDFILESUFFIX, ++fileNumber);
if (access(fileName, F_OK) < 0) {
if (errno == ENOENT)
break; // found a non existing file suffix
LOG_ERROR;
return;
}
}
ok = true;
2000-12-08 16:23:32 +01:00
Start();
}
cRecordBuffer::~cRecordBuffer()
{
2000-12-08 16:23:32 +01:00
stop = true;
Cancel(3);
if (recordFile >= 0)
CloseVideoFile(recordFile);
}
2000-12-08 16:23:32 +01:00
void cRecordBuffer::Action(void)
{
dsyslog(LOG_INFO, "recording thread started (pid=%d)", getpid());
time_t t = time(NULL);
for (;;) {
usleep(1); // this keeps the CPU load low
LOCK_THREAD;
int r = Read();
if (r >= 0) {
if (r > 0)
t = time(NULL);
if (!WriteWithTimeout())
break;
}
if (r < 0 || (r == 0 && time(NULL) - t > 3))
esyslog(LOG_ERR, "ERROR: video data stream broken");
}
SetPlayMode(videoDev, VID_PLAY_RESET);
dsyslog(LOG_INFO, "end recording thread");
}
bool cRecordBuffer::RunningLowOnDiskSpace(void)
{
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
uint Free = FreeDiskSpaceMB(fileName);
lastDiskSpaceCheck = time(NULL);
if (Free < MINFREEDISKSPACE) {
dsyslog(LOG_INFO, "low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
return true;
}
}
return false;
}
int cRecordBuffer::Synchronize(void)
{
2000-12-08 16:23:32 +01:00
// Positions to the start of a data block (skipping everything up to
// an I-frame if not synced) and returns the block length.
int Length = 0;
int Skipped = 0;
int s;
2000-12-08 16:23:32 +01:00
while (Available() > MINVIDEODATA && (s = FindStartCode(SC_BLOCK)) >= 0) {
pictureType = NO_PICTURE;
Skipped += s;
2000-12-08 16:23:32 +01:00
Skip(s);
int PictureOffset = FindStartCode(SC_PICTURE);
if (PictureOffset >= 0) {
pictureType = GetPictureType(PictureOffset);
if (I_FRAME <= pictureType && pictureType <= B_FRAME) {
if (!synced && pictureType == I_FRAME) {
synced = true;
Skipped = 0;
}
if (synced && (Length = FindStartCode(SC_BLOCK, PictureOffset)) > 0) {
Length += PictureOffset;
break;
}
Length = 0;
}
else
esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pictureType);
}
2000-12-08 16:23:32 +01:00
else {
if (synced && (Length = FindStartCode(SC_BLOCK, 1)) > 0) {
Length++;
break;
}
}
Skip(1);
}
if (synced && Skipped)
esyslog(LOG_ERR, "ERROR: skipped %d bytes", Skipped);
return Length;
}
bool cRecordBuffer::NextFile(void)
{
if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME
if (fileSize > MAXVIDEOFILESIZE || RunningLowOnDiskSpace()) {
if (CloseVideoFile(recordFile) < 0)
LOG_ERROR;
// don't return 'false', maybe we can still record into the next file
recordFile = -1;
fileNumber++;
if (fileNumber == 0)
esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
fileSize = 0;
}
}
if (recordFile < 0) {
sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
dsyslog(LOG_INFO, "recording to '%s'", fileName);
recordFile = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_NONBLOCK);
if (recordFile < 0) {
LOG_ERROR;
return false;
}
}
return true;
2000-02-19 13:36:48 +01:00
}
int cRecordBuffer::Write(int Max)
2000-02-19 13:36:48 +01:00
{
// This function ignores the incoming 'Max'!
2000-12-08 16:23:32 +01:00
// It tries to write out exactly *one* frame block.
if (!ok)
return -1;
int n = Synchronize();
if (n) {
if (stop && pictureType == I_FRAME) {
ok = false;
2000-09-17 11:53:35 +02:00
return -1; // finish the recording before the next 'I' frame
}
if (NextFile()) {
if (index && pictureType != NO_PICTURE)
index->Write(pictureType, fileNumber, fileSize);
int written = 0;
for (;;) {
int w = cFileBuffer::Write(n);
if (w >= 0) {
fileSize += w;
written += w;
n -= w;
if (n == 0)
return written;
}
else
return w;
}
}
return -1;
}
return 0;
2000-02-19 13:36:48 +01:00
}
2000-12-08 16:23:32 +01:00
bool cRecordBuffer::WriteWithTimeout(void)
2000-02-19 13:36:48 +01:00
{
int t0 = time_ms();
2000-12-08 16:23:32 +01:00
do {
int w = Write();
if (w < 0)
return false;
if (w == 0)
break;
} while (time_ms() - t0 < MAXRECORDWRITETIME);
return true;
}
// --- cReplayBuffer ---------------------------------------------------------
2000-12-08 16:23:32 +01:00
class cReplayBuffer : public cFileBuffer, public cThread {
private:
2000-12-08 16:23:32 +01:00
enum eReplayCmd { rcNone, rcPause, rcPlay, rcForward, rcBackward };
enum eReplayMode { rmPlay, rmFastForward, rmFastRewind, rmSlowRewind };
int fileOffset;
2000-12-08 16:23:32 +01:00
int videoDev;
int replayFile;
eReplayMode mode;
int lastIndex;
2000-07-30 14:34:07 +02:00
int brakeCounter;
2000-12-08 16:23:32 +01:00
eReplayCmd command;
bool active;
bool NextFile(uchar FileNumber = 0, int FileOffset = -1);
void Close(void);
2000-12-08 16:23:32 +01:00
void SetCmd(eReplayCmd Cmd) { LOCK_THREAD; command = Cmd; }
protected:
virtual void Action(void);
public:
cReplayBuffer(int *OutFile, const char *FileName);
2000-09-17 11:53:35 +02:00
virtual ~cReplayBuffer();
virtual int Read(int Max = -1);
virtual int Write(int Max = -1);
void SetMode(eReplayMode Mode);
int Resume(void);
bool Save(void);
2000-12-08 16:23:32 +01:00
void Pause(void) { SetCmd(rcPause); }
void Play(void) { SetCmd(rcPlay); }
void Forward(void) { SetCmd(rcForward); }
void Backward(void) { SetCmd(rcBackward); }
void SkipSeconds(int Seconds);
2000-04-23 15:38:16 +02:00
void GetIndex(int &Current, int &Total);
};
cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName)
:cFileBuffer(&replayFile, OutFile, FileName, false, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
{
fileOffset = 0;
2000-12-08 16:23:32 +01:00
videoDev = *OutFile;
replayFile = -1;
mode = rmPlay;
2000-07-30 14:34:07 +02:00
brakeCounter = 0;
2000-12-08 16:23:32 +01:00
command = rcNone;
lastIndex = -1;
2000-12-08 16:23:32 +01:00
active = false;
if (!fileName)
return;//XXX find a better way???
// All recordings start with '1':
fileNumber = 1; //TODO what if it doesn't start with '1'???
2000-12-08 16:23:32 +01:00
Start();
}
cReplayBuffer::~cReplayBuffer()
{
2000-12-08 16:23:32 +01:00
active = false;
Cancel(3);
Close();
2000-12-08 16:23:32 +01:00
SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
SetPlayMode(videoDev, VID_PLAY_RESET);
}
void cReplayBuffer::Action(void)
{
dsyslog(LOG_INFO, "replay thread started (pid=%d)", getpid());
bool Paused = false;
bool FastForward = false;
bool FastRewind = false;
int ResumeIndex = Resume();
if (ResumeIndex >= 0)
2000-12-09 11:13:00 +01:00
isyslog(LOG_INFO, "resuming replay at index %d (%s)", ResumeIndex, IndexToStr(ResumeIndex, true));
2000-12-08 16:23:32 +01:00
active = true;
for (; active;) {
usleep(1); // this keeps the CPU load low
LOCK_THREAD;
if (command != rcNone) {
switch (command) {
case rcPause: SetPlayMode(videoDev, Paused ? VID_PLAY_NORMAL : VID_PLAY_PAUSE);
Paused = !Paused;
if (FastForward || FastRewind) {
SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
Clear();
}
FastForward = FastRewind = false;
SetMode(rmPlay);
break;
case rcPlay: if (FastForward || FastRewind || Paused) {
SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
SetPlayMode(videoDev, VID_PLAY_NORMAL);
FastForward = FastRewind = Paused = false;
SetMode(rmPlay);
}
break;
case rcForward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
Clear();
FastForward = !FastForward;
FastRewind = false;
if (Paused) {
SetMode(rmPlay);
SetPlayMode(videoDev, FastForward ? VID_PLAY_SLOW_MOTION : VID_PLAY_PAUSE);
}
else {
SetPlayMode(videoDev, VID_PLAY_NORMAL);
SetMode(FastForward ? rmFastForward : rmPlay);
}
break;
case rcBackward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
Clear();
FastRewind = !FastRewind;
FastForward = false;
if (Paused) {
SetMode(FastRewind ? rmSlowRewind : rmPlay);
SetPlayMode(videoDev, FastRewind ? VID_PLAY_NORMAL : VID_PLAY_PAUSE);
}
else {
SetPlayMode(videoDev, VID_PLAY_NORMAL);
SetMode(FastRewind ? rmFastRewind : rmPlay);
}
break;
default: break;
}
command = rcNone;
}
if (Read() < 0 || Write() < 0)
break;
}
Save();
dsyslog(LOG_INFO, "end replaying thread");
2000-02-19 13:36:48 +01:00
}
void cReplayBuffer::Close(void)
2000-02-19 13:36:48 +01:00
{
if (replayFile >= 0) {
if (close(replayFile) < 0)
LOG_ERROR;
replayFile = -1;
fileOffset = 0;
}
}
void cReplayBuffer::SetMode(eReplayMode Mode)
{
mode = Mode;
2000-07-30 14:34:07 +02:00
brakeCounter = 0;
if (mode != rmPlay)
Clear();
}
int cReplayBuffer::Resume(void)
{
if (index) {
int Index = index->GetResume();
if (Index >= 0) {
uchar FileNumber;
int FileOffset;
if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset))
return Index;
}
}
return -1;
}
bool cReplayBuffer::Save(void)
{
if (index) {
int Index = index->Get(fileNumber, fileOffset);
if (Index >= 0) {
Index -= RESUMEBACKUP;
if (Index > 0) {
uchar FileNumber;
int FileOffset;
Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset);
}
else
Index = 0;
if (Index >= 0)
return index->StoreResume(Index);
}
}
2000-02-19 13:36:48 +01:00
return false;
}
void cReplayBuffer::SkipSeconds(int Seconds)
{
2000-12-08 16:23:32 +01:00
LOCK_THREAD;
SetPlayMode(videoDev, VID_PLAY_PAUSE);
SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
SetPlayMode(videoDev, VID_PLAY_NORMAL);
command = rcPlay;
SetMode(rmPlay);
Clear();
if (index && Seconds) {
int Index = index->Get(fileNumber, fileOffset);
if (Index >= 0) {
if (Seconds < 0) {
int sec = index->Last() / FRAMESPERSEC;
if (Seconds < -sec)
Seconds = - sec;
}
Index += Seconds * FRAMESPERSEC;
if (Index < 0)
2000-04-24 13:54:23 +02:00
Index = 1; // not '0', to allow GetNextIFrame() below to work!
uchar FileNumber;
int FileOffset;
if (index->GetNextIFrame(Index, false, &FileNumber, &FileOffset) >= 0)
if ((Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset)) >= 0)
NextFile(FileNumber, FileOffset);
}
}
}
2000-04-23 15:38:16 +02:00
void cReplayBuffer::GetIndex(int &Current, int &Total)
{
if (index) {
2000-12-08 16:23:32 +01:00
LOCK_THREAD;
2000-04-23 15:38:16 +02:00
Current = index->Get(fileNumber, fileOffset);
Total = index->Last();
}
else
Current = Total = -1;
}
bool cReplayBuffer::NextFile(uchar FileNumber, int FileOffset)
{
if (FileNumber > 0) {
Clear();
if (FileNumber != fileNumber) {
Close();
fileNumber = FileNumber;
}
}
if (replayFile >= 0 && EndOfFile()) {
Close();
fileNumber++;
if (fileNumber == 0)
esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
}
if (replayFile < 0) {
sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
if (access(fileName, R_OK) == 0) {
dsyslog(LOG_INFO, "playing '%s'", fileName);
replayFile = open(fileName, O_RDONLY | O_NONBLOCK);
if (replayFile < 0) {
LOG_ERROR;
return false;
}
}
else if (errno != ENOENT)
LOG_ERROR;
}
if (replayFile >= 0) {
if (FileOffset >= 0) {
if ((fileOffset = lseek(replayFile, FileOffset, SEEK_SET)) != FileOffset)
LOG_ERROR;
// don't return 'false', maybe we can still replay the file
}
return true;
}
2000-02-19 13:36:48 +01:00
return false;
}
int cReplayBuffer::Read(int Max = -1)
2000-02-19 13:36:48 +01:00
{
if (mode != rmPlay) {
if (index) {
if (Available())
return 0; // write out the entire block
int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileNumber, fileOffset);
if (Index >= 0) {
uchar FileNumber;
int FileOffset, Length;
2000-07-30 14:34:07 +02:00
if (mode == rmSlowRewind && (brakeCounter++ % 24) != 0) {
// show every I_FRAME 24 times in rmSlowRewind mode to achieve roughly the same speed as in slow forward mode
Index = index->GetNextIFrame(Index, true, &FileNumber, &FileOffset, &Length); // jump ahead one frame
2000-07-30 14:34:07 +02:00
}
Index = index->GetNextIFrame(Index, mode == rmFastForward, &FileNumber, &FileOffset, &Length);
if (Index >= 0) {
if (!NextFile(FileNumber, FileOffset))
return -1;
Max = Length;
}
lastIndex = Index;
}
if (Index < 0) {
// This results in normal replay after a fast rewind.
// After a fast forward it will stop.
// TODO Could we cause it to pause at the last frame?
SetMode(rmPlay);
return 0;
}
}
}
else
lastIndex = -1;
//XXX timeout as in recording???
if (NextFile()) {
int readin = 0;
do {
// If Max is > 0 here we need to make sure we read in the entire block!
int r = cFileBuffer::Read(Max);
if (r >= 0)
readin += r;
else
return -1;
} while (readin < Max && Free() > 0);
2000-12-08 16:23:32 +01:00
if (mode != rmPlay) {
// delete the audio data in modes other than rmPlay:
int AudioOffset = FindStartCode(SC_AUDIO);
if (AudioOffset >= 0) {
int AudioLength = FindPacketLength(AudioOffset);
Set(AudioOffset, AudioLength, 0);
}
}
return readin;
}
if (Available() > 0)
return 0;
return -1;
2000-02-19 13:36:48 +01:00
}
int cReplayBuffer::Write(int Max)
2000-02-19 13:36:48 +01:00
{
int Written = 0;
if (Max) {
int w;
do {
w = cFileBuffer::Write(Max);
if (w >= 0) {
fileOffset += w;
Written += w;
if (Max < 0)
break;
Max -= w;
}
else
return w;
2000-12-08 16:23:32 +01:00
} while (Max > 0); // we MUST write this entire frame block
}
return Written;
2000-02-19 13:36:48 +01:00
}
2000-11-19 16:49:14 +01:00
// --- cTransferBuffer -------------------------------------------------------
class cTransferBuffer : public cThread {
private:
bool active;
int fromDevice, toDevice;
protected:
virtual void Action(void);
public:
cTransferBuffer(int FromDevice, int ToDevice);
virtual ~cTransferBuffer();
};
cTransferBuffer::cTransferBuffer(int FromDevice, int ToDevice)
{
fromDevice = FromDevice;
toDevice = ToDevice;
2000-12-08 16:23:32 +01:00
active = false;
2000-11-19 16:49:14 +01:00
Start();
}
cTransferBuffer::~cTransferBuffer()
{
2000-12-08 16:23:32 +01:00
active = false;
Cancel(3);
SetPlayMode(fromDevice, VID_PLAY_RESET);
SetPlayMode(toDevice, VID_PLAY_RESET);
2000-11-19 16:49:14 +01:00
}
void cTransferBuffer::Action(void)
{
dsyslog(LOG_INFO, "data transfer thread started (pid=%d)", getpid());
2000-12-08 16:23:32 +01:00
cRingBuffer Buffer(&fromDevice, &toDevice, VIDEOBUFSIZE, 0, 0);
active = true;
while (active && Buffer.Available() < 100000) { // need to give the read buffer a head start
Buffer.Read(); // initializes fromDevice for reading
2000-11-19 16:49:14 +01:00
usleep(1); // this keeps the CPU load low
}
2000-12-08 16:23:32 +01:00
for (; active;) {
if (Buffer.Read() < 0 || Buffer.Write() < 0)
break;
usleep(1); // this keeps the CPU load low
}
2000-11-19 16:49:14 +01:00
dsyslog(LOG_INFO, "data transfer thread stopped (pid=%d)", getpid());
}
// --- cDvbApi ---------------------------------------------------------------
2000-02-19 13:36:48 +01:00
2000-05-01 16:29:46 +02:00
int cDvbApi::NumDvbApis = 0;
cDvbApi *cDvbApi::dvbApi[MAXDVBAPI] = { NULL };
cDvbApi *cDvbApi::PrimaryDvbApi = NULL;
2000-10-29 13:17:22 +01:00
cDvbApi::cDvbApi(const char *VideoFileName, const char *VbiFileName)
{
2000-10-29 13:17:22 +01:00
siProcessor = NULL;
2000-12-08 16:23:32 +01:00
recordBuffer = NULL;
replayBuffer = NULL;
2000-11-19 16:49:14 +01:00
transferBuffer = NULL;
transferringFromDvbApi = NULL;
2000-12-08 16:23:32 +01:00
ca = 0;
priority = -1;
2000-10-29 13:17:22 +01:00
videoDev = open(VideoFileName, O_RDWR | O_NONBLOCK);
if (videoDev >= 0) {
siProcessor = new cSIProcessor(VbiFileName);
if (!NumDvbApis) // only the first one shall set the system time
siProcessor->SetUseTSTime(Setup.SetSystemTime);
siProcessor->AddFilter(0x14, 0x70); // TDT
siProcessor->AddFilter(0x14, 0x73); // TOT
siProcessor->AddFilter(0x12, 0x4e); // event info, actual TS, present/following
siProcessor->AddFilter(0x12, 0x4f); // event info, other TS, present/following
siProcessor->AddFilter(0x12, 0x50); // event info, actual TS, schedule
siProcessor->AddFilter(0x12, 0x60); // event info, other TS, schedule
siProcessor->Start();
}
else
LOG_ERROR_STR(VideoFileName);
cols = rows = 0;
2000-09-17 13:47:06 +02:00
ovlGeoSet = ovlStat = ovlFbSet = false;
ovlBrightness = ovlColour = ovlHue = ovlContrast = 32768;
ovlClipCount = 0;
2000-07-15 12:39:20 +02:00
#if defined(DEBUG_OSD) || defined(REMOTE_KBD)
initscr();
2000-09-17 11:53:35 +02:00
keypad(stdscr, true);
nonl();
cbreak();
noecho();
timeout(10);
#endif
#if defined(DEBUG_OSD)
memset(&colorPairs, 0, sizeof(colorPairs));
start_color();
2000-09-17 11:53:35 +02:00
leaveok(stdscr, true);
2000-04-22 13:51:48 +02:00
window = NULL;
2000-10-03 10:34:48 +02:00
#else
osd = NULL;
#endif
2000-11-05 18:39:17 +01:00
currentChannel = 1;
}
cDvbApi::~cDvbApi()
{
2000-05-01 16:29:46 +02:00
if (videoDev >= 0) {
2000-10-29 13:17:22 +01:00
delete siProcessor;
2000-05-01 16:29:46 +02:00
Close();
2000-12-08 16:23:32 +01:00
StopReplay();
2000-05-01 16:29:46 +02:00
StopRecord();
2000-11-19 16:49:14 +01:00
StopTransfer();
2000-09-17 13:47:06 +02:00
OvlO(false); //Overlay off!
//XXX the following call sometimes causes a segfault - driver problem?
2000-05-01 16:29:46 +02:00
close(videoDev);
}
2000-07-15 12:39:20 +02:00
#if defined(DEBUG_OSD) || defined(REMOTE_KBD)
2000-05-01 16:29:46 +02:00
endwin();
#endif
}
2000-09-10 10:51:58 +02:00
bool cDvbApi::SetPrimaryDvbApi(int n)
{
n--;
if (0 <= n && n < NumDvbApis && dvbApi[n]) {
isyslog(LOG_INFO, "setting primary DVB to %d", n + 1);
PrimaryDvbApi = dvbApi[n];
return true;
}
esyslog(LOG_ERR, "invalid DVB interface: %d", n + 1);
return false;
}
cDvbApi *cDvbApi::GetDvbApi(int Ca, int Priority)
2000-05-01 16:29:46 +02:00
{
2000-09-10 10:51:58 +02:00
cDvbApi *d = NULL;
int index = Ca - 1;
2000-05-01 16:29:46 +02:00
for (int i = MAXDVBAPI; --i >= 0; ) {
if (dvbApi[i]) {
if (i == index) { // means we need exactly _this_ device
2000-09-10 10:51:58 +02:00
d = dvbApi[i];
break;
}
else if (Ca == 0) { // means any device would be acceptable
if (!d || !dvbApi[i]->Recording() || (d->Recording() && d->Priority() > dvbApi[i]->Priority()))
d = dvbApi[i];
if (d && d != PrimaryDvbApi && !d->Recording()) // avoids the PrimaryDvbApi if possible
2000-09-10 10:51:58 +02:00
break;
}
2000-05-01 16:29:46 +02:00
}
}
return (d && (!d->Recording() || d->Priority() < Priority || (!d->Ca() && Ca))) ? d : NULL;
2000-05-01 16:29:46 +02:00
}
int cDvbApi::Index(void)
{
for (int i = 0; i < MAXDVBAPI; i++) {
if (dvbApi[i] == this)
return i;
}
return -1;
}
bool cDvbApi::Init(void)
{
NumDvbApis = 0;
2000-10-29 13:17:22 +01:00
for (int i = 0; i < MAXDVBAPI; i++) {
char fileName[strlen(VIDEODEVICE) + 10];
2000-05-01 16:29:46 +02:00
sprintf(fileName, "%s%d", VIDEODEVICE, i);
if (access(fileName, F_OK | R_OK | W_OK) == 0) {
dsyslog(LOG_INFO, "probing %s", fileName);
int f = open(fileName, O_RDWR);
if (f >= 0) {
struct video_capability cap;
int r = ioctl(f, VIDIOCGCAP, &cap);
2000-05-01 16:29:46 +02:00
close(f);
if (r == 0 && (cap.type & VID_TYPE_DVB)) {
2000-10-29 13:17:22 +01:00
char vbiFileName[strlen(VBIDEVICE) + 10];
sprintf(vbiFileName, "%s%d", VBIDEVICE, i);
dvbApi[i] = new cDvbApi(fileName, vbiFileName);
NumDvbApis++;
}
2000-05-01 16:29:46 +02:00
}
else {
if (errno != ENODEV)
LOG_ERROR_STR(fileName);
break;
}
}
else {
if (errno != ENOENT)
LOG_ERROR_STR(fileName);
break;
}
}
PrimaryDvbApi = dvbApi[0];
if (NumDvbApis > 0) {
2000-05-01 16:29:46 +02:00
isyslog(LOG_INFO, "found %d video device%s", NumDvbApis, NumDvbApis > 1 ? "s" : "");
} // need braces because of isyslog-macro
else {
2000-05-01 16:29:46 +02:00
esyslog(LOG_ERR, "ERROR: no video device found, giving up!");
}
2000-05-01 16:29:46 +02:00
return NumDvbApis > 0;
}
void cDvbApi::Cleanup(void)
{
for (int i = 0; i < MAXDVBAPI; i++) {
delete dvbApi[i];
dvbApi[i] = NULL;
}
PrimaryDvbApi = NULL;
}
2000-10-29 13:17:22 +01:00
const cSchedules *cDvbApi::Schedules(cThreadLock *ThreadLock) const
{
if (siProcessor && ThreadLock->Lock(siProcessor))
return siProcessor->Schedules();
return NULL;
}
2000-09-17 11:53:35 +02:00
bool cDvbApi::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY)
{
int result = 0;
// just do this once?
struct video_mbuf mbuf;
result |= ioctl(videoDev, VIDIOCGMBUF, &mbuf);
int msize = mbuf.size;
// gf: this needs to be a protected member of cDvbApi! //XXX kls: WHY???
unsigned char *mem = (unsigned char *)mmap(0, msize, PROT_READ | PROT_WRITE, MAP_SHARED, videoDev, 0);
if (!mem || mem == (unsigned char *)-1)
return false;
// set up the size and RGB
struct video_capability vc;
result |= ioctl(videoDev, VIDIOCGCAP, &vc);
struct video_mmap vm;
vm.frame = 0;
if ((SizeX > 0) && (SizeX <= vc.maxwidth) &&
(SizeY > 0) && (SizeY <= vc.maxheight)) {
vm.width = SizeX;
vm.height = SizeY;
}
else {
vm.width = vc.maxwidth;
vm.height = vc.maxheight;
}
vm.format = VIDEO_PALETTE_RGB24;
// this needs to be done every time:
result |= ioctl(videoDev, VIDIOCMCAPTURE, &vm);
result |= ioctl(videoDev, VIDIOCSYNC, &vm.frame);
// make RGB out of BGR:
int memsize = vm.width * vm.height;
unsigned char *mem1 = mem;
for (int i = 0; i < memsize; i++) {
unsigned char tmp = mem1[2];
mem1[2] = mem1[0];
mem1[0] = tmp;
mem1 += 3;
}
if (Quality < 0)
Quality = 255; //XXX is this 'best'???
isyslog(LOG_INFO, "grabbing to %s (%s %d %d %d)", FileName, Jpeg ? "JPEG" : "PNM", Quality, vm.width, vm.height);
FILE *f = fopen(FileName, "wb");
if (f) {
if (Jpeg) {
// write JPEG file:
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, f);
cinfo.image_width = vm.width;
cinfo.image_height = vm.height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, Quality, true);
jpeg_start_compress(&cinfo, true);
int rs = vm.width * 3;
JSAMPROW rp[vm.height];
for (int k = 0; k < vm.height; k++)
rp[k] = &mem[rs * k];
jpeg_write_scanlines(&cinfo, rp, vm.height);
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
}
else {
// write PNM file:
if (fprintf(f, "P6\n%d\n%d\n255\n", vm.width, vm.height) < 0 ||
fwrite(mem, vm.width * vm.height * 3, 1, f) < 0) {
LOG_ERROR_STR(FileName);
result |= 1;
}
}
fclose(f);
}
else {
LOG_ERROR_STR(FileName);
result |= 1;
}
2000-09-17 13:47:06 +02:00
if (ovlStat && ovlGeoSet) {
// switch the Overlay on again (gf: why have i to do anything again?)
OvlG(ovlSizeX, ovlSizeY, ovlPosX, ovlPosY);
}
if (ovlFbSet)
OvlP(ovlBrightness, ovlColour, ovlHue, ovlContrast);
2000-09-17 11:53:35 +02:00
munmap(mem, msize);
return result == 0;
}
2000-09-17 13:47:06 +02:00
bool cDvbApi::OvlF(int SizeX, int SizeY, int FbAddr, int Bpp, int Palette)
{
int result = 0;
// get the actual X-Server settings???
// plausibility-check problem: can't be verified w/o X-server!!!
if (SizeX <= 0 || SizeY <= 0 || FbAddr == 0 || Bpp / 8 > 4 ||
Bpp / 8 <= 0 || Palette <= 0 || Palette > 13 || ovlClipCount < 0 ||
SizeX > 4096 || SizeY > 4096) {
ovlFbSet = ovlGeoSet = false;
OvlO(false);
return false;
}
else {
dsyslog(LOG_INFO, "OvlF: %d %d %x %d %d", SizeX, SizeY, FbAddr, Bpp, Palette);
// this is the problematic part!
struct video_buffer vb;
result |= ioctl(videoDev, VIDIOCGFBUF, &vb);
vb.base = (void*)FbAddr;
vb.depth = Bpp;
vb.height = SizeY;
vb.width = SizeX;
vb.bytesperline = ((vb.depth + 1) / 8) * vb.width;
//now the real thing: setting the framebuffer
result |= ioctl(videoDev, VIDIOCSFBUF, &vb);
if (result) {
ovlFbSet = ovlGeoSet = false;
ovlClipCount = 0;
OvlO(false);
return false;
}
else {
ovlFbSizeX = SizeX;
ovlFbSizeY = SizeY;
ovlBpp = Bpp;
ovlPalette = Palette;
ovlFbSet = true;
return true;
}
}
}
bool cDvbApi::OvlG(int SizeX, int SizeY, int PosX, int PosY)
{
int result = 0;
// get the actual X-Server settings???
struct video_capability vc;
result |= ioctl(videoDev, VIDIOCGCAP, &vc);
if (!ovlFbSet)
return false;
if (SizeX < vc.minwidth || SizeY < vc.minheight ||
SizeX > vc.maxwidth || SizeY>vc.maxheight
// || PosX > FbSizeX || PosY > FbSizeY
// PosX < -SizeX || PosY < -SizeY ||
) {
ovlGeoSet = false;
OvlO(false);
return false;
}
else {
struct video_window vw;
result |= ioctl(videoDev, VIDIOCGWIN, &vw);
vw.x = PosX;
vw.y = PosY;
vw.width = SizeX;
vw.height = SizeY;
vw.chromakey = ovlPalette;
vw.flags = VIDEO_WINDOW_CHROMAKEY; // VIDEO_WINDOW_INTERLACE; //VIDEO_CLIP_BITMAP;
vw.clips = ovlClipRects;
vw.clipcount = ovlClipCount;
result |= ioctl(videoDev, VIDIOCSWIN, &vw);
if (result) {
ovlGeoSet = false;
ovlClipCount = 0;
return false;
}
else {
ovlSizeX = SizeX;
ovlSizeY = SizeY;
ovlPosX = PosX;
ovlPosY = PosY;
ovlGeoSet = true;
ovlStat = true;
return true;
}
}
}
bool cDvbApi::OvlC(int ClipCount, CRect *cr)
{
if (ovlGeoSet && ovlFbSet) {
for (int i = 0; i < ClipCount; i++) {
ovlClipRects[i].x = cr[i].x;
ovlClipRects[i].y = cr[i].y;
ovlClipRects[i].width = cr[i].width;
ovlClipRects[i].height = cr[i].height;
ovlClipRects[i].next = &(ovlClipRects[i + 1]);
}
ovlClipCount = ClipCount;
//use it:
return OvlG(ovlSizeX, ovlSizeY, ovlPosX, ovlPosY);
}
return false;
}
bool cDvbApi::OvlP(__u16 Brightness, __u16 Colour, __u16 Hue, __u16 Contrast)
{
int result = 0;
ovlBrightness = Brightness;
ovlColour = Colour;
ovlHue = Hue;
ovlContrast = Contrast;
struct video_picture vp;
if (!ovlFbSet)
return false;
result |= ioctl(videoDev, VIDIOCGPICT, &vp);
vp.brightness = Brightness;
vp.colour = Colour;
vp.hue = Hue;
vp.contrast = Contrast;
vp.depth = ovlBpp;
vp.palette = ovlPalette; // gf: is this always ok? VIDEO_PALETTE_RGB565;
result |= ioctl(videoDev, VIDIOCSPICT, &vp);
return result == 0;
}
bool cDvbApi::OvlO(bool Value)
{
int result = 0;
if (!ovlGeoSet && Value)
return false;
int one = 1;
int zero = 0;
result |= ioctl(videoDev, VIDIOCCAPTURE, Value ? &one : &zero);
ovlStat = Value;
if (result) {
ovlStat = false;
return false;
}
return true;
}
#ifdef DEBUG_OSD
void cDvbApi::SetColor(eDvbColor colorFg, eDvbColor colorBg)
{
int color = (colorBg << 16) | colorFg | 0x80000000;
for (int i = 0; i < MaxColorPairs; i++) {
if (!colorPairs[i]) {
colorPairs[i] = color;
init_pair(i + 1, colorFg, colorBg);
wattrset(window, COLOR_PAIR(i + 1));
break;
}
else if (color == colorPairs[i]) {
wattrset(window, COLOR_PAIR(i + 1));
break;
}
}
}
#else
void cDvbApi::Cmd(OSD_Command cmd, int color, int x0, int y0, int x1, int y1, const void *data)
2000-02-19 13:36:48 +01:00
{
if (videoDev >= 0) {
2000-02-19 13:36:48 +01:00
struct drawcmd dc;
dc.cmd = cmd;
dc.color = color;
dc.x0 = x0;
dc.y0 = y0;
dc.x1 = x1;
dc.y1 = y1;
dc.data = (void *)data;
ioctl(videoDev, VIDIOCSOSDCOMMAND, &dc);
2000-09-09 14:57:43 +02:00
usleep(10); // XXX Workaround for a driver bug (cInterface::DisplayChannel() displayed texts at wrong places
// XXX and sometimes the OSD was no longer displayed).
// XXX Increase the value if the problem still persists on your particular system.
// TODO Check if this is still necessary with driver versions after 0.6.
2000-02-19 13:36:48 +01:00
}
}
#endif
2000-04-23 15:38:16 +02:00
void cDvbApi::Open(int w, int h)
{
2000-04-23 15:38:16 +02:00
int d = (h < 0) ? MenuLines + h : 0;
h = abs(h);
cols = w;
rows = h;
#ifdef DEBUG_OSD
2000-04-22 13:51:48 +02:00
window = subwin(stdscr, h, w, d, 0);
2000-09-17 11:53:35 +02:00
syncok(window, true);
#define B2C(b) (((b) * 1000) / 255)
#define SETCOLOR(n, r, g, b, o) init_color(n, B2C(r), B2C(g), B2C(b))
#else
w *= charWidth;
h *= lineHeight;
2000-04-22 13:51:48 +02:00
d *= lineHeight;
int x = (720 - MenuColumns * charWidth) / 2; //TODO PAL vs. NTSC???
int y = (576 - MenuLines * lineHeight) / 2 + d;
2000-10-03 10:34:48 +02:00
osd = new cDvbOsd(videoDev, x, y, x + w - 1, y + h - 1, 4);
#define SETCOLOR(n, r, g, b, o) Cmd(OSD_SetColor, n, r, g, b, o)
2000-10-03 10:34:48 +02:00
SETCOLOR(clrTransparent, 0x00, 0x00, 0x00, 0);
#endif
2000-10-03 10:34:48 +02:00
SETCOLOR(clrBackground, 0x00, 0x00, 0x00, 127); // background 50% gray
SETCOLOR(clrBlack, 0x00, 0x00, 0x00, 255);
SETCOLOR(clrRed, 0xFC, 0x14, 0x14, 255);
SETCOLOR(clrGreen, 0x24, 0xFC, 0x24, 255);
SETCOLOR(clrYellow, 0xFC, 0xC0, 0x24, 255);
SETCOLOR(clrBlue, 0x00, 0x00, 0xFC, 255);
SETCOLOR(clrCyan, 0x00, 0xFC, 0xFC, 255);
SETCOLOR(clrMagenta, 0xB0, 0x00, 0xFC, 255);
SETCOLOR(clrWhite, 0xFC, 0xFC, 0xFC, 255);
}
2000-02-19 13:36:48 +01:00
void cDvbApi::Close(void)
2000-02-19 13:36:48 +01:00
{
2000-05-01 16:29:46 +02:00
#ifdef DEBUG_OSD
if (window) {
delwin(window);
window = 0;
}
2000-05-01 16:29:46 +02:00
#else
2000-10-03 10:34:48 +02:00
delete osd;
osd = NULL;
#endif
2000-02-19 13:36:48 +01:00
}
void cDvbApi::Clear(void)
2000-02-19 13:36:48 +01:00
{
#ifdef DEBUG_OSD
SetColor(clrBackground, clrBackground);
Fill(0, 0, cols, rows, clrBackground);
#else
2000-10-03 10:34:48 +02:00
osd->Clear();
#endif
2000-02-19 13:36:48 +01:00
}
void cDvbApi::Fill(int x, int y, int w, int h, eDvbColor color)
2000-02-19 13:36:48 +01:00
{
if (x < 0) x = cols + x;
if (y < 0) y = rows + y;
#ifdef DEBUG_OSD
SetColor(color, color);
for (int r = 0; r < h; r++) {
wmove(window, y + r, x); // ncurses wants 'y' before 'x'!
whline(window, ' ', w);
}
2000-04-22 13:51:48 +02:00
wsyncup(window); // shouldn't be necessary because of 'syncok()', but w/o it doesn't work
#else
2000-10-03 10:34:48 +02:00
osd->Fill(x * charWidth, y * lineHeight, (x + w) * charWidth - 1, (y + h) * lineHeight - 1, color);
#endif
2000-02-19 13:36:48 +01:00
}
2000-12-09 11:13:00 +01:00
void cDvbApi::SetBitmap(int x, int y, const cBitmap &Bitmap)
{
#ifndef DEBUG_OSD
osd->SetBitmap(x, y, Bitmap);
#endif
}
void cDvbApi::ClrEol(int x, int y, eDvbColor color)
2000-02-19 13:36:48 +01:00
{
Fill(x, y, cols - x, 1, color);
2000-02-19 13:36:48 +01:00
}
2000-11-01 11:45:05 +01:00
int cDvbApi::CellWidth(void)
{
#ifdef DEBUG_OSD
return 1;
#else
return charWidth;
#endif
}
2000-12-09 11:13:00 +01:00
int cDvbApi::LineHeight(void)
{
#ifdef DEBUG_OSD
return 1;
#else
return lineHeight;
#endif
}
2000-11-01 11:45:05 +01:00
int cDvbApi::Width(unsigned char c)
{
#ifdef DEBUG_OSD
return 1;
#else
return osd->Width(c);
#endif
}
int cDvbApi::WidthInCells(const char *s)
{
#ifdef DEBUG_OSD
return strlen(s);
#else
return (osd->Width(s) + charWidth - 1) / charWidth;
#endif
}
2000-11-18 15:46:00 +01:00
eDvbFont cDvbApi::SetFont(eDvbFont Font)
{
#ifdef DEBUG_OSD
return Font;
#else
return osd->SetFont(Font);
#endif
}
void cDvbApi::Text(int x, int y, const char *s, eDvbColor colorFg, eDvbColor colorBg)
2000-02-19 13:36:48 +01:00
{
if (x < 0) x = cols + x;
if (y < 0) y = rows + y;
#ifdef DEBUG_OSD
SetColor(colorFg, colorBg);
wmove(window, y, x); // ncurses wants 'y' before 'x'!
waddnstr(window, s, cols - x);
#else
2000-10-03 10:34:48 +02:00
osd->Text(x * charWidth, y * lineHeight, s, colorFg, colorBg);
#endif
}
void cDvbApi::Flush(void)
{
#ifndef DEBUG_OSD
if (osd)
osd->Flush();
#endif
2000-02-19 13:36:48 +01:00
}
2000-11-05 18:39:17 +01:00
bool cDvbApi::SetChannel(int ChannelNumber, int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Ca, int Pnr)
{
if (videoDev >= 0) {
2000-11-19 16:49:14 +01:00
StopTransfer();
if (transferringFromDvbApi) {
transferringFromDvbApi->StopTransfer();
transferringFromDvbApi = NULL;
}
2000-12-08 16:23:32 +01:00
SetPlayMode(videoDev, VID_PLAY_RESET);
struct frontend front;
ioctl(videoDev, VIDIOCGFRONTEND, &front);
unsigned int freq = FrequencyMHz;
front.ttk = (freq < 11700UL) ? 0 : 1;
if (freq < 11700UL)
2000-10-08 16:18:23 +02:00
freq -= Setup.LnbFrequLo;
else
2000-10-08 16:18:23 +02:00
freq -= Setup.LnbFrequHi;
front.channel_flags = Ca ? DVB_CHANNEL_CA : DVB_CHANNEL_FTA;
front.pnr = Pnr;
front.freq = freq * 1000000UL;
front.diseqc = Diseqc;
front.srate = Srate * 1000;
front.volt = (Polarization == 'v' || Polarization == 'V') ? 0 : 1;
front.video_pid = Vpid;
front.audio_pid = Apid;
front.fec = 8;
front.AFC = 1;
ioctl(videoDev, VIDIOCSFRONTEND, &front);
2000-10-29 13:17:22 +01:00
if (front.sync & 0x1F == 0x1F) {
if (this == PrimaryDvbApi && siProcessor)
2000-10-29 13:17:22 +01:00
siProcessor->SetCurrentServiceID(Pnr);
2000-11-05 18:39:17 +01:00
currentChannel = ChannelNumber;
2000-11-19 16:49:14 +01:00
// If this DVB card can't receive this channel, let's see if we can
// use the card that actually can receive it and transfer data from there:
if (Ca && Ca != Index() + 1) {
cDvbApi *CaDvbApi = GetDvbApi(Ca, 0);
if (CaDvbApi) {
if (!CaDvbApi->Recording()) {
if (CaDvbApi->SetChannel(ChannelNumber, FrequencyMHz, Polarization, Diseqc, Srate, Vpid, Apid, Ca, Pnr))
transferringFromDvbApi = CaDvbApi->StartTransfer(videoDev);
}
}
}
return true;
2000-10-29 13:17:22 +01:00
}
esyslog(LOG_ERR, "ERROR: channel not sync'ed (front.sync=%X)!", front.sync);
}
return false;
}
2000-11-19 16:49:14 +01:00
bool cDvbApi::Transferring(void)
{
return transferBuffer;
}
cDvbApi *cDvbApi::StartTransfer(int TransferToVideoDev)
{
StopTransfer();
transferBuffer = new cTransferBuffer(videoDev, TransferToVideoDev);
return this;
}
void cDvbApi::StopTransfer(void)
{
if (transferBuffer) {
delete transferBuffer;
transferBuffer = NULL;
2000-12-08 16:23:32 +01:00
SetPlayMode(videoDev, VID_PLAY_RESET);
2000-11-19 16:49:14 +01:00
}
}
bool cDvbApi::Recording(void)
{
2000-12-08 16:23:32 +01:00
if (recordBuffer && !recordBuffer->Active())
StopRecord();
return recordBuffer != NULL;
}
bool cDvbApi::Replaying(void)
{
2000-12-08 16:23:32 +01:00
if (replayBuffer && !replayBuffer->Active())
StopReplay();
return replayBuffer != NULL;
}
bool cDvbApi::StartRecord(const char *FileName, int Ca, int Priority)
{
if (Recording()) {
esyslog(LOG_ERR, "ERROR: StartRecord() called while recording - ignored!");
return false;
}
if (videoDev >= 0) {
2000-11-19 16:49:14 +01:00
StopTransfer();
2000-12-08 16:23:32 +01:00
StopReplay(); // TODO: remove this if the driver is able to do record and replay at the same time
2000-05-01 16:29:46 +02:00
// Check FileName:
if (!FileName) {
esyslog(LOG_ERR, "ERROR: StartRecord: file name is (null)");
return false;
}
isyslog(LOG_INFO, "record %s", FileName);
// Create directories if necessary:
if (!MakeDirs(FileName, true))
return false;
2000-12-08 16:23:32 +01:00
// Create recording buffer:
2000-12-08 16:23:32 +01:00
recordBuffer = new cRecordBuffer(&videoDev, FileName);
2000-12-08 16:23:32 +01:00
if (recordBuffer) {
ca = Ca;
priority = Priority;
return true;
}
2000-12-08 16:23:32 +01:00
else
esyslog(LOG_ERR, "ERROR: can't allocate recording buffer");
}
return false;
}
void cDvbApi::StopRecord(void)
{
2000-12-08 16:23:32 +01:00
if (recordBuffer) {
delete recordBuffer;
recordBuffer = NULL;
ca = 0;
priority = -1;
}
}
2000-12-09 11:13:00 +01:00
bool cDvbApi::StartReplay(const char *FileName)
{
if (Recording()) {
esyslog(LOG_ERR, "ERROR: StartReplay() called while recording - ignored!");
return false;
}
2000-11-19 16:49:14 +01:00
StopTransfer();
2000-12-08 16:23:32 +01:00
StopReplay();
if (videoDev >= 0) {
// Check FileName:
if (!FileName) {
esyslog(LOG_ERR, "ERROR: StartReplay: file name is (null)");
return false;
}
isyslog(LOG_INFO, "replay %s", FileName);
2000-12-08 16:23:32 +01:00
// Create replay buffer:
2000-12-08 16:23:32 +01:00
replayBuffer = new cReplayBuffer(&videoDev, FileName);
if (replayBuffer)
return true;
else
esyslog(LOG_ERR, "ERROR: can't allocate replaying buffer");
}
return false;
}
2000-12-08 16:23:32 +01:00
void cDvbApi::StopReplay(void)
{
2000-12-08 16:23:32 +01:00
if (replayBuffer) {
delete replayBuffer;
replayBuffer = NULL;
}
}
void cDvbApi::Pause(void)
{
2000-12-08 16:23:32 +01:00
if (replayBuffer)
replayBuffer->Pause();
}
void cDvbApi::Play(void)
{
2000-12-08 16:23:32 +01:00
if (replayBuffer)
replayBuffer->Play();
}
void cDvbApi::Forward(void)
{
2000-12-08 16:23:32 +01:00
if (replayBuffer)
replayBuffer->Forward();
}
void cDvbApi::Backward(void)
{
2000-12-08 16:23:32 +01:00
if (replayBuffer)
replayBuffer->Backward();
}
void cDvbApi::Skip(int Seconds)
{
2000-12-08 16:23:32 +01:00
if (replayBuffer)
replayBuffer->SkipSeconds(Seconds);
}
2000-12-08 16:23:32 +01:00
bool cDvbApi::GetIndex(int &Current, int &Total)
2000-04-23 15:38:16 +02:00
{
2000-12-08 16:23:32 +01:00
if (replayBuffer) {
replayBuffer->GetIndex(Current, Total);
return true;
2000-04-23 15:38:16 +02:00
}
2000-04-24 15:32:11 +02:00
return false;
2000-04-23 15:38:16 +02:00
}
// --- cEITScanner -----------------------------------------------------------
cEITScanner::cEITScanner(void)
{
lastScan = lastActivity = time(NULL);
currentChannel = 0;
lastChannel = 1;
}
void cEITScanner::Activity(void)
{
if (currentChannel) {
Channels.SwitchTo(currentChannel);
currentChannel = 0;
}
lastActivity = time(NULL);
}
void cEITScanner::Process(void)
{
if (Channels.MaxNumber() > 1) {
time_t now = time(NULL);
if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
for (int i = 0; i < cDvbApi::NumDvbApis; i++) {
cDvbApi *DvbApi = cDvbApi::GetDvbApi(i, 0);
if (DvbApi) {
if (DvbApi != cDvbApi::PrimaryDvbApi || (cDvbApi::NumDvbApis == 1 && Setup.EPGScanTimeout && now - lastActivity > Setup.EPGScanTimeout * 3600)) {
2000-11-19 16:49:14 +01:00
if (!(DvbApi->Recording() || DvbApi->Replaying() || DvbApi->Transferring())) {
int oldCh = lastChannel;
int ch = oldCh + 1;
while (ch != oldCh) {
if (ch > Channels.MaxNumber())
ch = 1;
cChannel *Channel = Channels.GetByNumber(ch);
if (Channel && Channel->pnr) {
if (DvbApi == cDvbApi::PrimaryDvbApi && !currentChannel)
currentChannel = DvbApi->Channel();
Channel->Switch(DvbApi, false);
lastChannel = ch;
break;
}
ch++;
}
}
}
}
}
lastScan = time(NULL);
}
}
}