diff options
Diffstat (limited to 'dvbapi.c')
-rw-r--r-- | dvbapi.c | 2846 |
1 files changed, 0 insertions, 2846 deletions
diff --git a/dvbapi.c b/dvbapi.c deleted file mode 100644 index 0b14a87..0000000 --- a/dvbapi.c +++ /dev/null @@ -1,2846 +0,0 @@ -/* - * dvbapi.c: Interface to the DVB driver - * - * See the main source file 'vdr.c' for copyright information and - * how to reach the author. - * - * $Id: dvbapi.c 1.175 2002/05/13 16:29:17 kls Exp $ - */ - -#include "dvbapi.h" -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -extern "C" { -#define HAVE_BOOLEAN -#include <jpeglib.h> -} -#include <stdlib.h> -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <unistd.h> -#include "config.h" -#include "recording.h" -#include "remux.h" -#include "ringbuffer.h" -#include "tools.h" -#include "videodir.h" - -#define DEV_VIDEO "/dev/video" -#define DEV_OST_OSD "/dev/ost/osd" -#define DEV_OST_FRONTEND "/dev/ost/frontend" -#define DEV_OST_SEC "/dev/ost/sec" -#define DEV_OST_DVR "/dev/ost/dvr" -#define DEV_OST_DEMUX "/dev/ost/demux" -#define DEV_OST_VIDEO "/dev/ost/video" -#define DEV_OST_AUDIO "/dev/ost/audio" - -// The size of the array used to buffer video data: -// (must be larger than MINVIDEODATA - see remux.h) -#define VIDEOBUFSIZE MEGABYTE(1) - -// The maximum size of a single frame: -#define MAXFRAMESIZE KILOBYTE(192) - -#define MAXFILESPERRECORDING 255 - -#define MINFREEDISKSPACE (512) // MB -#define DISKCHECKINTERVAL 100 // seconds - -#define INDEXFILESUFFIX "/index.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) - -// The maximum time we wait before assuming that a recorded video data stream -// is broken: -#define MAXBROKENTIMEOUT 30 // seconds - -// The maximum time to wait before giving up while catching up on an index file: -#define MAXINDEXCATCHUP 2 // seconds - -// The default priority for non-primary DVB cards: -#define DEFAULTPRIORITY -2 - -#define CHECK(s) { if ((s) < 0) LOG_ERROR; } // used for 'ioctl()' calls - -#define FATALERRNO (errno != EAGAIN && errno != EINTR) - -typedef unsigned char uchar; - -const char *IndexToHMSF(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; -} - -int HMSFToIndex(const char *HMSF) -{ - int h, m, s, f = 0; - if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f)) - return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1; - return 0; -} - -// --- cIndexFile ------------------------------------------------------------ - -class cIndexFile { -private: - struct tIndex { int offset; uchar type; uchar number; short reserved; }; - int f; - char *fileName; - int size, last; - tIndex *index; - cResumeFile resumeFile; - bool CatchUp(int Index = -1); -public: - cIndexFile(const char *FileName, bool Record); - ~cIndexFile(); - bool Ok(void) { return index != NULL; } - bool Write(uchar PictureType, uchar FileNumber, int FileOffset); - bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL); - int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false); - int Get(uchar FileNumber, int FileOffset); - int Last(void) { CatchUp(); 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) -{ - f = -1; - fileName = NULL; - size = 0; - last = -1; - index = NULL; - if (FileName) { - fileName = new char[strlen(FileName) + strlen(INDEXFILESUFFIX) + 1]; - if (fileName) { - strcpy(fileName, FileName); - char *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("ERROR: invalid file size (%ld) 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)safe_read(f, index, buf.st_size) != buf.st_size) { - esyslog("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("ERROR: can't allocate %d bytes for index '%s'", size * sizeof(tIndex), fileName); - } - } - else - LOG_ERROR; - } - else if (!Record) - isyslog("missing index file %s", fileName); - if (Record) { - if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) >= 0) { - if (delta) { - esyslog("ERROR: padding index file with %d '0' bytes", delta); - while (delta--) - writechar(f, 0); - } - } - else - LOG_ERROR_STR(fileName); - } - } - else - esyslog("ERROR: can't copy file name '%s'", FileName); - } -} - -cIndexFile::~cIndexFile() -{ - if (f >= 0) - close(f); - delete fileName; - delete index; -} - -bool cIndexFile::CatchUp(int Index) -{ - if (index && f >= 0) { - for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) { - 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 (safe_read(f, &index[last + 1], delta) != delta) { - esyslog("ERROR: can't read from index"); - delete index; - index = NULL; - close(f); - f = -1; - break; - } - last = newLast; - } - else - LOG_ERROR_STR(fileName); - } - else - esyslog("ERROR: can't realloc() index"); - } - } - else - LOG_ERROR_STR(fileName); - if (Index >= last) - sleep(1); - else - return true; - } - } - return false; -} - -bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) -{ - if (f >= 0) { - tIndex i = { FileOffset, PictureType, FileNumber, 0 }; - if (safe_write(f, &i, sizeof(i)) < 0) { - LOG_ERROR_STR(fileName); - close(f); - f = -1; - return false; - } - last++; - } - return f >= 0; -} - -bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length) -{ - if (index) { - CatchUp(Index); - if (Index >= 0 && Index <= last) { - *FileNumber = index[Index].number; - *FileOffset = index[Index].offset; - if (PictureType) - *PictureType = index[Index].type; - if (Length) { - int fn = index[Index + 1].number; - int fo = index[Index + 1].offset; - if (fn == *FileNumber) - *Length = fo - *FileOffset; - else - *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly) - } - return true; - } - } - return false; -} - -int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd) -{ - if (index) { - CatchUp(); - int d = Forward ? 1 : -1; - for (;;) { - Index += d; - if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? 100 : 0)) { - if (index[Index].type == I_FRAME) { - if (FileNumber) - *FileNumber = index[Index].number; - else - FileNumber = &index[Index].number; - if (FileOffset) - *FileOffset = index[Index].offset; - else - 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("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; -} - -// --- cFileName ------------------------------------------------------------- - -class cFileName { -private: - int file; - int fileNumber; - char *fileName, *pFileNumber; - bool record; - bool blocking; -public: - cFileName(const char *FileName, bool Record, bool Blocking = false); - ~cFileName(); - const char *Name(void) { return fileName; } - int Number(void) { return fileNumber; } - int Open(void); - void Close(void); - int SetOffset(int Number, int Offset = 0); - int NextFile(void); - }; - -cFileName::cFileName(const char *FileName, bool Record, bool Blocking) -{ - file = -1; - fileNumber = 0; - record = Record; - blocking = Blocking; - // Prepare the file name: - fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN]; - if (!fileName) { - esyslog("ERROR: can't copy file name '%s'", fileName); - return; - } - strcpy(fileName, FileName); - pFileNumber = fileName + strlen(fileName); - SetOffset(1); -} - -cFileName::~cFileName() -{ - Close(); - delete fileName; -} - -int cFileName::Open(void) -{ - if (file < 0) { - int BlockingFlag = blocking ? 0 : O_NONBLOCK; - if (record) { - dsyslog("recording to '%s'", fileName); - file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag); - if (file < 0) - LOG_ERROR_STR(fileName); - } - else { - if (access(fileName, R_OK) == 0) { - dsyslog("playing '%s'", fileName); - file = open(fileName, O_RDONLY | BlockingFlag); - if (file < 0) - LOG_ERROR_STR(fileName); - } - else if (errno != ENOENT) - LOG_ERROR_STR(fileName); - } - } - return file; -} - -void cFileName::Close(void) -{ - if (file >= 0) { - if ((record && CloseVideoFile(file) < 0) || (!record && close(file) < 0)) - LOG_ERROR_STR(fileName); - file = -1; - } -} - -int cFileName::SetOffset(int Number, int Offset) -{ - if (fileNumber != Number) - Close(); - if (0 < Number && Number <= MAXFILESPERRECORDING) { - fileNumber = Number; - sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); - if (record) { - if (access(fileName, F_OK) == 0) // file exists, let's try next suffix - return SetOffset(Number + 1); - else if (errno != ENOENT) { // something serious has happened - LOG_ERROR_STR(fileName); - return -1; - } - // found a non existing file suffix - } - if (Open() >= 0) { - if (!record && Offset >= 0 && lseek(file, Offset, SEEK_SET) != Offset) { - LOG_ERROR_STR(fileName); - return -1; - } - } - return file; - } - esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); - return -1; -} - -int cFileName::NextFile(void) -{ - return SetOffset(fileNumber + 1); -} - -// --- cRecordBuffer --------------------------------------------------------- - -class cRecordBuffer : public cRingBufferLinear { -private: - cDvbApi *dvbApi; - cFileName fileName; - cIndexFile *index; - cRemux remux; - uchar pictureType; - int fileSize; - int videoDev; - int recordFile; - bool recording; - time_t lastDiskSpaceCheck; - bool RunningLowOnDiskSpace(void); - bool NextFile(void); -protected: - virtual void Input(void); - virtual void Output(void); -public: - cRecordBuffer(cDvbApi *DvbApi, const char *FileName, int VPid, int APid1, int APid2, int DPid1, int DPid2); - virtual ~cRecordBuffer(); - }; - -cRecordBuffer::cRecordBuffer(cDvbApi *DvbApi, const char *FileName, int VPid, int APid1, int APid2, int DPid1, int DPid2) -:cRingBufferLinear(VIDEOBUFSIZE, true) -,fileName(FileName, true) -,remux(VPid, APid1, APid2, DPid1, DPid2, true) -{ - dvbApi = DvbApi; - index = NULL; - pictureType = NO_PICTURE; - fileSize = 0; - recordFile = fileName.Open(); - recording = false; - lastDiskSpaceCheck = time(NULL); - if (!fileName.Name()) - return; - // Create the index file: - index = new cIndexFile(FileName, true); - if (!index) - esyslog("ERROR: can't allocate index"); - // let's continue without index, so we'll at least have the recording - videoDev = dvbApi->SetModeRecord(); - Start(); -} - -cRecordBuffer::~cRecordBuffer() -{ - Stop(); - dvbApi->SetModeNormal(true); - delete index; -} - -bool cRecordBuffer::RunningLowOnDiskSpace(void) -{ - if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { - int Free = FreeDiskSpaceMB(fileName.Name()); - lastDiskSpaceCheck = time(NULL); - if (Free < MINFREEDISKSPACE) { - dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE); - return true; - } - } - return false; -} - -bool cRecordBuffer::NextFile(void) -{ - if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME - if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) { - recordFile = fileName.NextFile(); - fileSize = 0; - } - } - return recordFile >= 0; -} - -void cRecordBuffer::Input(void) -{ - dsyslog("input thread started (pid=%d)", getpid()); - - uchar b[MINVIDEODATA]; - time_t t = time(NULL); - recording = true; - for (;;) { - if (cFile::FileReady(videoDev, 100)) { - int r = read(videoDev, b, sizeof(b)); - if (r > 0) { - uchar *p = b; - while (r > 0) { - int w = Put(p, r); - p += w; - r -= w; - if (r > 0) - usleep(1); // this keeps the CPU load low - } - t = time(NULL); - } - else if (r < 0) { - if (FATALERRNO) { - if (errno == EBUFFEROVERFLOW) // this error code is not defined in the library - esyslog("ERROR (%s,%d): DVB driver buffer overflow", __FILE__, __LINE__); - else { - LOG_ERROR; - break; - } - } - } - } - if (time(NULL) - t > MAXBROKENTIMEOUT) { - esyslog("ERROR: video data stream broken"); - cThread::EmergencyExit(true); - t = time(NULL); - } - if (!recording) - break; - } - - dsyslog("input thread ended (pid=%d)", getpid()); -} - -void cRecordBuffer::Output(void) -{ - dsyslog("output thread started (pid=%d)", getpid()); - - uchar b[MINVIDEODATA]; - int r = 0; - for (;;) { - int g = Get(b + r, sizeof(b) - r); - if (g > 0) - r += g; - if (r > 0) { - int Count = r, Result; - const uchar *p = remux.Process(b, Count, Result, &pictureType); - if (p) { - if (!Busy() && pictureType == I_FRAME) // finish the recording before the next 'I' frame - break; - if (NextFile()) { - if (index && pictureType != NO_PICTURE) - index->Write(pictureType, fileName.Number(), fileSize); - if (safe_write(recordFile, p, Result) < 0) { - LOG_ERROR_STR(fileName.Name()); - recording = false; - return; - } - fileSize += Result; - } - else - break; - } - if (Count > 0) { - r -= Count; - memmove(b, b + Count, r); - } - if (!recording) - break; - } - else - usleep(1); // this keeps the CPU load low - } - recording = false; - - dsyslog("output thread ended (pid=%d)", getpid()); -} - -// --- ReadFrame ------------------------------------------------------------- - -int ReadFrame(int f, uchar *b, int Length, int Max) -{ - if (Length == -1) - Length = Max; // this means we read up to EOF (see cIndex) - else if (Length > Max) { - esyslog("ERROR: frame larger than buffer (%d > %d)", Length, Max); - Length = Max; - } - int r = safe_read(f, b, Length); - if (r < 0) - LOG_ERROR; - return r; -} - -// --- cBackTrace ---------------------------------------------------------- - -#define AVG_FRAME_SIZE 15000 // an assumption about the average frame size -#define DVB_BUF_SIZE (256 * 1024) // an assumption about the dvb firmware buffer size -#define BACKTRACE_ENTRIES (DVB_BUF_SIZE / AVG_FRAME_SIZE + 20) // how many entries are needed to backtrace buffer contents - -class cBackTrace { -private: - int index[BACKTRACE_ENTRIES]; - int length[BACKTRACE_ENTRIES]; - int pos, num; -public: - cBackTrace(void); - void Clear(void); - void Add(int Index, int Length); - int Get(bool Forward); - }; - -cBackTrace::cBackTrace(void) -{ - Clear(); -} - -void cBackTrace::Clear(void) -{ - pos = num = 0; -} - -void cBackTrace::Add(int Index, int Length) -{ - index[pos] = Index; - length[pos] = Length; - if (++pos >= BACKTRACE_ENTRIES) - pos = 0; - if (num < BACKTRACE_ENTRIES) - num++; -} - -int cBackTrace::Get(bool Forward) -{ - int p = pos; - int n = num; - int l = DVB_BUF_SIZE + (Forward ? 0 : 256 * 1024); //XXX (256 * 1024) == DVB_BUF_SIZE ??? - int i = -1; - - while (n && l > 0) { - if (--p < 0) - p = BACKTRACE_ENTRIES - 1; - i = index[p] - 1; - l -= length[p]; - n--; - } - return i; -} - -// --- cPlayBuffer --------------------------------------------------------- - -#define MAX_VIDEO_SLOWMOTION 63 // max. arg to pass to VIDEO_SLOWMOTION // TODO is this value correct? - -class cPlayBuffer : public cRingBufferFrame { -private: - cBackTrace backTrace; -protected: - enum ePlayModes { pmPlay, pmPause, pmSlow, pmFast, pmStill }; - enum ePlayDirs { pdForward, pdBackward }; - static int Speeds[]; - cDvbApi *dvbApi; - int videoDev, audioDev; - cPipe dolbyDev; - int blockInput, blockOutput; - ePlayModes playMode; - ePlayDirs playDir; - int trickSpeed; - int readIndex, writeIndex; - bool canDoTrickMode; - bool canToggleAudioTrack; - bool skipAC3bytes; - uchar audioTrack; - void TrickSpeed(int Increment); - virtual void Empty(bool Block = false); - virtual void StripAudioPackets(uchar *b, int Length, uchar Except = 0x00) {} - virtual void PlayExternalDolby(const uchar *b, int MaxLength); - virtual void Output(void); - void putFrame(cFrame *Frame); - void putFrame(unsigned char *Data, int Length, eFrameType Type = ftUnknown); -public: - cPlayBuffer(cDvbApi *DvbApi, int VideoDev, int AudioDev); - virtual ~cPlayBuffer(); - virtual void Pause(void); - virtual void Play(void); - virtual void Forward(void); - virtual void Backward(void); - virtual int SkipFrames(int Frames) { return -1; } - virtual void SkipSeconds(int Seconds) {} - virtual void Goto(int Position, bool Still = false) {} - virtual void GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { Current = Total = -1; } - bool GetReplayMode(bool &Play, bool &Forward, int &Speed); - bool CanToggleAudioTrack(void) { return canToggleAudioTrack; }; - virtual void ToggleAudioTrack(void); - }; - -#define NORMAL_SPEED 4 // the index of the '1' entry in the following array -#define MAX_SPEEDS 3 // the offset of the maximum speed from normal speed in either direction -#define SPEED_MULT 12 // the speed multiplier -int cPlayBuffer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 }; - -cPlayBuffer::cPlayBuffer(cDvbApi *DvbApi, int VideoDev, int AudioDev) -:cRingBufferFrame(VIDEOBUFSIZE) -{ - dvbApi = DvbApi; - videoDev = VideoDev; - audioDev = AudioDev; - blockInput = blockOutput = false; - playMode = pmPlay; - playDir = pdForward; - trickSpeed = NORMAL_SPEED; - readIndex = writeIndex = -1; - canDoTrickMode = false; - canToggleAudioTrack = false; - skipAC3bytes = false; - audioTrack = 0xC0; -} - -cPlayBuffer::~cPlayBuffer() -{ -} - -void cPlayBuffer::PlayExternalDolby(const uchar *b, int MaxLength) -{ - if (cDvbApi::AudioCommand()) { - if (!dolbyDev && !dolbyDev.Open(cDvbApi::AudioCommand(), "w")) { - esyslog("ERROR: can't open pipe to audio command '%s'", cDvbApi::AudioCommand()); - return; - } - if (b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x01) { - if (b[3] == 0xBD) { // dolby - int l = b[4] * 256 + b[5] + 6; - int written = b[8] + (skipAC3bytes ? 13 : 9); // skips the PES header - int n = min(l - written, MaxLength); - while (n > 0) { - int w = fwrite(&b[written], 1, n, dolbyDev); - if (w < 0) { - LOG_ERROR; - break; - } - n -= w; - written += w; - } - } - } - } -} - -void cPlayBuffer::Output(void) -{ - dsyslog("output thread started (pid=%d)", getpid()); - - while (Busy()) { - if (blockOutput) { - if (blockOutput > 1) - blockOutput = 1; - continue; - } - const cFrame *frame = Get(); - if (frame) { - if (frame->Type() == ftDolby) - PlayExternalDolby(frame->Data(), frame->Count()); - else { - StripAudioPackets((uchar *)frame->Data(), frame->Count(), (playMode == pmFast || playMode == pmSlow) ? 0x00 : audioTrack);//XXX - const uchar *p = frame->Data(); - int r = frame->Count(); - while (r > 0 && Busy() && !blockOutput) { - if (cFile::FileReadyForWriting(videoDev, 100)) { - int w = write(videoDev, p, r); - if (w > 0) { - p += w; - r -= w; - } - else if (w < 0 && FATALERRNO) { - LOG_ERROR; - Stop(); - return; - } - } - } - writeIndex = frame->Index(); - backTrace.Add(frame->Index(), frame->Count()); - } - Drop(frame); - } - } - - dsyslog("output thread ended (pid=%d)", getpid()); -} - -void cPlayBuffer::putFrame(cFrame *Frame) -{ - while (Busy() && !blockInput) { - if (Put(Frame)) - return; - } - delete Frame; // caller relies on frame being put, otherwise this would be a memory leak! -} - -void cPlayBuffer::putFrame(unsigned char *Data, int Length, eFrameType Type) -{ - putFrame(new cFrame(Data, Length, Type)); -} - -void cPlayBuffer::TrickSpeed(int Increment) -{ - int nts = trickSpeed + Increment; - if (Speeds[nts] == 1) { - trickSpeed = nts; - if (playMode == pmFast) - Play(); - else - Pause(); - } - else if (Speeds[nts]) { - trickSpeed = nts; - int Mult = (playMode == pmSlow && playDir == pdForward) ? 1 : SPEED_MULT; - int sp = (Speeds[nts] > 0) ? Mult / Speeds[nts] : -Speeds[nts] * Mult; - if (sp > MAX_VIDEO_SLOWMOTION) - sp = MAX_VIDEO_SLOWMOTION; - CHECK(ioctl(videoDev, VIDEO_SLOWMOTION, sp)); - } -} - -void cPlayBuffer::Empty(bool Block) -{ - if (!(blockInput || blockOutput)) { - blockInput = blockOutput = 2; - EnablePut(); - EnableGet(); - time_t t0 = time(NULL); - while ((blockInput > 1 || blockOutput > 1) && time(NULL) - t0 < 2) - usleep(1); - Lock(); - if ((readIndex = backTrace.Get(playDir == pdForward)) < 0) - readIndex = writeIndex; - cRingBufferFrame::Clear(); - CHECK(ioctl(videoDev, VIDEO_CLEAR_BUFFER)); - CHECK(ioctl(audioDev, AUDIO_CLEAR_BUFFER)); - } - if (!Block) { - blockInput = blockOutput = 0; - backTrace.Clear(); - Unlock(); - } -} - -void cPlayBuffer::Pause(void) -{ - if (playMode == pmPause || playMode == pmStill) - Play(); - else { - bool empty = (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)); - if (empty) - Empty(true); - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, false)); - CHECK(ioctl(videoDev, VIDEO_FREEZE)); - playMode = pmPause; - if (empty) - Empty(false); - } -} - -void cPlayBuffer::Play(void) -{ - if (playMode != pmPlay) { - bool empty = (playMode == pmStill || playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)); - if (empty) - Empty(true); - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, true)); - CHECK(ioctl(videoDev, VIDEO_CONTINUE)); - playMode = pmPlay; - playDir = pdForward; - if (empty) - Empty(false); - } -} - -void cPlayBuffer::Forward(void) -{ - if (canDoTrickMode) { - switch (playMode) { - case pmFast: - if (Setup.MultiSpeedMode) { - TrickSpeed(playDir == pdForward ? 1 : -1); - break; - } - else if (playDir == pdForward) { - Play(); - break; - } - // run into pmPlay - case pmPlay: - Empty(true); - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, false)); - playMode = pmFast; - playDir = pdForward; - trickSpeed = NORMAL_SPEED; - TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS); - Empty(false); - break; - case pmSlow: - if (Setup.MultiSpeedMode) { - TrickSpeed(playDir == pdForward ? -1 : 1); - break; - } - else if (playDir == pdForward) { - Pause(); - break; - } - // run into pmPause - case pmStill: - case pmPause: - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, false)); - playMode = pmSlow; - playDir = pdForward; - trickSpeed = NORMAL_SPEED; - TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS); - break; - } - } -} - -void cPlayBuffer::Backward(void) -{ - if (canDoTrickMode) { - switch (playMode) { - case pmFast: - if (Setup.MultiSpeedMode) { - TrickSpeed(playDir == pdBackward ? 1 : -1); - break; - } - else if (playDir == pdBackward) { - Play(); - break; - } - // run into pmPlay - case pmPlay: - Empty(true); - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, false)); - playMode = pmFast; - playDir = pdBackward; - trickSpeed = NORMAL_SPEED; - TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS); - Empty(false); - break; - case pmSlow: - if (Setup.MultiSpeedMode) { - TrickSpeed(playDir == pdBackward ? -1 : 1); - break; - } - else if (playDir == pdBackward) { - Pause(); - break; - } - // run into pmPause - case pmStill: - case pmPause: - Empty(true); - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, false)); - playMode = pmSlow; - playDir = pdBackward; - trickSpeed = NORMAL_SPEED; - TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS); - Empty(false); - break; - } - } -} - -bool cPlayBuffer::GetReplayMode(bool &Play, bool &Forward, int &Speed) -{ - Play = (playMode == pmPlay || playMode == pmFast); - Forward = (playDir == pdForward); - if (playMode == pmFast || playMode == pmSlow) - Speed = Setup.MultiSpeedMode ? abs(trickSpeed - NORMAL_SPEED) : 0; - else - Speed = -1; - return true; -} - -void cPlayBuffer::ToggleAudioTrack(void) -{ - if (CanToggleAudioTrack()) { - audioTrack = (audioTrack == 0xC0) ? 0xC1 : 0xC0; - Empty(); - } -} - -// --- cReplayBuffer --------------------------------------------------------- - -class cReplayBuffer : public cPlayBuffer { -private: - cIndexFile *index; - cFileName fileName; - int replayFile; - bool eof; - bool NextFile(uchar FileNumber = 0, int FileOffset = -1); - void Close(void); - virtual void StripAudioPackets(uchar *b, int Length, uchar Except = 0x00); - void DisplayFrame(uchar *b, int Length); - int Resume(void); - bool Save(void); -protected: - virtual void Input(void); -public: - cReplayBuffer(cDvbApi *DvbApi, int VideoDev, int AudioDev, const char *FileName); - virtual ~cReplayBuffer(); - virtual int SkipFrames(int Frames); - virtual void SkipSeconds(int Seconds); - virtual void Goto(int Position, bool Still = false); - virtual void GetIndex(int &Current, int &Total, bool SnapToIFrame = false); - }; - -cReplayBuffer::cReplayBuffer(cDvbApi *DvbApi, int VideoDev, int AudioDev, const char *FileName) -:cPlayBuffer(DvbApi, VideoDev, AudioDev) -,fileName(FileName, false) -{ - index = NULL; - replayFile = fileName.Open(); - eof = false; - if (!fileName.Name()) - return; - // Create the index file: - index = new cIndexFile(FileName, false); - if (!index) - esyslog("ERROR: can't allocate index"); - else if (!index->Ok()) { - delete index; - index = NULL; - } - canDoTrickMode = index != NULL; - dvbApi->SetModeReplay(); - Start(); -} - -cReplayBuffer::~cReplayBuffer() -{ - Stop(); - Save(); - Close(); - dvbApi->SetModeNormal(false); - delete index; -} - -void cReplayBuffer::Input(void) -{ - dsyslog("input thread started (pid=%d)", getpid()); - - readIndex = Resume(); - if (readIndex >= 0) - isyslog("resuming replay at index %d (%s)", readIndex, IndexToHMSF(readIndex, true)); - - uchar b[MAXFRAMESIZE]; - while (Busy() && (blockInput || NextFile())) { - if (blockInput) { - if (blockInput > 1) - blockInput = 1; - continue; - } - if (playMode != pmStill) { - int r = 0; - if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) { - uchar FileNumber; - int FileOffset, Length; - int Index = index->GetNextIFrame(readIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length, true); - if (Index >= 0) { - if (!NextFile(FileNumber, FileOffset)) - break; - } - else { - // can't call Play() here, because those functions may only be - // called from the foreground thread - and we also don't need - // to empty the buffer here - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, true)); - CHECK(ioctl(videoDev, VIDEO_CONTINUE)); - playMode = pmPlay; - playDir = pdForward; - continue; - } - readIndex = Index; - r = ReadFrame(replayFile, b, Length, sizeof(b)); - // must call StripAudioPackets() here because the buffer is not emptied - // when falling back from "fast forward" to "play" (see above) - StripAudioPackets(b, r); - } - else if (index) { - uchar FileNumber; - int FileOffset, Length; - readIndex++; - if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) - break; - r = ReadFrame(replayFile, b, Length, sizeof(b)); - } - else // allows replay even if the index file is missing - r = read(replayFile, b, sizeof(b)); - if (r > 0) - putFrame(new cFrame(b, r, ftUnknown, readIndex)); - else if (r == 0) - eof = true; - else if (r < 0 && FATALERRNO) { - LOG_ERROR; - break; - } - } - else//XXX - usleep(1); // this keeps the CPU load low - } - - dsyslog("input thread ended (pid=%d)", getpid()); -} - -void cReplayBuffer::StripAudioPackets(uchar *b, int Length, uchar Except) -{ - if (canDoTrickMode) { - for (int i = 0; i < Length - 6; i++) { - if (b[i] == 0x00 && b[i + 1] == 0x00 && b[i + 2] == 0x01) { - uchar c = b[i + 3]; - int l = b[i + 4] * 256 + b[i + 5] + 6; - switch (c) { - case 0xBD: // dolby - if (Except) - PlayExternalDolby(&b[i], Length - i); - // continue with deleting the data - otherwise it disturbs DVB replay - case 0xC0 ... 0xC1: // audio - if (c == 0xC1) - canToggleAudioTrack = true; - if (!Except || c != Except) { - int n = l; - for (int j = i; j < Length && n--; j++) - b[j] = 0x00; - } - break; - case 0xE0 ... 0xEF: // video - break; - default: - //esyslog("ERROR: unexpected packet id %02X", c); - l = 0; - } - if (l) - i += l - 1; // the loop increments, too! - } - /*XXX - else - esyslog("ERROR: broken packet header"); - XXX*/ - } - } -} - -void cReplayBuffer::DisplayFrame(uchar *b, int Length) -{ - StripAudioPackets(b, Length); - CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, false)); - CHECK(ioctl(audioDev, AUDIO_SET_MUTE, true)); -/* Using the VIDEO_STILLPICTURE ioctl call would be the - correct way to display a still frame, but unfortunately this - doesn't work with frames from VDR. So let's do pretty much the - same here as in DVB/driver/dvb.c's play_iframe() - I have absolutely - no idea why it works this way, but doesn't work with VIDEO_STILLPICTURE. - If anybody ever finds out what could be changed so that VIDEO_STILLPICTURE - could be used, please let me know! - kls 2002-03-23 -*/ -//#define VIDEO_STILLPICTURE_WORKS_WITH_VDR_FRAMES -#ifdef VIDEO_STILLPICTURE_WORKS_WITH_VDR_FRAMES - videoDisplayStillPicture sp = { (char *)b, Length }; - CHECK(ioctl(videoDev, VIDEO_STILLPICTURE, &sp)); -#else -#define MIN_IFRAME 400000 - for (int i = MIN_IFRAME / Length + 1; i > 0; i--) { - safe_write(videoDev, b, Length); - usleep(1); // allows the buffer to be displayed in case the progress display is active - } -#endif -} - -void cReplayBuffer::Close(void) -{ - if (replayFile >= 0) { - fileName.Close(); - replayFile = -1; - } -} - -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 = writeIndex; - if (Index >= 0) { - Index -= RESUMEBACKUP; - if (Index > 0) - Index = index->GetNextIFrame(Index, false); - else - Index = 0; - if (Index >= 0) - return index->StoreResume(Index); - } - } - return false; -} - -int cReplayBuffer::SkipFrames(int Frames) -{ - if (index && Frames) { - int Current, Total; - GetIndex(Current, Total, true); - int OldCurrent = Current; - Current = index->GetNextIFrame(Current + Frames, Frames > 0); - return Current >= 0 ? Current : OldCurrent; - } - return -1; -} - -void cReplayBuffer::SkipSeconds(int Seconds) -{ - if (index && Seconds) { - Empty(true); - int Index = writeIndex; - if (Index >= 0) { - Index = max(Index + Seconds * FRAMESPERSEC, 0); - if (Index > 0) - Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL, true); - if (Index >= 0) - readIndex = writeIndex = Index - 1; // Input() will first increment it! - } - Empty(false); - Play(); - } -} - -void cReplayBuffer::Goto(int Index, bool Still) -{ - if (index) { - Empty(true); - if (++Index <= 0) - Index = 1; // not '0', to allow GetNextIFrame() below to work! - uchar FileNumber; - int FileOffset, Length; - Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length); - if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) { - uchar b[MAXFRAMESIZE]; - int r = ReadFrame(replayFile, b, Length, sizeof(b)); - if (r > 0) { - if (playMode == pmPause) - CHECK(ioctl(videoDev, VIDEO_CONTINUE)); - DisplayFrame(b, r); - } - playMode = pmStill; - } - readIndex = writeIndex = Index; - Empty(false); - } -} - -void cReplayBuffer::GetIndex(int &Current, int &Total, bool SnapToIFrame) -{ - if (index) { - if (playMode == pmStill) - Current = max(readIndex, 0); - else { - Current = max(writeIndex, 0); - if (SnapToIFrame) { - int i1 = index->GetNextIFrame(Current + 1, false); - int i2 = index->GetNextIFrame(Current, true); - Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2; - } - } - Total = index->Last(); - } - else - Current = Total = -1; -} - -bool cReplayBuffer::NextFile(uchar FileNumber, int FileOffset) -{ - if (FileNumber > 0) - replayFile = fileName.SetOffset(FileNumber, FileOffset); - else if (replayFile >= 0 && eof) { - Close(); - replayFile = fileName.NextFile(); - } - eof = false; - return replayFile >= 0; -} - -// --- cTransferBuffer ------------------------------------------------------- - -class cTransferBuffer : public cRingBufferLinear { -private: - cDvbApi *dvbApi; - int fromDevice, toDevice; - bool gotBufferReserve; - cRemux remux; -protected: - virtual void Input(void); - virtual void Output(void); -public: - cTransferBuffer(cDvbApi *DvbApi, int ToDevice, int VPid, int APid); - virtual ~cTransferBuffer(); - void SetAudioPid(int APid); - }; - -cTransferBuffer::cTransferBuffer(cDvbApi *DvbApi, int ToDevice, int VPid, int APid) -:cRingBufferLinear(VIDEOBUFSIZE, true) -,remux(VPid, APid, 0, 0, 0) -{ - dvbApi = DvbApi; - fromDevice = dvbApi->SetModeRecord(); - toDevice = ToDevice; - gotBufferReserve = false; - Start(); -} - -cTransferBuffer::~cTransferBuffer() -{ - Stop(); - dvbApi->SetModeNormal(true); -} - -void cTransferBuffer::SetAudioPid(int APid) -{ - Clear(); - //XXX we may need to have access to the audio device, too, in order to clear it - CHECK(ioctl(toDevice, VIDEO_CLEAR_BUFFER)); - gotBufferReserve = false; - remux.SetAudioPid(APid); -} - -void cTransferBuffer::Input(void) -{ - dsyslog("input thread started (pid=%d)", getpid()); - - uchar b[MINVIDEODATA]; - int n = 0; - while (Busy()) { - if (cFile::FileReady(fromDevice, 100)) { - int r = read(fromDevice, b + n, sizeof(b) - n); - if (r > 0) { - n += r; - int Count = n, Result; - const uchar *p = remux.Process(b, Count, Result); - if (p) { - while (Result > 0 && Busy()) { - int w = Put(p, Result); - p += w; - Result -= w; - } - } - if (Count > 0) { - n -= Count; - memmove(b, b + Count, n); - } - } - else if (r < 0) { - if (FATALERRNO) { - if (errno == EBUFFEROVERFLOW) // this error code is not defined in the library - esyslog("ERROR (%s,%d): DVB driver buffer overflow", __FILE__, __LINE__); - else { - LOG_ERROR; - break; - } - } - } - } - } - - dsyslog("input thread ended (pid=%d)", getpid()); -} - -void cTransferBuffer::Output(void) -{ - dsyslog("output thread started (pid=%d)", getpid()); - - uchar b[MINVIDEODATA]; - while (Busy()) { - if (!gotBufferReserve) { - if (Available() < MAXFRAMESIZE) { - usleep(100000); // allow the buffer to collect some reserve - continue; - } - else - gotBufferReserve = true; - } - int r = Get(b, sizeof(b)); - if (r > 0) { - uchar *p = b; - while (r > 0 && Busy() && cFile::FileReadyForWriting(toDevice, 100)) { - int w = write(toDevice, p, r); - if (w > 0) { - p += w; - r -= w; - } - else if (w < 0 && FATALERRNO) { - LOG_ERROR; - Stop(); - return; - } - } - } - else - usleep(1); // this keeps the CPU load low - } - - dsyslog("output thread ended (pid=%d)", getpid()); -} - -// --- cCuttingBuffer -------------------------------------------------------- - -class cCuttingBuffer : public cThread { -private: - const char *error; - bool active; - int fromFile, toFile; - cFileName *fromFileName, *toFileName; - cIndexFile *fromIndex, *toIndex; - cMarks fromMarks, toMarks; -protected: - virtual void Action(void); -public: - cCuttingBuffer(const char *FromFileName, const char *ToFileName); - virtual ~cCuttingBuffer(); - const char *Error(void) { return error; } - }; - -cCuttingBuffer::cCuttingBuffer(const char *FromFileName, const char *ToFileName) -{ - error = NULL; - active = false; - fromFile = toFile = -1; - fromFileName = toFileName = NULL; - fromIndex = toIndex = NULL; - if (fromMarks.Load(FromFileName) && fromMarks.Count()) { - fromFileName = new cFileName(FromFileName, false, true); - toFileName = new cFileName(ToFileName, true, true); - fromIndex = new cIndexFile(FromFileName, false); - toIndex = new cIndexFile(ToFileName, true); - toMarks.Load(ToFileName); // doesn't actually load marks, just sets the file name - Start(); - } - else - esyslog("no editing marks found for %s", FromFileName); -} - -cCuttingBuffer::~cCuttingBuffer() -{ - active = false; - Cancel(3); - delete fromFileName; - delete toFileName; - delete fromIndex; - delete toIndex; -} - -void cCuttingBuffer::Action(void) -{ - dsyslog("video cutting thread started (pid=%d)", getpid()); - - cMark *Mark = fromMarks.First(); - if (Mark) { - fromFile = fromFileName->Open(); - toFile = toFileName->Open(); - active = fromFile >= 0 && toFile >= 0; - int Index = Mark->position; - Mark = fromMarks.Next(Mark); - int FileSize = 0; - int CurrentFileNumber = 0; - int LastIFrame = 0; - toMarks.Add(0); - toMarks.Save(); - uchar buffer[MAXFRAMESIZE]; - while (active) { - uchar FileNumber; - int FileOffset, Length; - uchar PictureType; - - // Make sure there is enough disk space: - - AssertFreeDiskSpace(); - - // Read one frame: - - if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) { - if (FileNumber != CurrentFileNumber) { - fromFile = fromFileName->SetOffset(FileNumber, FileOffset); - CurrentFileNumber = FileNumber; - } - if (fromFile >= 0) { - Length = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); - if (Length < 0) { - error = "ReadFrame"; - break; - } - } - else { - error = "fromFile"; - break; - } - } - else - break; - - // Write one frame: - - if (PictureType == I_FRAME) { // every file shall start with an I_FRAME - if (!Mark) // edited version shall end before next I-frame - break; - if (FileSize > MEGABYTE(Setup.MaxVideoFileSize)) { - toFile = toFileName->NextFile(); - if (toFile < 0) { - error = "toFile 1"; - break; - } - FileSize = 0; - } - LastIFrame = 0; - } - if (safe_write(toFile, buffer, Length) < 0) { - error = "safe_write"; - break; - } - if (!toIndex->Write(PictureType, toFileName->Number(), FileSize)) { - error = "toIndex"; - break; - } - FileSize += Length; - if (!LastIFrame) - LastIFrame = toIndex->Last(); - - // Check editing marks: - - if (Mark && Index >= Mark->position) { - Mark = fromMarks.Next(Mark); - toMarks.Add(LastIFrame); - if (Mark) - toMarks.Add(toIndex->Last() + 1); - toMarks.Save(); - if (Mark) { - Index = Mark->position; - Mark = fromMarks.Next(Mark); - CurrentFileNumber = 0; // triggers SetOffset before reading next frame - if (Setup.SplitEditedFiles) { - toFile = toFileName->NextFile(); - if (toFile < 0) { - error = "toFile 2"; - break; - } - FileSize = 0; - } - } - // the 'else' case (i.e. 'final end mark reached') is handled above - // in 'Write one frame', so that the edited version will end right - // before the next I-frame. - } - } - } - else - esyslog("no editing marks found!"); - dsyslog("end video cutting thread"); -} - -// --- cVideoCutter ---------------------------------------------------------- - -char *cVideoCutter::editedVersionName = NULL; -cCuttingBuffer *cVideoCutter::cuttingBuffer = NULL; -bool cVideoCutter::error = false; -bool cVideoCutter::ended = false; - -bool cVideoCutter::Start(const char *FileName) -{ - if (!cuttingBuffer) { - error = false; - ended = false; - cRecording Recording(FileName); - const char *evn = Recording.PrefixFileName('%'); - if (evn && RemoveVideoFile(evn) && MakeDirs(evn, true)) { - // XXX this can be removed once RenameVideoFile() follows symlinks (see videodir.c) - // remove a possible deleted recording with the same name to avoid symlink mixups: - char *s = strdup(evn); - char *e = strrchr(s, '.'); - if (e) { - if (strcmp(e, ".rec") == 0) { - strcpy(e, ".del"); - RemoveVideoFile(s); - } - } - delete s; - // XXX - editedVersionName = strdup(evn); - Recording.WriteSummary(); - cuttingBuffer = new cCuttingBuffer(FileName, editedVersionName); - return true; - } - } - return false; -} - -void cVideoCutter::Stop(void) -{ - bool Interrupted = cuttingBuffer && cuttingBuffer->Active(); - const char *Error = cuttingBuffer ? cuttingBuffer->Error() : NULL; - delete cuttingBuffer; - cuttingBuffer = NULL; - if ((Interrupted || Error) && editedVersionName) { - if (Interrupted) - isyslog("editing process has been interrupted"); - if (Error) - esyslog("ERROR: '%s' during editing process", Error); - RemoveVideoFile(editedVersionName); //XXX what if this file is currently being replayed? - } -} - -bool cVideoCutter::Active(void) -{ - if (cuttingBuffer) { - if (cuttingBuffer->Active()) - return true; - error = cuttingBuffer->Error(); - Stop(); - if (!error) - cRecordingUserCommand::InvokeCommand(RUC_EDITEDRECORDING, editedVersionName); - delete editedVersionName; - editedVersionName = NULL; - ended = true; - } - return false; -} - -bool cVideoCutter::Error(void) -{ - bool result = error; - error = false; - return result; -} - -bool cVideoCutter::Ended(void) -{ - bool result = ended; - ended = false; - return result; -} - -// --- cDvbApi --------------------------------------------------------------- - -static const char *OstName(const char *Name, int n) -{ - static char buffer[_POSIX_PATH_MAX]; - snprintf(buffer, sizeof(buffer), "%s%d", Name, n); - return buffer; -} - -static int OstOpen(const char *Name, int n, int Mode, bool ReportError = false) -{ - const char *FileName = OstName(Name, n); - int fd = open(FileName, Mode); - if (fd < 0 && ReportError) - LOG_ERROR_STR(FileName); - return fd; -} - -int cDvbApi::NumDvbApis = 0; -int cDvbApi::useDvbApi = 0; -cDvbApi *cDvbApi::dvbApi[MAXDVBAPI] = { NULL }; -cDvbApi *cDvbApi::PrimaryDvbApi = NULL; -char *cDvbApi::audioCommand = NULL; - -cDvbApi::cDvbApi(int n) -{ - frontendType = FrontendType(-1); // don't know how else to initialize this - there is no FE_UNKNOWN - vPid = aPid1 = aPid2 = dPid1 = dPid2 = 0; - siProcessor = NULL; - recordBuffer = NULL; - replayBuffer = NULL; - transferBuffer = NULL; - transferringFromDvbApi = NULL; - ca = -1; - priority = DEFAULTPRIORITY; - cardIndex = n; - - // Devices that are only present on DVB-C or DVB-S cards: - - fd_frontend = OstOpen(DEV_OST_FRONTEND, n, O_RDWR); - fd_sec = OstOpen(DEV_OST_SEC, n, O_RDWR); - - // Devices that all DVB cards must have: - - fd_demuxv = OstOpen(DEV_OST_DEMUX, n, O_RDWR | O_NONBLOCK, true); - fd_demuxa1 = OstOpen(DEV_OST_DEMUX, n, O_RDWR | O_NONBLOCK, true); - fd_demuxa2 = OstOpen(DEV_OST_DEMUX, n, O_RDWR | O_NONBLOCK, true); - fd_demuxd1 = OstOpen(DEV_OST_DEMUX, n, O_RDWR | O_NONBLOCK, true); - fd_demuxd2 = OstOpen(DEV_OST_DEMUX, n, O_RDWR | O_NONBLOCK, true); - fd_demuxt = OstOpen(DEV_OST_DEMUX, n, O_RDWR | O_NONBLOCK, true); - - // Devices not present on "budget" cards: - - fd_osd = OstOpen(DEV_OST_OSD, n, O_RDWR); - fd_video = OstOpen(DEV_OST_VIDEO, n, O_RDWR | O_NONBLOCK); - fd_audio = OstOpen(DEV_OST_AUDIO, n, O_RDWR | O_NONBLOCK); - - // Devices that will be dynamically opened and closed when necessary: - - fd_dvr = -1; - - // Video format: - - SetVideoFormat(Setup.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3); - - // We only check the devices that must be present - the others will be checked before accessing them: - - if (fd_frontend >= 0 && fd_demuxv >= 0 && fd_demuxa1 >= 0 && fd_demuxa2 >= 0 && fd_demuxd1 >= 0 && fd_demuxd2 >= 0 && fd_demuxt >= 0) { - siProcessor = new cSIProcessor(OstName(DEV_OST_DEMUX, n)); - FrontendInfo feinfo; - CHECK(ioctl(fd_frontend, FE_GET_INFO, &feinfo)); - frontendType = feinfo.type; - } - else - esyslog("ERROR: can't open video device %d", n); - cols = rows = 0; - -#if defined(DEBUG_OSD) || defined(REMOTE_KBD) - initscr(); - keypad(stdscr, true); - nonl(); - cbreak(); - noecho(); - timeout(10); -#endif -#if defined(DEBUG_OSD) - memset(&colorPairs, 0, sizeof(colorPairs)); - start_color(); - leaveok(stdscr, true); - window = NULL; -#else - osd = NULL; -#endif - currentChannel = 1; - mute = false; - volume = Setup.CurrentVolume; -} - -cDvbApi::~cDvbApi() -{ - delete siProcessor; - Close(); - StopReplay(); - StopRecord(); - StopTransfer(); - // We're not explicitly closing any device files here, since this sometimes - // caused segfaults. Besides, the program is about to terminate anyway... -#if defined(DEBUG_OSD) || defined(REMOTE_KBD) - endwin(); -#endif -} - -void cDvbApi::SetUseDvbApi(int n) -{ - if (n < MAXDVBAPI) - useDvbApi |= (1 << n); -} - -bool cDvbApi::SetPrimaryDvbApi(int n) -{ - n--; - if (0 <= n && n < NumDvbApis && dvbApi[n]) { - isyslog("setting primary DVB to %d", n + 1); - PrimaryDvbApi = dvbApi[n]; - return true; - } - esyslog("invalid DVB interface: %d", n + 1); - return false; -} - -int cDvbApi::CanShift(int Ca, int Priority, int UsedCards) -{ - // Test whether a recording on this DVB device can be shifted to another one - // in order to perform a new recording with the given Ca and Priority on this device: - int ShiftLevel = -1; // default means this device can't be shifted - if (UsedCards & (1 << CardIndex()) != 0) - return ShiftLevel; // otherwise we would get into a loop - if (Recording()) { - if (ProvidesCa(Ca) // this device provides the requested Ca - && (Ca != this->Ca() // the requested Ca is different from the one currently used... - || Priority > this->Priority())) { // ...or the request comes from a higher priority - cDvbApi *d = NULL; - int Provides[MAXDVBAPI]; - UsedCards |= (1 << CardIndex()); - for (int i = 0; i < NumDvbApis; i++) { - if ((Provides[i] = dvbApi[i]->ProvidesCa(this->Ca())) != 0) { // this device is basicly able to do the job - if (dvbApi[i] != this) { // it is not _this_ device - int sl = dvbApi[i]->CanShift(this->Ca(), Priority, UsedCards); // this is the original Priority! - if (sl >= 0 && (ShiftLevel < 0 || sl < ShiftLevel)) { - d = dvbApi[i]; - ShiftLevel = sl; - } - } - } - } - if (ShiftLevel >= 0) - ShiftLevel++; // adds the device's own shift - } - } - else if (Priority > this->Priority()) - ShiftLevel = 0; // no shifting necessary, this device can do the job - return ShiftLevel; -} - -cDvbApi *cDvbApi::GetDvbApi(int Ca, int Priority) -{ - cDvbApi *d = NULL; - int Provides[MAXDVBAPI]; - // Check which devices provide Ca: - for (int i = 0; i < NumDvbApis; i++) { - if ((Provides[i] = dvbApi[i]->ProvidesCa(Ca)) != 0) { // this device is basicly able to do the job - if (Priority > dvbApi[i]->Priority() // Priority is high enough to use this device - && (!d // we don't have a device yet, or... - || dvbApi[i]->Priority() < d->Priority() // ...this one has an even lower Priority - || (dvbApi[i]->Priority() == d->Priority() // ...same Priority... - && Provides[i] < Provides[d->CardIndex()]))) // ...but this one provides fewer Ca values - d = dvbApi[i]; - } - } - if (!d && Ca > MAXDVBAPI) { - // We didn't find one the easy way, so now we have to try harder: - int ShiftLevel = -1; - for (int i = 0; i < NumDvbApis; i++) { - if (Provides[i]) { // this device is basicly able to do the job, but for some reason we didn't get it above - int sl = dvbApi[i]->CanShift(Ca, Priority); // asks this device to shift its job to another device - if (sl >= 0 && (ShiftLevel < 0 || sl < ShiftLevel)) { - d = dvbApi[i]; // found one that can be shifted with the fewest number of subsequent shifts - ShiftLevel = sl; - } - } - } - } - return d; -} - -void cDvbApi::SetCaCaps(void) -{ - for (int d = 0; d < NumDvbApis; d++) { - for (int i = 0; i < MAXCACAPS; i++) - dvbApi[d]->caCaps[i] = Setup.CaCaps[dvbApi[d]->CardIndex()][i]; - } -} - -int cDvbApi::ProvidesCa(int Ca) -{ - if (Ca == CardIndex() + 1) - return 1; // exactly _this_ card was requested - if (Ca && Ca <= MAXDVBAPI) - return 0; // a specific card was requested, but not _this_ one - int result = Ca ? 0 : 1; // by default every card can provide FTA - int others = Ca ? 1 : 0; - for (int i = 0; i < MAXCACAPS; i++) { - if (caCaps[i]) { - if (caCaps[i] == Ca) - result = 1; - else - others++; - } - } - return result ? result + others : 0; -} - -bool cDvbApi::Probe(const char *FileName) -{ - if (access(FileName, F_OK) == 0) { - dsyslog("probing %s", FileName); - int f = open(FileName, O_RDONLY); - if (f >= 0) { - close(f); - return true; - } - else if (errno != ENODEV && errno != EINVAL) - LOG_ERROR_STR(FileName); - } - else if (errno != ENOENT) - LOG_ERROR_STR(FileName); - return false; -} - -bool cDvbApi::Init(void) -{ - NumDvbApis = 0; - for (int i = 0; i < MAXDVBAPI; i++) { - if (useDvbApi == 0 || (useDvbApi & (1 << i)) != 0) { - if (Probe(OstName(DEV_OST_FRONTEND, i))) - dvbApi[NumDvbApis++] = new cDvbApi(i); - else - break; - } - } - PrimaryDvbApi = dvbApi[0]; - if (NumDvbApis > 0) - isyslog("found %d video device%s", NumDvbApis, NumDvbApis > 1 ? "s" : ""); - else - esyslog("ERROR: no video device found, giving up!"); - SetCaCaps(); - return NumDvbApis > 0; -} - -void cDvbApi::Cleanup(void) -{ - for (int i = 0; i < NumDvbApis; i++) { - delete dvbApi[i]; - dvbApi[i] = NULL; - } - PrimaryDvbApi = NULL; -} - -bool cDvbApi::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY) -{ - int videoDev = OstOpen(DEV_VIDEO, CardIndex(), O_RDWR, true); - if (videoDev >= 0) { - int result = 0; - struct video_mbuf mbuf; - result |= ioctl(videoDev, VIDIOCGMBUF, &mbuf); - if (result == 0) { - int msize = mbuf.size; - unsigned char *mem = (unsigned char *)mmap(0, msize, PROT_READ | PROT_WRITE, MAP_SHARED, videoDev, 0); - if (mem && mem != (unsigned char *)-1) { - // 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; - 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("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; - } - munmap(mem, msize); - } - else - result |= 1; - } - close(videoDev); - return result == 0; - } - return false; -} - -#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; - } - } -} -#endif - -void cDvbApi::Open(int w, int h) -{ - int d = (h < 0) ? Setup.OSDheight + h : 0; - h = abs(h); - cols = w; - rows = h; -#ifdef DEBUG_OSD - window = subwin(stdscr, h, w, d, (Setup.OSDwidth - w) / 2); - 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)) - //XXX - 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); -#else - w *= charWidth; - h *= lineHeight; - d *= lineHeight; - int x = (720 - w + charWidth) / 2; //TODO PAL vs. NTSC??? - int y = (576 - Setup.OSDheight * lineHeight) / 2 + d; - //XXX - osd = new cDvbOsd(fd_osd, x, y); - //XXX TODO this should be transferred to the places where the individual windows are requested (there's too much detailed knowledge here!) - if (h / lineHeight == 5) { //XXX channel display - osd->Create(0, 0, w, h, 4); - } - else if (h / lineHeight == 1) { //XXX info display - osd->Create(0, 0, w, h, 4); - } - else if (d == 0) { //XXX full menu - osd->Create(0, 0, w, lineHeight, 2); - osd->Create(0, lineHeight, w, (Setup.OSDheight - 3) * lineHeight, 2); - osd->AddColor(clrBackground); - osd->AddColor(clrCyan); - osd->AddColor(clrWhite); - osd->AddColor(clrBlack); - osd->Create(0, (Setup.OSDheight - 2) * lineHeight, w, 2 * lineHeight, 4); - } - else { //XXX progress display - /*XXX - osd->Create(0, 0, w, lineHeight, 1); - osd->Create(0, lineHeight, w, lineHeight, 2, false); - osd->Create(0, 2 * lineHeight, w, lineHeight, 1); - XXX*///XXX some pixels are not drawn correctly with lower bpp values - osd->Create(0, 0, w, 3*lineHeight, 4); - } -#endif -} - -void cDvbApi::Close(void) -{ -#ifdef DEBUG_OSD - if (window) { - delwin(window); - window = 0; - } -#else - delete osd; - osd = NULL; -#endif -} - -void cDvbApi::Clear(void) -{ -#ifdef DEBUG_OSD - SetColor(clrBackground, clrBackground); - Fill(0, 0, cols, rows, clrBackground); -#else - osd->Clear(); -#endif -} - -void cDvbApi::Fill(int x, int y, int w, int h, eDvbColor color) -{ - 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); - } - wsyncup(window); // shouldn't be necessary because of 'syncok()', but w/o it doesn't work -#else - osd->Fill(x * charWidth, y * lineHeight, (x + w) * charWidth - 1, (y + h) * lineHeight - 1, color); -#endif -} - -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) -{ - Fill(x, y, cols - x, 1, color); -} - -int cDvbApi::CellWidth(void) -{ -#ifdef DEBUG_OSD - return 1; -#else - return charWidth; -#endif -} - -int cDvbApi::LineHeight(void) -{ -#ifdef DEBUG_OSD - return 1; -#else - return lineHeight; -#endif -} - -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 -} - -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) -{ - 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 - osd->Text(x * charWidth, y * lineHeight, s, colorFg, colorBg); -#endif -} - -void cDvbApi::Flush(void) -{ -#ifdef DEBUG_OSD - refresh(); -#else - if (osd) - osd->Flush(); -#endif -} - -int cDvbApi::Priority(void) -{ - return (this == PrimaryDvbApi && !Recording()) ? Setup.PrimaryLimit - 1 : priority; -} - -int cDvbApi::SetModeRecord(void) -{ - // Sets up the DVB device for recording - - SetPids(true); - if (fd_dvr >= 0) - close(fd_dvr); - fd_dvr = OstOpen(DEV_OST_DVR, CardIndex(), O_RDONLY | O_NONBLOCK); - if (fd_dvr < 0) - LOG_ERROR; - return fd_dvr; -} - -void cDvbApi::SetModeReplay(void) -{ - // Sets up the DVB device for replay - - if (fd_video >= 0 && fd_audio >= 0) { - if (siProcessor) - siProcessor->SetStatus(false); - CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true)); - CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_MEMORY)); - CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); - CHECK(ioctl(fd_audio, AUDIO_PLAY)); - CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY)); - CHECK(ioctl(fd_video, VIDEO_PLAY)); - } -} - -void cDvbApi::SetModeNormal(bool FromRecording) -{ - // Puts the DVB device back into "normal" viewing mode (after replay or recording) - - if (FromRecording) { - close(fd_dvr); - fd_dvr = -1; - SetPids(false); - } - else { - if (fd_video >= 0 && fd_audio >= 0) { - CHECK(ioctl(fd_video, VIDEO_STOP, true)); - CHECK(ioctl(fd_audio, AUDIO_STOP, true)); - CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER)); - CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER)); - CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX)); - CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_DEMUX)); - CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); - CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false)); - if (siProcessor) - siProcessor->SetStatus(true); - } - } -} - -void cDvbApi::SetVideoFormat(videoFormat_t Format) -{ - if (fd_video >= 0) - CHECK(ioctl(fd_video, VIDEO_SET_FORMAT, Format)); -} - -bool cDvbApi::SetPid(int fd, dmxPesType_t PesType, int Pid, dmxOutput_t Output) -{ - if (Pid) { - CHECK(ioctl(fd, DMX_STOP)); - if (Pid != 0x1FFF) { - dmxPesFilterParams pesFilterParams; - pesFilterParams.pid = Pid; - pesFilterParams.input = DMX_IN_FRONTEND; - pesFilterParams.output = Output; - pesFilterParams.pesType = PesType; - pesFilterParams.flags = DMX_IMMEDIATE_START; - if (ioctl(fd, DMX_SET_PES_FILTER, &pesFilterParams) < 0) { - LOG_ERROR; - return false; - } - } - } - return true; -} - -bool cDvbApi::SetPids(bool ForRecording) -{ - return SetVpid(vPid, ForRecording ? DMX_OUT_TS_TAP : DMX_OUT_DECODER) && - SetApid1(aPid1, ForRecording ? DMX_OUT_TS_TAP : DMX_OUT_DECODER) && - SetApid2(ForRecording ? aPid2 : 0, DMX_OUT_TS_TAP) && (!Setup.RecordDolbyDigital || - SetDpid1(ForRecording ? dPid1 : 0, DMX_OUT_TS_TAP) && - SetDpid2(ForRecording ? dPid2 : 0, DMX_OUT_TS_TAP)); -} - -eSetChannelResult cDvbApi::SetChannel(int ChannelNumber, int Frequency, char Polarization, int Diseqc, int Srate, int Vpid, int Apid1, int Apid2, int Dpid1, int Dpid2, int Tpid, int Ca, int Pnr) -{ - StopTransfer(); - StopReplay(); - - // Must set this anyway to avoid getting stuck when switching through - // channels with 'Up' and 'Down' keys: - currentChannel = ChannelNumber; - vPid = Vpid; - aPid1 = Apid1; - aPid2 = Apid2; - dPid1 = Dpid1; - dPid2 = Dpid2; - - // Avoid noise while switching: - - if (fd_video >= 0 && fd_audio >= 0) { - CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true)); - CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true)); - CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER)); - CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER)); - } - - // Stop setting system time: - - if (siProcessor) - siProcessor->SetCurrentTransponder(0); - - // If this card can't receive this channel, we must not actually switch - // the channel here, because that would irritate the driver when we - // start replaying in Transfer Mode immediately after switching the channel: - bool NeedsTransferMode = (this == PrimaryDvbApi && !ProvidesCa(Ca)); - - if (!NeedsTransferMode) { - - // Turn off current PIDs: - - SetVpid( 0x1FFF, DMX_OUT_DECODER); - SetApid1(0x1FFF, DMX_OUT_DECODER); - SetApid2(0x1FFF, DMX_OUT_DECODER); - SetDpid1(0x1FFF, DMX_OUT_DECODER); - SetDpid2(0x1FFF, DMX_OUT_DECODER); - SetTpid( 0x1FFF, DMX_OUT_DECODER); - - FrontendParameters Frontend; - - switch (frontendType) { - case FE_QPSK: { // DVB-S - - // Frequency offsets: - - unsigned int freq = Frequency; - int tone = SEC_TONE_OFF; - - if (freq < (unsigned int)Setup.LnbSLOF) { - freq -= Setup.LnbFrequLo; - tone = SEC_TONE_OFF; - } - else { - freq -= Setup.LnbFrequHi; - tone = SEC_TONE_ON; - } - - Frontend.Frequency = freq * 1000UL; - Frontend.Inversion = INVERSION_AUTO; - Frontend.u.qpsk.SymbolRate = Srate * 1000UL; - Frontend.u.qpsk.FEC_inner = FEC_AUTO; - - int volt = (Polarization == 'v' || Polarization == 'V') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18; - - // DiseqC: - - secCommand scmd; - scmd.type = 0; - scmd.u.diseqc.addr = 0x10; - scmd.u.diseqc.cmd = 0x38; - scmd.u.diseqc.numParams = 1; - scmd.u.diseqc.params[0] = 0xF0 | ((Diseqc * 4) & 0x0F) | (tone == SEC_TONE_ON ? 1 : 0) | (volt == SEC_VOLTAGE_18 ? 2 : 0); - - secCmdSequence scmds; - scmds.voltage = volt; - scmds.miniCommand = SEC_MINI_NONE; - scmds.continuousTone = tone; - scmds.numCommands = Setup.DiSEqC ? 1 : 0; - scmds.commands = &scmd; - - CHECK(ioctl(fd_sec, SEC_SEND_SEQUENCE, &scmds)); - } - break; - case FE_QAM: { // DVB-C - - // Frequency and symbol rate: - - Frontend.Frequency = Frequency * 1000000UL; - Frontend.Inversion = INVERSION_AUTO; - Frontend.u.qam.SymbolRate = Srate * 1000UL; - Frontend.u.qam.FEC_inner = FEC_AUTO; - Frontend.u.qam.QAM = QAM_64; - } - break; - case FE_OFDM: { // DVB-T - - // Frequency and OFDM paramaters: - - Frontend.Frequency = Frequency * 1000UL; - Frontend.Inversion = INVERSION_AUTO; - Frontend.u.ofdm.bandWidth=BANDWIDTH_8_MHZ; - Frontend.u.ofdm.HP_CodeRate=FEC_2_3; - Frontend.u.ofdm.LP_CodeRate=FEC_1_2; - Frontend.u.ofdm.Constellation=QAM_64; - Frontend.u.ofdm.TransmissionMode=TRANSMISSION_MODE_2K; - Frontend.u.ofdm.guardInterval=GUARD_INTERVAL_1_32; - Frontend.u.ofdm.HierarchyInformation=HIERARCHY_NONE; - } - break; - default: - esyslog("ERROR: attempt to set channel with unknown DVB frontend type"); - return scrFailed; - } - - // Tuning: - - CHECK(ioctl(fd_frontend, FE_SET_FRONTEND, &Frontend)); - - // Wait for channel sync: - - if (cFile::FileReady(fd_frontend, 5000)) { - FrontendEvent event; - int res = ioctl(fd_frontend, FE_GET_EVENT, &event); - if (res >= 0) { - if (event.type != FE_COMPLETION_EV) { - esyslog("ERROR: channel %d not sync'ed on DVB card %d!", ChannelNumber, CardIndex() + 1); - if (this == PrimaryDvbApi) - cThread::RaisePanic(); - return scrFailed; - } - } - else - esyslog("ERROR %d in frontend get event (channel %d, card %d)", res, ChannelNumber, CardIndex() + 1); - } - else - esyslog("ERROR: timeout while tuning"); - - // PID settings: - - if (!SetPids(false)) { - esyslog("ERROR: failed to set PIDs for channel %d", ChannelNumber); - return scrFailed; - } - SetTpid(Tpid, DMX_OUT_DECODER); - if (fd_audio >= 0) - CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); - } - - if (this == PrimaryDvbApi && siProcessor) - siProcessor->SetCurrentServiceID(Pnr); - - eSetChannelResult Result = scrOk; - - // 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 (NeedsTransferMode) { - cDvbApi *CaDvbApi = GetDvbApi(Ca, 0); - if (CaDvbApi && !CaDvbApi->Recording()) { - if ((Result = CaDvbApi->SetChannel(ChannelNumber, Frequency, Polarization, Diseqc, Srate, Vpid, Apid1, Apid2, Dpid1, Dpid2, Tpid, Ca, Pnr)) == scrOk) { - SetModeReplay(); - transferringFromDvbApi = CaDvbApi->StartTransfer(fd_video); - } - } - else - Result = scrNoTransfer; - } - - if (fd_video >= 0 && fd_audio >= 0) { - CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false)); - CHECK(ioctl(fd_video, VIDEO_SET_BLANK, false)); - } - - // Start setting system time: - - if (Result == scrOk && siProcessor) - siProcessor->SetCurrentTransponder(Frequency); - - return Result; -} - -bool cDvbApi::Transferring(void) -{ - return transferBuffer; -} - -cDvbApi *cDvbApi::StartTransfer(int TransferToVideoDev) -{ - StopTransfer(); - transferBuffer = new cTransferBuffer(this, TransferToVideoDev, vPid, aPid1); - return this; -} - -void cDvbApi::StopTransfer(void) -{ - if (transferBuffer) { - delete transferBuffer; - transferBuffer = NULL; - } - if (transferringFromDvbApi) { - transferringFromDvbApi->StopTransfer(); - transferringFromDvbApi = NULL; - } -} - -int cDvbApi::SecondsToFrames(int Seconds) -{ - return Seconds * FRAMESPERSEC; -} - -bool cDvbApi::Recording(void) -{ - if (recordBuffer && !recordBuffer->Active()) - StopRecord(); - return recordBuffer != NULL; -} - -bool cDvbApi::Replaying(void) -{ - if (replayBuffer && !replayBuffer->Active()) - StopReplay(); - return replayBuffer != NULL; -} - -bool cDvbApi::StartRecord(const char *FileName, int Ca, int Priority) -{ - if (Recording()) { - esyslog("ERROR: StartRecord() called while recording - ignored!"); - return false; - } - - StopTransfer(); - - StopReplay(); // TODO: remove this if the driver is able to do record and replay at the same time - - // Check FileName: - - if (!FileName) { - esyslog("ERROR: StartRecord: file name is (null)"); - return false; - } - isyslog("record %s", FileName); - - // Create directories if necessary: - - if (!MakeDirs(FileName, true)) - return false; - - // Make sure the disk is up and running: - - SpinUpDisk(FileName); - - // Create recording buffer: - - recordBuffer = new cRecordBuffer(this, FileName, vPid, aPid1, aPid2, dPid1, dPid2); - - if (recordBuffer) { - ca = Ca; - priority = Priority; - return true; - } - else - esyslog("ERROR: can't allocate recording buffer"); - - return false; -} - -void cDvbApi::StopRecord(void) -{ - if (recordBuffer) { - delete recordBuffer; - recordBuffer = NULL; - ca = -1; - priority = DEFAULTPRIORITY; - } -} - -bool cDvbApi::StartReplay(const char *FileName) -{ - if (Recording()) { - esyslog("ERROR: StartReplay() called while recording - ignored!"); - return false; - } - StopTransfer(); - StopReplay(); - if (fd_video >= 0 && fd_audio >= 0) { - - // Check FileName: - - if (!FileName) { - esyslog("ERROR: StartReplay: file name is (null)"); - return false; - } - isyslog("replay %s", FileName); - - // Create replay buffer: - - replayBuffer = new cReplayBuffer(this, fd_video, fd_audio, FileName); - if (replayBuffer) - return true; - else - esyslog("ERROR: can't allocate replaying buffer"); - } - return false; -} - -void cDvbApi::StopReplay(void) -{ - if (replayBuffer) { - delete replayBuffer; - replayBuffer = NULL; - if (this == PrimaryDvbApi) { - // let's explicitly switch the channel back in case it was in Transfer Mode: - cChannel *Channel = Channels.GetByNumber(currentChannel); - if (Channel) { - Channel->Switch(this, false); - usleep(100000); // allow driver to sync in case a new replay will start immediately - } - } - } -} - -void cDvbApi::Pause(void) -{ - if (replayBuffer) - replayBuffer->Pause(); -} - -void cDvbApi::Play(void) -{ - if (replayBuffer) - replayBuffer->Play(); -} - -void cDvbApi::Forward(void) -{ - if (replayBuffer) - replayBuffer->Forward(); -} - -void cDvbApi::Backward(void) -{ - if (replayBuffer) - replayBuffer->Backward(); -} - -void cDvbApi::SkipSeconds(int Seconds) -{ - if (replayBuffer) - replayBuffer->SkipSeconds(Seconds); -} - -int cDvbApi::SkipFrames(int Frames) -{ - if (replayBuffer) - return replayBuffer->SkipFrames(Frames); - return -1; -} - -bool cDvbApi::GetIndex(int &Current, int &Total, bool SnapToIFrame) -{ - if (replayBuffer) { - replayBuffer->GetIndex(Current, Total, SnapToIFrame); - return true; - } - return false; -} - -bool cDvbApi::GetReplayMode(bool &Play, bool &Forward, int &Speed) -{ - return replayBuffer && replayBuffer->GetReplayMode(Play, Forward, Speed); -} - -void cDvbApi::Goto(int Position, bool Still) -{ - if (replayBuffer) - replayBuffer->Goto(Position, Still); -} - -bool cDvbApi::CanToggleAudioTrack(void) -{ - return replayBuffer ? replayBuffer->CanToggleAudioTrack() : (aPid1 && aPid2 && aPid1 != aPid2); -} - -bool cDvbApi::ToggleAudioTrack(void) -{ - if (replayBuffer) { - replayBuffer->ToggleAudioTrack(); - return true; - } - else { - int a = aPid2; - aPid2 = aPid1; - aPid1 = a; - if (transferringFromDvbApi) - return transferringFromDvbApi->ToggleAudioTrack(); - else { - if (transferBuffer) - transferBuffer->SetAudioPid(aPid1); - return SetPids(transferBuffer != NULL); - } - } - return false; -} - -bool cDvbApi::ToggleMute(void) -{ - int OldVolume = volume; - mute = !mute; - SetVolume(0, mute); - volume = OldVolume; - return mute; -} - -void cDvbApi::SetVolume(int Volume, bool Absolute) -{ - if (fd_audio >= 0) { - volume = min(max(Absolute ? Volume : volume + Volume, 0), MAXVOLUME); - audioMixer_t am; - am.volume_left = am.volume_right = volume; - CHECK(ioctl(fd_audio, AUDIO_SET_MIXER, &am)); - if (volume > 0) - mute = false; - } -} - -void cDvbApi::SetAudioCommand(const char *Command) -{ - delete audioCommand; - audioCommand = strdup(Command); -} - -// --- cEITScanner ----------------------------------------------------------- - -cEITScanner::cEITScanner(void) -{ - lastScan = lastActivity = time(NULL); - currentChannel = 0; - lastChannel = 0; - numTransponders = 0; - transponders = NULL; -} - -cEITScanner::~cEITScanner() -{ - delete transponders; -} - -bool cEITScanner::TransponderScanned(cChannel *Channel) -{ - for (int i = 0; i < numTransponders; i++) { - if (transponders[i] == Channel->frequency) - return true; - } - transponders = (int *)realloc(transponders, ++numTransponders * sizeof(int)); - transponders[numTransponders - 1] = Channel->frequency; - return false; -} - -void cEITScanner::Activity(void) -{ - if (currentChannel) { - Channels.SwitchTo(currentChannel); - currentChannel = 0; - } - lastActivity = time(NULL); -} - -void cEITScanner::Process(void) -{ - if (Setup.EPGScanTimeout && Channels.MaxNumber() > 1) { - time_t now = time(NULL); - if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) { - for (int i = 0; i < MAXDVBAPI; i++) { - cDvbApi *DvbApi = cDvbApi::GetDvbApi(i + 1, MAXPRIORITY + 1); - if (DvbApi) { - if (DvbApi != cDvbApi::PrimaryDvbApi || (cDvbApi::NumDvbApis == 1 && Setup.EPGScanTimeout && now - lastActivity > Setup.EPGScanTimeout * 3600)) { - if (!(DvbApi->Recording() || DvbApi->Replaying() || DvbApi->Transferring())) { - int oldCh = lastChannel; - int ch = oldCh + 1; - while (ch != oldCh) { - if (ch > Channels.MaxNumber()) { - ch = 1; - numTransponders = 0; - } - cChannel *Channel = Channels.GetByNumber(ch); - if (Channel) { - if (Channel->ca <= MAXDVBAPI && !DvbApi->ProvidesCa(Channel->ca)) - break; // the channel says it explicitly needs a different card - if (Channel->pnr && !TransponderScanned(Channel)) { - if (DvbApi == cDvbApi::PrimaryDvbApi && !currentChannel) - currentChannel = DvbApi->Channel(); - Channel->Switch(DvbApi, false); - lastChannel = ch; - break; - } - } - ch++; - } - } - } - } - } - lastScan = time(NULL); - } - } -} - |