diff options
Diffstat (limited to 'dvbapi.c')
-rw-r--r-- | dvbapi.c | 1621 |
1 files changed, 927 insertions, 694 deletions
@@ -4,13 +4,15 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.c 1.66 2001/03/31 15:01:57 kls Exp $ + * $Id: dvbapi.c 1.96 2001/07/29 10:32:50 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> @@ -20,20 +22,29 @@ extern "C" { #include <sys/time.h> #include <unistd.h> #include "config.h" -#include "interface.h" #include "recording.h" #include "remux.h" #include "ringbuffer.h" #include "tools.h" #include "videodir.h" -#define VIDEODEVICE "/dev/video" -#define VBIDEVICE "/dev/vbi" +#define DEV_VIDEO "/dev/video" +#define DEV_OST_OSD "/dev/ost/osd" +#define DEV_OST_QAMFE "/dev/ost/qamfe" +#define DEV_OST_QPSKFE "/dev/ost/qpskfe" +#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 (1024*1024) +// The maximum size of a single frame: +#define MAXFRAMESIZE (192*1024) + #define FRAMESPERSEC 25 // The maximum file size is limited by the range that can be covered @@ -55,16 +66,13 @@ extern "C" { // The number of frames to back up when resuming an interrupted replay session: #define RESUMEBACKUP (10 * FRAMESPERSEC) -typedef unsigned char uchar; +// The maximum time we wait before assuming that a recorded video data stream +// is broken: +#define MAXBROKENTIMEOUT 30 // seconds -static void SetPlayMode(int VideoDev, int Mode) -{ - if (VideoDev >= 0) { - struct video_play_mode pmode; - pmode.mode = Mode; - ioctl(VideoDev, VIDIOCSPLAYMODE, &pmode); - } -} +#define CHECK(s) { if ((s) < 0) LOG_ERROR; } // used for 'ioctl()' calls + +typedef unsigned char uchar; const char *IndexToHMSF(int Index, bool WithFrame) { @@ -100,6 +108,7 @@ private: public: cIndexFile(const char *FileName, bool Record); ~cIndexFile(); + bool Ok(void) { return index != NULL; } void 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); @@ -158,8 +167,10 @@ cIndexFile::cIndexFile(const char *FileName, bool Record) else LOG_ERROR; } + else if (!Record) + isyslog(LOG_INFO, "missing index file %s", fileName); if (Record) { - if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP)) >= 0) { + if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) >= 0) { if (delta) { esyslog(LOG_ERR, "ERROR: padding index file with %d '0' bytes", delta); while (delta--) @@ -315,236 +326,6 @@ int cIndexFile::Get(uchar FileNumber, int FileOffset) return -1; } -// --- 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; } -public: - int Available(void) { return (tail >= head) ? tail - head : size - head + tail; } -protected: - int Readable(void) { return (tail >= head) ? size - tail - (head ? 0 : 1) : head - tail - 1; } // keep a 1 byte gap! - int Writeable(void) { return (tail >= head) ? tail - head : size - head; } - int Byte(int Offset); - bool Set(int Offset, int Length, int Value); -protected: - 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; } - int FindStartCode(uchar Code, int Offset = 0); - int GetPacketLength(int Offset = 0); -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_() -{ - 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; -} - -bool cRingBuffer_::Set(int Offset, int Length, int Value) -{ - if (buffer && Offset + Length <= Available() ) { - Offset += head; - while (Length--) { - if (Offset >= size) - Offset -= size; - buffer[Offset] = Value; - Offset++; - } - return true; - } - return false; -} - -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; - } - 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; -} - -int cRingBuffer_::FindStartCode(uchar Code, int Offset) -{ - // Searches for a start code (beginning at Offset) and returns the number - // of bytes from Offset to the start code. - - 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_PHEAD) - break; // found another block start while looking for a different code - } - return -1; -} - -int cRingBuffer_::GetPacketLength(int Offset) -{ - // Returns the entire length of the packet starting at offset. - return (Byte(Offset + 4) << 8) + Byte(Offset + 5) + 6; -} - // --- cFileName ------------------------------------------------------------- class cFileName { @@ -553,8 +334,9 @@ private: int fileNumber; char *fileName, *pFileNumber; bool record; + bool blocking; public: - cFileName(const char *FileName, bool Record); + cFileName(const char *FileName, bool Record, bool Blocking = false); ~cFileName(); const char *Name(void) { return fileName; } int Number(void) { return fileNumber; } @@ -564,11 +346,12 @@ public: int NextFile(void); }; -cFileName::cFileName(const char *FileName, bool Record) +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) { @@ -589,16 +372,17 @@ cFileName::~cFileName() int cFileName::Open(void) { if (file < 0) { + int BlockingFlag = blocking ? 0 : O_NONBLOCK; if (record) { dsyslog(LOG_INFO, "recording to '%s'", fileName); - file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_NONBLOCK); + file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag); if (file < 0) LOG_ERROR_STR(fileName); } else { if (access(fileName, R_OK) == 0) { dsyslog(LOG_INFO, "playing '%s'", fileName); - file = open(fileName, O_RDONLY | O_NONBLOCK); + file = open(fileName, O_RDONLY | BlockingFlag); if (file < 0) LOG_ERROR_STR(fileName); } @@ -655,6 +439,7 @@ int cFileName::NextFile(void) class cRecordBuffer : public cRingBuffer { private: + cDvbApi *dvbApi; cFileName fileName; cIndexFile *index; cRemux remux; @@ -670,18 +455,19 @@ protected: virtual void Input(void); virtual void Output(void); public: - cRecordBuffer(int *InFile, const char *FileName); + cRecordBuffer(cDvbApi *DvbApi, const char *FileName, int VPid, int APid1, int APid2, int DPid1, int DPid2); virtual ~cRecordBuffer(); }; -cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName) -:cRingBuffer(VIDEOBUFSIZE) +cRecordBuffer::cRecordBuffer(cDvbApi *DvbApi, const char *FileName, int VPid, int APid1, int APid2, int DPid1, int DPid2) +:cRingBuffer(VIDEOBUFSIZE, true) ,fileName(FileName, true) +,remux(VPid, APid1, APid2, DPid1, DPid2, true) { + dvbApi = DvbApi; index = NULL; pictureType = NO_PICTURE; fileSize = 0; - videoDev = *InFile; recordFile = fileName.Open(); recording = false; lastDiskSpaceCheck = time(NULL); @@ -692,12 +478,14 @@ cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName) if (!index) esyslog(LOG_ERR, "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; } @@ -746,18 +534,19 @@ void cRecordBuffer::Input(void) else if (r < 0) { if (errno != EAGAIN) { LOG_ERROR; - break; + if (errno != EBUFFEROVERFLOW) + break; } } - else if (time(NULL) - t > 5) { + if (time(NULL) - t > MAXBROKENTIMEOUT) { esyslog(LOG_ERR, "ERROR: video data stream broken"); + cThread::EmergencyExit(true); t = time(NULL); } cFile::FileReady(videoDev, 100); if (!recording) break; } - SetPlayMode(videoDev, VID_PLAY_RESET); dsyslog(LOG_INFO, "input thread ended (pid=%d)", getpid()); } @@ -766,15 +555,14 @@ void cRecordBuffer::Output(void) { dsyslog(LOG_INFO, "output thread started (pid=%d)", getpid()); - uchar b[MINVIDEODATA * 2]; + uchar b[MINVIDEODATA]; int r = 0; for (;;) { - usleep(1); // this keeps the CPU load low - r += Get(b + r, sizeof(b) - r); - if (r > 0) { - //XXX buffer full??? + int g = Get(b + r, sizeof(b) - r); + if (g > 0) { + r += g; int Count = r, Result; - const uchar *p = remux.Process(b, Count, Result, pictureType); + 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; @@ -803,166 +591,353 @@ void cRecordBuffer::Output(void) if (!recording) break; } + else + usleep(1); // this keeps the CPU load low } recording = false; dsyslog(LOG_INFO, "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(LOG_ERR, "ERROR: frame larger than buffer (%d > %d)", Length, Max); + Length = Max; + } + int r = read(f, b, Length); + if (r < 0) + LOG_ERROR; + return r; +} + // --- cReplayBuffer --------------------------------------------------------- -class cReplayBuffer : public cRingBuffer_, public cThread { +class cReplayBuffer : public cRingBuffer { private: - enum eReplayCmd { rcNone, rcStill, rcPause, rcPlay, rcForward, rcBackward }; - enum eReplayMode { rmStill, rmPlay, rmFastForward, rmFastRewind, rmSlowRewind }; + cDvbApi *dvbApi; cIndexFile *index; cFileName fileName; int fileOffset; - int videoDev; + int videoDev, audioDev; + FILE *dolbyDev; int replayFile; - eReplayMode mode; - int lastIndex, stillIndex; - int brakeCounter; - eReplayCmd command; - bool active; + bool eof; + int blockInput, blockOutput; + bool paused, fastForward, fastRewind; + int lastIndex, stillIndex, playIndex; + bool canToggleAudioTrack; + uchar audioTrack; bool NextFile(uchar FileNumber = 0, int FileOffset = -1); + void Clear(bool Block = false); void Close(void); - void SetCmd(eReplayCmd Cmd) { LOCK_THREAD; command = Cmd; } - void SetTemporalReference(void); + void StripAudioPackets(uchar *b, int Length, uchar Except = 0x00); + void DisplayFrame(uchar *b, int Length); + int Resume(void); + bool Save(void); protected: - virtual void Action(void); + virtual void Input(void); + virtual void Output(void); public: - cReplayBuffer(int *OutFile, const char *FileName); + cReplayBuffer(cDvbApi *DvbApi, int VideoDev, int AudioDev, const char *FileName); virtual ~cReplayBuffer(); - virtual int Read(int Max = -1); - virtual int Write(int Max = -1); - void SetMode(eReplayMode Mode); - int Resume(void); - bool Save(void); - void Pause(void) { SetCmd(rcPause); } - void Play(void) { SetCmd(rcPlay); } - void Forward(void) { SetCmd(rcForward); } - void Backward(void) { SetCmd(rcBackward); } + void Pause(void); + void Play(void); + void Forward(void); + void Backward(void); int SkipFrames(int Frames); void SkipSeconds(int Seconds); void Goto(int Position, bool Still = false); void GetIndex(int &Current, int &Total, bool SnapToIFrame = false); + bool CanToggleAudioTrack(void) { return canToggleAudioTrack; } + void ToggleAudioTrack(void); }; -cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName) -:cRingBuffer_(&replayFile, OutFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) +cReplayBuffer::cReplayBuffer(cDvbApi *DvbApi, int VideoDev, int AudioDev, const char *FileName) +:cRingBuffer(VIDEOBUFSIZE) ,fileName(FileName, false) { + dvbApi = DvbApi; index = NULL; fileOffset = 0; - videoDev = *OutFile; + videoDev = VideoDev; + audioDev = AudioDev; + dolbyDev = NULL; + if (cDvbApi::AudioCommand()) { + dolbyDev = popen(cDvbApi::AudioCommand(), "w"); + if (!dolbyDev) + esyslog(LOG_ERR, "ERROR: can't open pipe to audio command '%s'", cDvbApi::AudioCommand()); + } replayFile = fileName.Open(); - mode = rmPlay; - brakeCounter = 0; - command = rcNone; - lastIndex = stillIndex = -1; - active = false; + eof = false; + blockInput = blockOutput = false; + paused = fastForward = fastRewind = false; + lastIndex = stillIndex = playIndex = -1; + canToggleAudioTrack = false; + audioTrack = 0xC0; if (!fileName.Name()) return; // Create the index file: index = new cIndexFile(FileName, false); - if (!index) + if (!index) { esyslog(LOG_ERR, "ERROR: can't allocate index"); - // let's continue without index, so we'll at least have the recording + } + else if (!index->Ok()) { + delete index; + index = NULL; + } + dvbApi->SetModeReplay(); Start(); } cReplayBuffer::~cReplayBuffer() { - active = false; - Cancel(3); + Stop(); + Save(); Close(); - SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); - SetPlayMode(videoDev, VID_PLAY_RESET); + if (dolbyDev) + pclose(dolbyDev); + dvbApi->SetModeNormal(false); delete index; } -void cReplayBuffer::Action(void) +void cReplayBuffer::Input(void) { - dsyslog(LOG_INFO, "replay thread started (pid=%d)", getpid()); - - bool Paused = false; - bool FastForward = false; - bool FastRewind = false; + dsyslog(LOG_INFO, "input thread started (pid=%d)", getpid()); int ResumeIndex = Resume(); if (ResumeIndex >= 0) isyslog(LOG_INFO, "resuming replay at index %d (%s)", ResumeIndex, IndexToHMSF(ResumeIndex, true)); - active = true; - while (active) { - usleep(1); // this keeps the CPU load low - - LOCK_THREAD; - - if (command != rcNone) { - switch (command) { - case rcStill: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); - SetPlayMode(videoDev, VID_PLAY_NORMAL); - SetMode(rmStill); - Paused = FastForward = FastRewind = false; - break; - 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); - if (!Paused) - stillIndex = -1; - break; - case rcPlay: if (FastForward || FastRewind || Paused) { - SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); - SetPlayMode(videoDev, VID_PLAY_NORMAL); - FastForward = FastRewind = Paused = false; - SetMode(rmPlay); - } - stillIndex = -1; - 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); - } - stillIndex = -1; - 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); - } - stillIndex = -1; - break; - default: break; - } - command = rcNone; + + int lastIndex = -1; + int brakeCounter = 0; + uchar b[MAXFRAMESIZE]; + while (Busy() && (blockInput || NextFile())) { + if (!blockInput && stillIndex < 0) { + int r = 0; + if (fastForward && !paused || fastRewind) { + int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileName.Number(), fileOffset); + uchar FileNumber; + int FileOffset, Length; + if (!paused || (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, fastForward, &FileNumber, &FileOffset, &Length); + if (Index >= 0) { + if (!NextFile(FileNumber, FileOffset)) + break; + } + else { + paused = fastForward = fastRewind = false; + Play(); + continue; + } + lastIndex = Index; + playIndex = -1; + r = ReadFrame(replayFile, b, Length, sizeof(b)); + StripAudioPackets(b, r); + } + else if (index) { + lastIndex = -1; + playIndex = (playIndex >= 0) ? playIndex + 1 : index->Get(fileName.Number(), fileOffset); + uchar FileNumber; + int FileOffset, Length; + if (!(index->Get(playIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) + break; + r = ReadFrame(replayFile, b, Length, sizeof(b)); + StripAudioPackets(b, r, audioTrack); + } + else // allows replay even if the index file is missing + r = read(replayFile, b, sizeof(b)); + if (r > 0) { + uchar *p = b; + while (r > 0 && Busy() && !blockInput) { + int w = Put(p, r); + p += w; + r -= w; + usleep(1); // this keeps the CPU load low + } + } + else if (r ==0) + eof = true; + else if (r < 0 && errno != EAGAIN) { + LOG_ERROR; + break; + } } - if (Read() < 0 || Write() < 0) - break; + else + usleep(1); // this keeps the CPU load low + if (blockInput > 1) + blockInput = 1; } - Save(); - dsyslog(LOG_INFO, "end replaying thread"); + dsyslog(LOG_INFO, "input thread ended (pid=%d)", getpid()); +} + +void cReplayBuffer::Output(void) +{ + dsyslog(LOG_INFO, "output thread started (pid=%d)", getpid()); + + uchar b[MINVIDEODATA]; + while (Busy()) { + int r = blockOutput ? 0 : Get(b, sizeof(b)); + if (r > 0) { + uchar *p = b; + while (r > 0 && Busy() && !blockOutput) { + cFile::FileReadyForWriting(videoDev, 100); + int w = write(videoDev, p, r); + if (w > 0) { + p += w; + r -= w; + fileOffset += w; + } + else if (w < 0 && errno != EAGAIN) { + LOG_ERROR; + Stop(); + return; + } + } + } + else + usleep(1); // this keeps the CPU load low + if (blockOutput > 1) + blockOutput = 1; + } + + dsyslog(LOG_INFO, "output thread ended (pid=%d)", getpid()); +} + +void cReplayBuffer::StripAudioPackets(uchar *b, int Length, uchar Except) +{ + 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 && dolbyDev) { + int written = b[i + 8] + 9; // skips the PES header + int n = l - written; + while (n > 0) { + int w = fwrite(&b[i + written], 1, n, dolbyDev); + if (w < 0) { + LOG_ERROR; + break; + } + n -= w; + written += w; + } + } + // 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(LOG_ERR, "ERROR: unexpected packet id %02X", c); + l = 0; + } + if (l) + i += l - 1; // the loop increments, too! + } + /*XXX + else + esyslog(LOG_ERR, "ERROR: broken packet header"); + XXX*/ + } +} + +void cReplayBuffer::DisplayFrame(uchar *b, int Length) +{ + StripAudioPackets(b, Length); + videoDisplayStillPicture sp = { (char *)b, Length }; + CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, false)); + CHECK(ioctl(audioDev, AUDIO_SET_MUTE, true)); + CHECK(ioctl(videoDev, VIDEO_STILLPICTURE, &sp)); +} + +void cReplayBuffer::Clear(bool Block) +{ + if (!(blockInput || blockOutput)) { + blockInput = blockOutput = 2; + time_t t0 = time(NULL); + while ((blockInput > 1 || blockOutput > 1) && time(NULL) - t0 < 2) + usleep(1); + Lock(); + cRingBuffer::Clear(); + playIndex = -1; + CHECK(ioctl(videoDev, VIDEO_CLEAR_BUFFER)); + CHECK(ioctl(audioDev, AUDIO_CLEAR_BUFFER)); + } + if (!Block) { + blockInput = blockOutput = 0; + Unlock(); + } +} + +void cReplayBuffer::Pause(void) +{ + paused = !paused; + CHECK(ioctl(videoDev, paused ? VIDEO_FREEZE : VIDEO_CONTINUE)); + if (fastForward || fastRewind) { + if (paused) + Clear(); + fastForward = fastRewind = false; + } + CHECK(ioctl(audioDev, AUDIO_SET_MUTE, paused)); + stillIndex = -1; +} + +void cReplayBuffer::Play(void) +{ + if (fastForward || fastRewind || paused) { + if (!paused) + Clear(); + stillIndex = -1; + CHECK(ioctl(videoDev, paused ? VIDEO_CONTINUE : VIDEO_PLAY)); + CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, true)); + CHECK(ioctl(audioDev, AUDIO_SET_MUTE, false)); + fastForward = fastRewind = paused = false; + } +} + +void cReplayBuffer::Forward(void) +{ + if (index || paused) { + if (!paused) + Clear(true); + stillIndex = -1; + fastForward = !fastForward; + fastRewind = false; + if (paused) + CHECK(ioctl(videoDev, fastForward ? VIDEO_SLOWMOTION : VIDEO_FREEZE, 2)); + CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, !fastForward)); + CHECK(ioctl(audioDev, AUDIO_SET_MUTE, fastForward || paused)); + if (!paused) + Clear(false); + } +} + +void cReplayBuffer::Backward(void) +{ + if (index) { + Clear(true); + stillIndex = -1; + fastRewind = !fastRewind; + fastForward = false; + CHECK(ioctl(audioDev, AUDIO_SET_AV_SYNC, !fastRewind)); + CHECK(ioctl(audioDev, AUDIO_SET_MUTE, fastRewind || paused)); + Clear(false); + } } void cReplayBuffer::Close(void) @@ -974,14 +949,6 @@ void cReplayBuffer::Close(void) } } -void cReplayBuffer::SetMode(eReplayMode Mode) -{ - mode = Mode; - brakeCounter = 0; - if (mode != rmPlay) - Clear(); -} - int cReplayBuffer::Resume(void) { if (index) { @@ -1016,9 +983,6 @@ bool cReplayBuffer::Save(void) int cReplayBuffer::SkipFrames(int Frames) { if (index && Frames) { - - LOCK_THREAD; - int Current, Total; GetIndex(Current, Total, true); int OldCurrent = Current; @@ -1030,16 +994,8 @@ int cReplayBuffer::SkipFrames(int Frames) void cReplayBuffer::SkipSeconds(int Seconds) { - 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) { + Clear(true); int Index = index->Get(fileName.Number(), fileOffset); if (Index >= 0) { if (Seconds < 0) { @@ -1055,31 +1011,41 @@ void cReplayBuffer::SkipSeconds(int Seconds) if (index->GetNextIFrame(Index, false, &FileNumber, &FileOffset) >= 0) NextFile(FileNumber, FileOffset); } + Clear(false); + Play(); } } void cReplayBuffer::Goto(int Index, bool Still) { - LOCK_THREAD; - - if (Still) - command = rcStill; - if (++Index <= 0) - Index = 1; // not '0', to allow GetNextIFrame() below to work! - uchar FileNumber; - int FileOffset; - if ((stillIndex = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset)) >= 0) - NextFile(FileNumber, FileOffset); - SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); - Clear(); + if (index) { + Clear(true); + if (paused) + CHECK(ioctl(videoDev, VIDEO_CONTINUE)); + 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) { + stillIndex = Index; + playIndex = -1; + uchar b[MAXFRAMESIZE]; + int r = ReadFrame(replayFile, b, Length, sizeof(b)); + if (r > 0) + DisplayFrame(b, r); + fileOffset += Length; + paused = true; + } + else + stillIndex = playIndex = -1; + Clear(false); + } } void cReplayBuffer::GetIndex(int &Current, int &Total, bool SnapToIFrame) { if (index) { - - LOCK_THREAD; - if (stillIndex >= 0) Current = stillIndex; else { @@ -1099,178 +1065,145 @@ void cReplayBuffer::GetIndex(int &Current, int &Total, bool SnapToIFrame) bool cReplayBuffer::NextFile(uchar FileNumber, int FileOffset) { if (FileNumber > 0) { - Clear(); fileOffset = FileOffset; replayFile = fileName.SetOffset(FileNumber, FileOffset); } - else if (replayFile >= 0 && EndOfFile()) { + else if (replayFile >= 0 && eof) { Close(); replayFile = fileName.NextFile(); } + eof = false; return replayFile >= 0; } -void cReplayBuffer::SetTemporalReference(void) -{ - for (int i = 0; i < Available(); i++) { - if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) { - switch (Byte(i + 3)) { - case SC_PICTURE: { - unsigned short m = (Byte(i + 4) << 8) | Byte(i + 5); - m &= 0x003F; - Set(i + 4, 1, m >> 8); - Set(i + 5, 1, m & 0xFF); - } - return; - } - } - } -} - -int cReplayBuffer::Read(int Max = -1) +void cReplayBuffer::ToggleAudioTrack(void) { - if (mode != rmPlay) { - if (index) { - if (Available()) - return 0; // write out the entire block - if (mode == rmStill) { - uchar FileNumber; - int FileOffset, Length; - if (index->GetNextIFrame(stillIndex + 1, false, &FileNumber, &FileOffset, &Length) >= 0) { - if (!NextFile(FileNumber, FileOffset)) - return -1; - Max = Length; - } - command = rcPause; - } - else { - int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileName.Number(), fileOffset); - if (Index >= 0) { - 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); // jump ahead one frame - } - uchar FileNumber; - int FileOffset, Length; - 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 = cRingBuffer_::Read(Max); - if (r >= 0) - readin += r; - else - return -1; - } while (readin < Max && Free() > 0); - if (mode != rmPlay) { - // delete the audio data in modes other than rmPlay: - int AudioOffset, StartOffset = 0; - while ((AudioOffset = FindStartCode(SC_AUDIO, StartOffset)) >= 0) { - if (!Set(StartOffset + AudioOffset, GetPacketLength(StartOffset + AudioOffset), 0)) - break; // to be able to replay old AV_PES recordings! - StartOffset += AudioOffset; - } - SetTemporalReference(); - } - return readin; - } - if (Available() > 0) - return 0; - return -1; -} - -int cReplayBuffer::Write(int Max) -{ - int Written = 0; - if (Max) { - int w; - do { - w = cRingBuffer_::Write(Max); - if (w >= 0) { - fileOffset += w; - Written += w; - if (Max < 0) - break; - Max -= w; - } - else - return w; - } while (Max > 0); // we MUST write this entire frame block + if (CanToggleAudioTrack()) { + audioTrack = (audioTrack == 0xC0) ? 0xC1 : 0xC0; + Clear(); } - return Written; } // --- cTransferBuffer ------------------------------------------------------- -class cTransferBuffer : public cThread { +class cTransferBuffer : public cRingBuffer { private: - bool active; + cDvbApi *dvbApi; int fromDevice, toDevice; + bool gotBufferReserve; + cRemux remux; protected: - virtual void Action(void); + virtual void Input(void); + virtual void Output(void); public: - cTransferBuffer(int FromDevice, int ToDevice); + cTransferBuffer(cDvbApi *DvbApi, int ToDevice, int VPid, int APid); virtual ~cTransferBuffer(); + void SetAudioPid(int APid); }; -cTransferBuffer::cTransferBuffer(int FromDevice, int ToDevice) +cTransferBuffer::cTransferBuffer(cDvbApi *DvbApi, int ToDevice, int VPid, int APid) +:cRingBuffer(VIDEOBUFSIZE, true) +,remux(VPid, APid, 0, 0, 0) { - fromDevice = FromDevice; + dvbApi = DvbApi; + fromDevice = dvbApi->SetModeRecord(); toDevice = ToDevice; - active = false; + gotBufferReserve = false; Start(); } cTransferBuffer::~cTransferBuffer() { - active = false; - Cancel(3); - SetPlayMode(fromDevice, VID_PLAY_RESET); - SetPlayMode(toDevice, VID_PLAY_RESET); + Stop(); + dvbApi->SetModeNormal(true); } -void cTransferBuffer::Action(void) +void cTransferBuffer::SetAudioPid(int APid) { - dsyslog(LOG_INFO, "data transfer thread started (pid=%d)", getpid()); + 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(LOG_INFO, "input thread started (pid=%d)", getpid()); - 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 - usleep(1); // this keeps the CPU load low + uchar b[MINVIDEODATA]; + int n = 0; + while (Busy()) { + 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 (errno != EAGAIN) { + LOG_ERROR; + if (errno != EBUFFEROVERFLOW) + break; + } + } } - while (active) { - if (Buffer.Read() < 0 || Buffer.Write() < 0) - break; - usleep(1); // this keeps the CPU load low + + dsyslog(LOG_INFO, "input thread ended (pid=%d)", getpid()); +} + +void cTransferBuffer::Output(void) +{ + dsyslog(LOG_INFO, "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()) { + int w = write(toDevice, p, r); + if (w > 0) { + p += w; + r -= w; + } + else if (w < 0 && errno != EAGAIN) { + LOG_ERROR; + Stop(); + return; + } + } + } + else + usleep(1); // this keeps the CPU load low } - dsyslog(LOG_INFO, "data transfer thread stopped (pid=%d)", getpid()); + + dsyslog(LOG_INFO, "output thread ended (pid=%d)", getpid()); } // --- cCuttingBuffer -------------------------------------------------------- -class cCuttingBuffer : public cRingBuffer_, public cThread { +class cCuttingBuffer : public cThread { private: bool active; int fromFile, toFile; @@ -1285,15 +1218,14 @@ public: }; cCuttingBuffer::cCuttingBuffer(const char *FromFileName, const char *ToFileName) -:cRingBuffer_(&fromFile, &toFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) { active = false; fromFile = toFile = -1; fromFileName = toFileName = NULL; fromIndex = toIndex = NULL; if (fromMarks.Load(FromFileName) && fromMarks.Count()) { - fromFileName = new cFileName(FromFileName, false); - toFileName = new cFileName(ToFileName, true); + 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 @@ -1329,22 +1261,24 @@ void cCuttingBuffer::Action(void) int LastIFrame = 0; toMarks.Add(0); toMarks.Save(); + uchar buffer[MAXFRAMESIZE]; while (active) { uchar FileNumber; int FileOffset, Length; uchar PictureType; - - Clear(); // makes sure one frame is completely read and written - + // 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 = cRingBuffer_::Read(Length); + if (fromFile >= 0) { + Length = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); + if (Length < 0) + break; + } else break; } @@ -1362,7 +1296,7 @@ void cCuttingBuffer::Action(void) } LastIFrame = 0; } - cRingBuffer_::Write(Length); + write(toFile, buffer, Length); toIndex->Write(PictureType, toFileName->Number(), FileSize); FileSize += Length; if (!LastIFrame) @@ -1424,13 +1358,32 @@ bool cVideoCutter::Active(void) // --- 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(const char *VideoFileName, const char *VbiFileName) +cDvbApi::cDvbApi(int n) { + vPid = aPid1 = aPid2 = dPid1 = dPid2 = 0; siProcessor = NULL; recordBuffer = NULL; replayBuffer = NULL; @@ -1438,21 +1391,49 @@ cDvbApi::cDvbApi(const char *VideoFileName, const char *VbiFileName) transferringFromDvbApi = NULL; ca = 0; priority = -1; - videoDev = open(VideoFileName, O_RDWR | O_NONBLOCK); - if (videoDev >= 0) { - siProcessor = new cSIProcessor(VbiFileName); + + // Devices that are only present on DVB-C or DVB-S cards: + + fd_qamfe = OstOpen(DEV_OST_QAMFE, n, O_RDWR); + fd_qpskfe = OstOpen(DEV_OST_QPSKFE, 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 may not be available, and are not necessary for normal operation: + + videoDev = OstOpen(DEV_VIDEO, n, O_RDWR); + + // 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_qpskfe >= 0 && fd_sec >= 0) || fd_qamfe >= 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)); if (!dvbApi[0]) // 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); + esyslog(LOG_ERR, "ERROR: can't open video device %d", n); cols = rows = 0; ovlGeoSet = ovlStat = ovlFbSet = false; @@ -1480,16 +1461,14 @@ cDvbApi::cDvbApi(const char *VideoFileName, const char *VbiFileName) cDvbApi::~cDvbApi() { - if (videoDev >= 0) { - delete siProcessor; - Close(); - StopReplay(); - StopRecord(); - StopTransfer(); - OvlO(false); //Overlay off! - //XXX the following call sometimes causes a segfault - driver problem? - //XXX close(videoDev); - } + delete siProcessor; + Close(); + StopReplay(); + StopRecord(); + StopTransfer(); + OvlO(false); //Overlay off! + // 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 @@ -1555,37 +1534,32 @@ int cDvbApi::Index(void) return -1; } +bool cDvbApi::Probe(const char *FileName) +{ + if (access(FileName, F_OK) == 0) { + dsyslog(LOG_INFO, "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) { - char fileName[strlen(VIDEODEVICE) + 10]; - 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); - close(f); - if (r == 0 && (cap.type & VID_TYPE_DVB)) { - char vbiFileName[strlen(VBIDEVICE) + 10]; - sprintf(vbiFileName, "%s%d", VBIDEVICE, i); - dvbApi[NumDvbApis++] = new cDvbApi(fileName, vbiFileName); - } - } - else { - if (errno != ENODEV) - LOG_ERROR_STR(fileName); - break; - } - } - else { - if (errno != ENOENT) - LOG_ERROR_STR(fileName); + if (Probe(OstName(DEV_OST_QPSKFE, i)) || Probe(OstName(DEV_OST_QAMFE, i))) + dvbApi[NumDvbApis++] = new cDvbApi(i); + else break; - } } } PrimaryDvbApi = dvbApi[0]; @@ -1616,6 +1590,8 @@ const cSchedules *cDvbApi::Schedules(cThreadLock *ThreadLock) const bool cDvbApi::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY) { + if (videoDev < 0) + return false; int result = 0; // just do this once? struct video_mbuf mbuf; @@ -1711,6 +1687,8 @@ bool cDvbApi::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, bool cDvbApi::OvlF(int SizeX, int SizeY, int FbAddr, int Bpp, int Palette) { + if (videoDev < 0) + return false; int result = 0; // get the actual X-Server settings??? // plausibility-check problem: can't be verified w/o X-server!!! @@ -1752,6 +1730,8 @@ bool cDvbApi::OvlF(int SizeX, int SizeY, int FbAddr, int Bpp, int Palette) bool cDvbApi::OvlG(int SizeX, int SizeY, int PosX, int PosY) { + if (videoDev < 0) + return false; int result = 0; // get the actual X-Server settings??? struct video_capability vc; @@ -1798,6 +1778,8 @@ bool cDvbApi::OvlG(int SizeX, int SizeY, int PosX, int PosY) bool cDvbApi::OvlC(int ClipCount, CRect *cr) { + if (videoDev < 0) + return false; if (ovlGeoSet && ovlFbSet) { for (int i = 0; i < ClipCount; i++) { ovlClipRects[i].x = cr[i].x; @@ -1815,6 +1797,8 @@ bool cDvbApi::OvlC(int ClipCount, CRect *cr) bool cDvbApi::OvlP(__u16 Brightness, __u16 Colour, __u16 Hue, __u16 Contrast) { + if (videoDev < 0) + return false; int result = 0; ovlBrightness = Brightness; ovlColour = Colour; @@ -1836,6 +1820,8 @@ bool cDvbApi::OvlP(__u16 Brightness, __u16 Colour, __u16 Hue, __u16 Contrast) bool cDvbApi::OvlO(bool Value) { + if (videoDev < 0) + return false; int result = 0; if (!ovlGeoSet && Value) return false; @@ -1867,30 +1853,11 @@ void cDvbApi::SetColor(eDvbColor colorFg, eDvbColor colorBg) } } } -#else -void cDvbApi::Cmd(OSD_Command cmd, int color, int x0, int y0, int x1, int y1, const void *data) -{ - if (videoDev >= 0) { - 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); - 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. - } -} #endif void cDvbApi::Open(int w, int h) { - int d = (h < 0) ? MenuLines + h : 0; + int d = (h < 0) ? Setup.OSDheight + h : 0; h = abs(h); cols = w; rows = h; @@ -1899,16 +1866,7 @@ void cDvbApi::Open(int w, int h) 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; - d *= lineHeight; - int x = (720 - MenuColumns * charWidth) / 2; //TODO PAL vs. NTSC??? - int y = (576 - MenuLines * lineHeight) / 2 + d; - 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) - SETCOLOR(clrTransparent, 0x00, 0x00, 0x00, 0); -#endif + //XXX SETCOLOR(clrBackground, 0x00, 0x00, 0x00, 127); // background 50% gray SETCOLOR(clrBlack, 0x00, 0x00, 0x00, 255); SETCOLOR(clrRed, 0xFC, 0x14, 0x14, 255); @@ -1918,6 +1876,35 @@ void cDvbApi::Open(int w, int h) 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 - (Setup.OSDwidth - 1) * 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, true, clrBackground, clrCyan, clrWhite, 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) @@ -2037,60 +2024,270 @@ void cDvbApi::Flush(void) #endif } -bool cDvbApi::SetChannel(int ChannelNumber, int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Tpid, int Ca, int Pnr) +int cDvbApi::SetModeRecord(void) { - if (videoDev >= 0) { - cThreadLock ThreadLock(siProcessor); // makes sure the siProcessor won't access the vbi-device while switching - StopTransfer(); - StopReplay(); - SetPlayMode(videoDev, VID_PLAY_RESET); - struct frontend front; - ioctl(videoDev, VIDIOCGFRONTEND, &front); - unsigned int freq = FrequencyMHz; - if (front.type == FRONT_DVBS) { - front.ttk = (freq < 11700UL) ? 0 : 1; - if (freq < 11700UL) { + // Sets up the DVB device for recording + + SetPids(true); + if (fd_dvr >= 0) + close(fd_dvr); + fd_dvr = OstOpen(DEV_OST_DVR, Index(), 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) + 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)); + 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) { + if (Pid != 0x1FFF) + 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) && + SetDpid1(ForRecording ? dPid1 : 0, DMX_OUT_TS_TAP) && + SetDpid2(ForRecording ? dPid2 : 0, DMX_OUT_TS_TAP); +} + +bool cDvbApi::SetChannel(int ChannelNumber, int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid1, int Apid2, int Dpid1, int Dpid2, int Tpid, int Ca, int Pnr) +{ + // Make sure the siProcessor won't access the device while switching + cThreadLock ThreadLock(siProcessor); + + 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)); + } + + // 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 && Ca && Ca != Index() + 1); + + 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); + + bool ChannelSynced = false; + + if (fd_qpskfe >= 0 && fd_sec >= 0) { // DVB-S + + // Frequency offsets: + + unsigned int freq = FrequencyMHz; + int tone = SEC_TONE_OFF; + + if (freq < (unsigned int)Setup.LnbSLOF) { freq -= Setup.LnbFrequLo; - front.ttk = 0; + tone = SEC_TONE_OFF; } else { freq -= Setup.LnbFrequHi; - front.ttk = 1; + tone = SEC_TONE_ON; + } + + qpskParameters qpsk; + qpsk.iFrequency = freq * 1000UL; + qpsk.SymbolRate = Srate * 1000UL; + 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)); + + // Tuning: + + CHECK(ioctl(fd_qpskfe, QPSK_TUNE, &qpsk)); + + // Wait for channel sync: + + if (cFile::FileReady(fd_qpskfe, 5000)) { + qpskEvent event; + int res = ioctl(fd_qpskfe, QPSK_GET_EVENT, &event); + if (res >= 0) + ChannelSynced = event.type == FE_COMPLETION_EV; + else + esyslog(LOG_ERR, "ERROR %d in qpsk get event", res); } + else + esyslog(LOG_ERR, "ERROR: timeout while tuning\n"); } - 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.tt_pid = Tpid; - front.fec = 8; - front.AFC = 1; - front.qam = 2; - ioctl(videoDev, VIDIOCSFRONTEND, &front); - if (front.sync & 0x1F == 0x1F) { - if (this == PrimaryDvbApi && siProcessor) - siProcessor->SetCurrentServiceID(Pnr); - currentChannel = ChannelNumber; - // 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 (this == PrimaryDvbApi && Ca && Ca != Index() + 1) { - cDvbApi *CaDvbApi = GetDvbApi(Ca, 0); - if (CaDvbApi) { - if (!CaDvbApi->Recording()) { - if (CaDvbApi->SetChannel(ChannelNumber, FrequencyMHz, Polarization, Diseqc, Srate, Vpid, Apid, Tpid, Ca, Pnr)) - transferringFromDvbApi = CaDvbApi->StartTransfer(videoDev); - } + else if (fd_qamfe >= 0) { // DVB-C + + // Frequency and symbol rate: + + qamParameters qam; + qam.Frequency = FrequencyMHz * 1000000UL; + qam.SymbolRate = Srate * 1000UL; + qam.FEC_inner = FEC_AUTO; + qam.QAM = QAM_64; + + // Tuning: + + CHECK(ioctl(fd_qamfe, QAM_TUNE, &qam)); + + // Wait for channel sync: + + if (cFile::FileReady(fd_qamfe, 5000)) { + qamEvent event; + int res = ioctl(fd_qamfe, QAM_GET_EVENT, &event); + if (res >= 0) + ChannelSynced = event.type == FE_COMPLETION_EV; + else + esyslog(LOG_ERR, "ERROR %d in qam get event", res); + } + else + esyslog(LOG_ERR, "ERROR: timeout while tuning\n"); + } + else { + esyslog(LOG_ERR, "ERROR: attempt to set channel without DVB-S or DVB-C device"); + return false; + } + + if (!ChannelSynced) { + esyslog(LOG_ERR, "ERROR: channel %d not sync'ed!", ChannelNumber); + if (this == PrimaryDvbApi) + cThread::RaisePanic(); + return false; + } + + // PID settings: + + if (!SetPids(false)) { + esyslog(LOG_ERR, "ERROR: failed to set PIDs for channel %d", ChannelNumber); + return false; + } + 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); + + // 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) { + if (!CaDvbApi->Recording()) { + if (CaDvbApi->SetChannel(ChannelNumber, FrequencyMHz, Polarization, Diseqc, Srate, Vpid, Apid1, Apid2, Dpid1, Dpid2, Tpid, Ca, Pnr)) { + SetModeReplay(); + transferringFromDvbApi = CaDvbApi->StartTransfer(fd_video); } } - return true; } - esyslog(LOG_ERR, "ERROR: channel %d not sync'ed (front.sync=%X)!", ChannelNumber, front.sync); } - return false; + + if (fd_video >= 0 && fd_audio >= 0) { + CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false)); + CHECK(ioctl(fd_video, VIDEO_SET_BLANK, false)); + } + + return true; } bool cDvbApi::Transferring(void) @@ -2101,7 +2298,7 @@ bool cDvbApi::Transferring(void) cDvbApi *cDvbApi::StartTransfer(int TransferToVideoDev) { StopTransfer(); - transferBuffer = new cTransferBuffer(videoDev, TransferToVideoDev); + transferBuffer = new cTransferBuffer(this, TransferToVideoDev, vPid, aPid1); return this; } @@ -2110,7 +2307,6 @@ void cDvbApi::StopTransfer(void) if (transferBuffer) { delete transferBuffer; transferBuffer = NULL; - SetPlayMode(videoDev, VID_PLAY_RESET); } if (transferringFromDvbApi) { transferringFromDvbApi->StopTransfer(); @@ -2143,37 +2339,36 @@ bool cDvbApi::StartRecord(const char *FileName, int Ca, int Priority) esyslog(LOG_ERR, "ERROR: StartRecord() called while recording - ignored!"); return false; } - if (videoDev >= 0) { - StopTransfer(); + StopTransfer(); - StopReplay(); // TODO: remove this if the driver is able to do record and replay at the same time + StopReplay(); // TODO: remove this if the driver is able to do record and replay at the same time - // Check FileName: + // Check FileName: - if (!FileName) { - esyslog(LOG_ERR, "ERROR: StartRecord: file name is (null)"); - return false; - } - isyslog(LOG_INFO, "record %s", FileName); + if (!FileName) { + esyslog(LOG_ERR, "ERROR: StartRecord: file name is (null)"); + return false; + } + isyslog(LOG_INFO, "record %s", FileName); - // Create directories if necessary: + // Create directories if necessary: - if (!MakeDirs(FileName, true)) - return false; + if (!MakeDirs(FileName, true)) + return false; - // Create recording buffer: + // Create recording buffer: - recordBuffer = new cRecordBuffer(&videoDev, FileName); + recordBuffer = new cRecordBuffer(this, FileName, vPid, aPid1, aPid2, dPid1, dPid2); - if (recordBuffer) { - ca = Ca; - priority = Priority; - return true; - } - else - esyslog(LOG_ERR, "ERROR: can't allocate recording buffer"); + if (recordBuffer) { + ca = Ca; + priority = Priority; + return true; } + else + esyslog(LOG_ERR, "ERROR: can't allocate recording buffer"); + return false; } @@ -2195,7 +2390,7 @@ bool cDvbApi::StartReplay(const char *FileName) } StopTransfer(); StopReplay(); - if (videoDev >= 0) { + if (fd_video >= 0 && fd_audio >= 0) { // Check FileName: @@ -2207,7 +2402,7 @@ bool cDvbApi::StartReplay(const char *FileName) // Create replay buffer: - replayBuffer = new cReplayBuffer(&videoDev, FileName); + replayBuffer = new cReplayBuffer(this, fd_video, fd_audio, FileName); if (replayBuffer) return true; else @@ -2221,6 +2416,12 @@ 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); + } } } @@ -2276,6 +2477,38 @@ void cDvbApi::Goto(int Position, bool Still) 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; +} + +void cDvbApi::SetAudioCommand(const char *Command) +{ + delete audioCommand; + audioCommand = strdup(Command); +} + // --- cEITScanner ----------------------------------------------------------- cEITScanner::cEITScanner(void) @@ -2318,7 +2551,7 @@ void cEITScanner::Process(void) 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); + cDvbApi *DvbApi = cDvbApi::GetDvbApi(i + 1, MAXPRIORITY); if (DvbApi) { if (DvbApi != cDvbApi::PrimaryDvbApi || (cDvbApi::NumDvbApis == 1 && Setup.EPGScanTimeout && now - lastActivity > Setup.EPGScanTimeout * 3600)) { if (!(DvbApi->Recording() || DvbApi->Replaying() || DvbApi->Transferring())) { |