diff --git a/HISTORY b/HISTORY index a2d05ff4..a888e9ba 100644 --- a/HISTORY +++ b/HISTORY @@ -414,7 +414,7 @@ Video Disk Recorder Revision History given number of seconds. This is mainly useful in combination with the new 'runvdr' script that restarts VDR in case is has exited. -2001-03-02: Version 0.72 +2001-04-01: Version 0.72 - Fixed SVDRP commands LSTC and LSTT to make them return an error message if no channels or timers are defined. @@ -427,3 +427,8 @@ Video Disk Recorder Revision History - Fixed internationalization of some Main menu texts. - Updated 'channels.conf' after the recent changes of Premiere World (thanks to Axel Gruber). +- Redesigned the ring buffer to make it work with two separate threads for + input and output (also prepared for using a remultiplexer). +- Fixed setting system time from transponders. +- Fixed a segfault in the Schedule menu in case there is no EPG information. +- The 'runvdr' script now kills any leftover vdr threads before restarting it. diff --git a/Makefile b/Makefile index c1cfabe1..e2b80bb1 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,14 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.20 2001/02/24 15:52:58 kls Exp $ +# $Id: Makefile 1.21 2001/03/18 16:47:00 kls Exp $ DVBDIR = ../DVB INCLUDES = -I$(DVBDIR)/driver OBJS = config.o dvbapi.o dvbosd.o eit.o font.o i18n.o interface.o menu.o osd.o\ - recording.o remote.o svdrp.o thread.o tools.o vdr.o videodir.o + recording.o remote.o remux.o ringbuffer.o svdrp.o thread.o tools.o vdr.o\ + videodir.o OSDFONT = -adobe-helvetica-medium-r-normal--23-*-100-100-p-*-iso8859-1 FIXFONT = -adobe-courier-bold-r-normal--25-*-100-100-m-*-iso8859-1 @@ -37,26 +38,28 @@ font: genfontfile fontfix.c fontosd.c # Implicit rules: %.o: %.c - g++ -g -O2 -Wall -m486 -c $(DEFINES) $(INCLUDES) $< + g++ -g -O2 -Wall -Woverloaded-virtual -m486 -c $(DEFINES) $(INCLUDES) $< # Dependencies: -config.o : config.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h -dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h -dvbosd.o : dvbosd.c dvbosd.h font.h tools.h -eit.o : eit.c config.h dvbapi.h dvbosd.h eit.h font.h thread.h tools.h videodir.h -font.o : font.c font.h fontfix.c fontosd.c tools.h -i18n.o : i18n.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h thread.h tools.h -interface.o: interface.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h -menu.o : menu.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h -osd.o : osd.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h osd.h remote.h svdrp.h thread.h tools.h -recording.o: recording.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h -remote.o : remote.c config.h dvbapi.h dvbosd.h eit.h font.h remote.h thread.h tools.h -svdrp.o : svdrp.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h -thread.o : thread.c thread.h tools.h -tools.o : tools.c tools.h -vdr.o : vdr.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h videodir.h -videodir.o : videodir.c tools.h videodir.h +config.o : config.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h +dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h remux.h ringbuffer.h svdrp.h thread.h tools.h videodir.h +dvbosd.o : dvbosd.c dvbosd.h font.h tools.h +eit.o : eit.c config.h dvbapi.h dvbosd.h eit.h font.h thread.h tools.h videodir.h +font.o : font.c font.h fontfix.c fontosd.c tools.h +i18n.o : i18n.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h thread.h tools.h +interface.o : interface.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h +menu.o : menu.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h +osd.o : osd.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h osd.h remote.h svdrp.h thread.h tools.h +recording.o : recording.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h +remote.o : remote.c config.h dvbapi.h dvbosd.h eit.h font.h remote.h thread.h tools.h +remux.o : remux.c remux.h tools.h +ringbuffer.o: ringbuffer.c ringbuffer.h thread.h tools.h +svdrp.o : svdrp.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h +thread.o : thread.c thread.h tools.h +tools.o : tools.c tools.h +vdr.o : vdr.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h videodir.h +videodir.o : videodir.c tools.h videodir.h # The main program: diff --git a/config.h b/config.h index 60a95c3a..1bc8a535 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.42 2001/02/24 13:19:39 kls Exp $ + * $Id: config.h 1.43 2001/03/18 16:47:00 kls Exp $ */ #ifndef __CONFIG_H @@ -19,7 +19,7 @@ #include "eit.h" #include "tools.h" -#define VDRVERSION "0.71" +#define VDRVERSION "0.72" #define MaxBuffer 10000 diff --git a/dvbapi.c b/dvbapi.c index 1c29c8b4..048683b4 100644 --- a/dvbapi.c +++ b/dvbapi.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.c 1.63 2001/03/14 18:39:53 kls Exp $ + * $Id: dvbapi.c 1.64 2001/03/18 16:47:16 kls Exp $ */ #include "dvbapi.h" @@ -22,6 +22,8 @@ extern "C" { #include "config.h" #include "interface.h" #include "recording.h" +#include "remux.h" +#include "ringbuffer.h" #include "tools.h" #include "videodir.h" @@ -29,29 +31,9 @@ extern "C" { #define VBIDEVICE "/dev/vbi" // The size of the array used to buffer video data: +// (must be larger than MINVIDEODATA - see remux.h) #define VIDEOBUFSIZE (1024*1024) -// The minimum amount of video data necessary to identify frames -// (must be smaller than VIDEOBUFSIZE!): -#define MINVIDEODATA (256*1024) // just a safe guess (max. size of any frame block, plus some safety) - -// The maximum time the buffer is allowed to write data to disk when recording: -#define MAXRECORDWRITETIME 50 // ms - -// Picture types: -#define NO_PICTURE 0 -#define I_FRAME 1 -#define P_FRAME 2 -#define B_FRAME 3 - -// Start codes: -#define SC_PICTURE 0x00 // "picture header" -#define SC_SEQU 0xB3 // "sequence header" -#define SC_PHEAD 0xBA // "pack header" -#define SC_SHEAD 0xBB // "system header" -#define SC_AUDIO 0xC0 -#define SC_VIDEO 0xE0 - #define FRAMESPERSEC 25 // The maximum file size is limited by the range that can be covered @@ -333,7 +315,7 @@ int cIndexFile::Get(uchar FileNumber, int FileOffset) return -1; } -// --- cRingBuffer ----------------------------------------------------------- +// --- 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 @@ -344,7 +326,7 @@ int cIndexFile::Get(uchar FileNumber, int FileOffset) will be made. */ -class cRingBuffer { +class cRingBuffer_ { private: uchar *buffer; int size, head, tail, freeLimit, availLimit; @@ -367,8 +349,8 @@ protected: 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(); + 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; } @@ -377,7 +359,7 @@ public: void Skip(int n); }; -cRingBuffer::cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit, int AvailLimit) +cRingBuffer_::cRingBuffer_(int *InFile, int *OutFile, int Size, int FreeLimit, int AvailLimit) { inFile = InFile; outFile = OutFile; @@ -393,13 +375,13 @@ cRingBuffer::cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit, int esyslog(LOG_ERR, "ERROR: can't allocate ring buffer (size=%d)", size); } -cRingBuffer::~cRingBuffer() +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) +int cRingBuffer_::Byte(int Offset) { if (buffer && Offset < Available()) { Offset += head; @@ -410,7 +392,7 @@ int cRingBuffer::Byte(int Offset) return -1; } -bool cRingBuffer::Set(int Offset, int Length, int Value) +bool cRingBuffer_::Set(int Offset, int Length, int Value) { if (buffer && Offset + Length <= Available() ) { Offset += head; @@ -425,7 +407,7 @@ bool cRingBuffer::Set(int Offset, int Length, int Value) return false; } -void cRingBuffer::Skip(int n) +void cRingBuffer_::Skip(int n) { if (n > 0) { if (head < tail) { @@ -443,7 +425,7 @@ void cRingBuffer::Skip(int n) } } -int cRingBuffer::Read(int Max) +int cRingBuffer_::Read(int Max) { if (buffer) { eof = false; @@ -501,7 +483,7 @@ int cRingBuffer::Read(int Max) return -1; } -int cRingBuffer::Write(int Max) +int cRingBuffer_::Write(int Max) { if (buffer) { int avail = Available(); @@ -540,7 +522,7 @@ int cRingBuffer::Write(int Max) return -1; } -int cRingBuffer::FindStartCode(uchar Code, int Offset) +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. @@ -557,7 +539,7 @@ int cRingBuffer::FindStartCode(uchar Code, int Offset) return -1; } -int cRingBuffer::GetPacketLength(int Offset) +int cRingBuffer_::GetPacketLength(int Offset) { // Returns the entire length of the packet starting at offset. return (Byte(Offset + 4) << 8) + Byte(Offset + 5) + 6; @@ -671,31 +653,29 @@ int cFileName::NextFile(void) // --- cRecordBuffer --------------------------------------------------------- -class cRecordBuffer : public cRingBuffer, public cThread { +class cRecordBuffer : public cRingBuffer { private: cFileName fileName; cIndexFile *index; + cRemux remux; uchar pictureType; int fileSize; int videoDev; int recordFile; - bool ok, synced, stop; + bool recording; time_t lastDiskSpaceCheck; bool RunningLowOnDiskSpace(void); - int ScanVideoPacket(int *PictureType, int Offset); - int Synchronize(void); bool NextFile(void); - virtual int Write(int Max = -1); - bool WriteWithTimeout(void); protected: - virtual void Action(void); + virtual void Input(void); + virtual void Output(void); public: cRecordBuffer(int *InFile, const char *FileName); virtual ~cRecordBuffer(); }; cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName) -:cRingBuffer(InFile, &recordFile, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0) +:cRingBuffer(VIDEOBUFSIZE) ,fileName(FileName, true) { index = NULL; @@ -703,7 +683,7 @@ cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName) fileSize = 0; videoDev = *InFile; recordFile = fileName.Open(); - ok = synced = stop = false; + recording = false; lastDiskSpaceCheck = time(NULL); if (!fileName.Name()) return; @@ -712,44 +692,15 @@ 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 - ok = true; Start(); } cRecordBuffer::~cRecordBuffer() { - stop = true; - Cancel(3); + Stop(); delete index; } -void cRecordBuffer::Action(void) -{ - dsyslog(LOG_INFO, "recording thread started (pid=%d)", getpid()); - - time_t t = time(NULL); - for (;;) { - usleep(1); // this keeps the CPU load low - - LOCK_THREAD; - - int r = Read(); - if (r >= 0) { - if (r > 0) - t = time(NULL); - if (!WriteWithTimeout()) - break; - } - if (r < 0 || (r == 0 && time(NULL) - t > 5)) { - esyslog(LOG_ERR, "ERROR: video data stream broken"); - t = time(NULL); - } - } - SetPlayMode(videoDev, VID_PLAY_RESET); - - dsyslog(LOG_INFO, "end recording thread"); -} - bool cRecordBuffer::RunningLowOnDiskSpace(void) { if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { @@ -763,88 +714,6 @@ bool cRecordBuffer::RunningLowOnDiskSpace(void) return false; } -int cRecordBuffer::ScanVideoPacket(int *PictureType, int Offset) -{ - // Scans the video packet starting at Offset and returns its length. - // If the return value is -1 the packet was not completely in the buffer. - - int Length = GetPacketLength(Offset); - if (Length <= Available()) { - int i = Offset + 8; // the minimum length of the video packet header - i += Byte(i) + 1; // possible additional header bytes - for (; i < Offset + Length; i++) { - if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) { - switch (Byte(i + 3)) { - case SC_PICTURE: *PictureType = GetPictureType(i); - return Length; - } - } - } - *PictureType = NO_PICTURE; - return Length; - } - return -1; -} - -int cRecordBuffer::Synchronize(void) -{ - // Positions to the start of a data block (skipping everything up to - // an I-frame if not synced) and returns the block length. - - pictureType = NO_PICTURE; - - //XXX remove this once the buffer is handled with two separate threads: - if (!synced && Free() < 100000) { - dsyslog(LOG_INFO, "unable to synchronize, dropped %d bytes", Available()); - Clear(); - return 0; - } - for (int i = 0; Available() > MINVIDEODATA && i < MINVIDEODATA; i++) { - if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) { - switch (Byte(i + 3)) { - case SC_VIDEO: { - int pt = NO_PICTURE; - int l = ScanVideoPacket(&pt, i); - if (l < 0) - return 0; // no useful data found, wait for more - if (pt != NO_PICTURE) { - if (pt < I_FRAME || B_FRAME < pt) { - esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pt); - } - else if (pictureType == NO_PICTURE) { - if (!synced) { - if (pt == I_FRAME) { - Skip(i); - synced = true; - } - else { - Skip(i + l); - i = 0; - break; - } - } - if (synced) - pictureType = pt; - } - else - return i; - } - else if (!synced) { - Skip(i + l); - i = 0; - break; - } - i += l - 1; // -1 to compensate for i++ in the loop! - } - break; - case SC_AUDIO: i += GetPacketLength(i) - 1; // -1 to compensate for i++ in the loop! - break; - } - } - } - return 0; // no useful data found, wait for more -} - bool cRecordBuffer::NextFile(void) { if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME @@ -856,56 +725,93 @@ bool cRecordBuffer::NextFile(void) return recordFile >= 0; } -int cRecordBuffer::Write(int Max) +void cRecordBuffer::Input(void) { - // This function ignores the incoming 'Max'! - // It tries to write out exactly *one* frame block. - if (!ok) - return -1; - int n = Synchronize(); - if (n) { - if (stop && pictureType == I_FRAME) { - ok = false; - return -1; // finish the recording before the next 'I' frame - } - if (NextFile()) { - if (index && pictureType != NO_PICTURE) - index->Write(pictureType, fileName.Number(), fileSize); - int written = 0; - for (;;) { - int w = cRingBuffer::Write(n); - if (w >= 0) { - fileSize += w; - written += w; - n -= w; - if (n == 0) - return written; + dsyslog(LOG_INFO, "input thread started (pid=%d)", getpid()); + + uchar b[MINVIDEODATA]; + time_t t = time(NULL); + recording = true; + for (;;) { + 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; } - else - return w; + t = time(NULL); + } + else if (r < 0) { + if (errno != EAGAIN) { + LOG_ERROR; + break; } - } - return -1; - } - return 0; + } + else if (time(NULL) - t > 5) { + esyslog(LOG_ERR, "ERROR: video data stream broken"); + t = time(NULL); + } + cFile::FileReady(videoDev, 100); + if (!recording) + break; + } + SetPlayMode(videoDev, VID_PLAY_RESET); + + dsyslog(LOG_INFO, "input thread ended (pid=%d)", getpid()); } -bool cRecordBuffer::WriteWithTimeout(void) +void cRecordBuffer::Output(void) { - int t0 = time_ms(); - do { - int w = Write(); - if (w < 0) - return false; - if (w == 0) - break; - } while (time_ms() - t0 < MAXRECORDWRITETIME); - return true; + dsyslog(LOG_INFO, "output thread started (pid=%d)", getpid()); + + uchar b[MINVIDEODATA * 2]; + 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 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); + while (Result > 0) { + int w = write(recordFile, p, Result); + if (w < 0) { + LOG_ERROR_STR(fileName.Name()); + recording = false; + return; + } + p += w; + Result -= w; + fileSize += w; + } + } + else + break; + } + if (Count > 0) { + r -= Count; + memmove(b, b + Count, r); + } + if (!recording) + break; + } + } + recording = false; + + dsyslog(LOG_INFO, "output thread ended (pid=%d)", getpid()); } // --- cReplayBuffer --------------------------------------------------------- -class cReplayBuffer : public cRingBuffer, public cThread { +class cReplayBuffer : public cRingBuffer_, public cThread { private: enum eReplayCmd { rcNone, rcStill, rcPause, rcPlay, rcForward, rcBackward }; enum eReplayMode { rmStill, rmPlay, rmFastForward, rmFastRewind, rmSlowRewind }; @@ -944,7 +850,7 @@ public: }; cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName) -:cRingBuffer(&replayFile, OutFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) +:cRingBuffer_(&replayFile, OutFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) ,fileName(FileName, false) { index = NULL; @@ -1271,7 +1177,7 @@ int cReplayBuffer::Read(int Max = -1) 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); + int r = cRingBuffer_::Read(Max); if (r >= 0) readin += r; else @@ -1300,7 +1206,7 @@ int cReplayBuffer::Write(int Max) if (Max) { int w; do { - w = cRingBuffer::Write(Max); + w = cRingBuffer_::Write(Max); if (w >= 0) { fileOffset += w; Written += w; @@ -1348,7 +1254,7 @@ void cTransferBuffer::Action(void) { dsyslog(LOG_INFO, "data transfer thread started (pid=%d)", getpid()); - cRingBuffer Buffer(&fromDevice, &toDevice, VIDEOBUFSIZE, 0, 0); + 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 @@ -1364,7 +1270,7 @@ void cTransferBuffer::Action(void) // --- cCuttingBuffer -------------------------------------------------------- -class cCuttingBuffer : public cRingBuffer, public cThread { +class cCuttingBuffer : public cRingBuffer_, public cThread { private: bool active; int fromFile, toFile; @@ -1379,7 +1285,7 @@ public: }; cCuttingBuffer::cCuttingBuffer(const char *FromFileName, const char *ToFileName) -:cRingBuffer(&fromFile, &toFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) +:cRingBuffer_(&fromFile, &toFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) { active = false; fromFile = toFile = -1; @@ -1438,7 +1344,7 @@ void cCuttingBuffer::Action(void) CurrentFileNumber = FileNumber; } if (fromFile >= 0) - Length = cRingBuffer::Read(Length); + Length = cRingBuffer_::Read(Length); else break; } @@ -1456,7 +1362,7 @@ void cCuttingBuffer::Action(void) } LastIFrame = 0; } - cRingBuffer::Write(Length); + cRingBuffer_::Write(Length); toIndex->Write(PictureType, toFileName->Number(), FileSize); FileSize += Length; if (!LastIFrame) diff --git a/remux.c b/remux.c new file mode 100644 index 00000000..e48296d8 --- /dev/null +++ b/remux.c @@ -0,0 +1,173 @@ +/* + * remux.c: A streaming MPEG2 remultiplexer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: remux.c 1.1 2001/03/31 08:42:17 kls Exp $ + */ + +/* The calling interface of the 'cRemux::Process()' function is defined + as follows: + + 'Data' points to a chunk of data that consists of 'Count' bytes. + The 'Process' function shall try to remultiplex as much of the + data as possible and return a pointer to the resulting buffer. + That buffer typically is different from the incoming 'Data', + but in the simplest case (when 'Process' does nothing) might + as well point to the original 'Data'. When returning, 'Count' + shall be set to the number of bytes that have been processed + (i.e. have been taken from 'Data'), while 'Result' indicates + how many bytes the returned buffer contains. 'PictureType' shall + be set to NO_PICTURE if the returned data does not start a new + picture, or one of I_FRAME, P_FRAME or B_FRAME if a new picture + starting point has been found. This also means that the returned + data buffer may contain at most one entire video frame, because + the next frame must be returned with its own value for 'PictureType'. + + 'Process' shall do it's best to keep the latency time as short + as possible in order to allow a quick start of VDR's "Transfer + mode" (displaying the signal of one DVB card on another card). + In order to do that, this function may decide to first pass + through the incoming data (almost) unprocessed, and make + actual processing kick in after a few seconds (if that is at + all possible for the algorithm). This may result in a non- + optimal stream at the beginning, which won't matter for normal + recordings but may make switching through encrypted channels + in "Transfer mode" faster. + + In the resulting data stream, a new packet shall always be started + when a frame border is encountered. VDR needs this in order to + be able to detect and store the frame indexes, and to easily + display single frames in fast forward/back mode. The very first + data block returned shall be the starting point of an I_FRAME. + Everything before that shall be silently dropped. + + If the incoming data is not enough to do remultiplexing, a value + of NULL shall be returned ('Result' has no meaning then). This + will tell the caller to wait for more data to be presented in + the next call. If NULL is returned and 'Count' is not 0, the + caller shall remove 'Count' bytes from the beginning of 'Data' + before the next call. This is the way 'Process' indicates that + it must skip that data. + + Any data that is not used during this call will appear at the + beginning of the incoming 'Data' buffer at the next call, plus + any new data that has become available. + + It is guaranteed that the caller will completely process any + returned data before the next call to 'Process'. That way, 'Process' + can dynamically allocate its return buffer and be sure the caller + doesn't keep any pointers into that buffer. +*/ + +#include "remux.h" +#include "tools.h" + +#if defined(REMUX_NONE) + +cRemux::cRemux(void) +{ + synced = false; +} + +cRemux::~cRemux() +{ +} + +int cRemux::GetPacketLength(const uchar *Data, int Count, int Offset) +{ + // Returns the entire length of the packet starting at offset, or -1 in case of error. + return (Offset + 5 < Count) ? (Data[Offset + 4] << 8) + Data[Offset + 5] + 6 : -1; +} + +int cRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType) +{ + // Scans the video packet starting at Offset and returns its length. + // If the return value is -1 the packet was not completely in the buffer. + + int Length = GetPacketLength(Data, Count, Offset); + if (Length > 0 && Offset + Length <= Count) { + int i = Offset + 8; // the minimum length of the video packet header + i += Data[i] + 1; // possible additional header bytes + for (; i < Offset + Length; i++) { + if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) { + switch (Data[i + 3]) { + case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07; + return Length; + } + } + } + PictureType = NO_PICTURE; + return Length; + } + return -1; +} + +const uchar *cRemux::Process(const uchar *Data, int &Count, int &Result, uchar &PictureType) +{ + int Skip = 0; + + PictureType = NO_PICTURE; + + if (Count >= MINVIDEODATA) { + for (int i = 0; i < Count; i++) { + if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) { + switch (Data[i + 3]) { + case SC_VIDEO: + { + uchar pt = NO_PICTURE; + int l = ScanVideoPacket(Data, Count, i, pt); + if (l < 0) { + if (Skip < Count) + Count = Skip; + return NULL; // no useful data found, wait for more + } + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pt); + } + else if (PictureType == NO_PICTURE) { + if (!synced) { + if (pt == I_FRAME) { + Skip = i; + synced = true; + } + else { + i += l; + Skip = i; + break; + } + } + if (synced) + PictureType = pt; + } + else { + Count = i; + Result = i - Skip; + return Data + Skip; + } + } + else if (!synced) { + i += l; + Skip = i; + break; + } + i += l - 1; // -1 to compensate for i++ in the loop! + } + break; + case SC_AUDIO: + i += GetPacketLength(Data, Count, i) - 1; // -1 to compensate for i++ in the loop! + break; + } + } + } + } + if (Skip < Count) + Count = Skip; + return NULL; // no useful data found, wait for more +} + +#elif defined(REMUX_TEST) +#endif + diff --git a/remux.h b/remux.h new file mode 100644 index 00000000..bceb676b --- /dev/null +++ b/remux.h @@ -0,0 +1,51 @@ +/* + * remux.h: A streaming MPEG2 remultiplexer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: remux.h 1.1 2001/03/31 08:42:27 kls Exp $ + */ + +#ifndef __REMUX_H +#define __REMUX_H + +// There are various experiments with different types of remultiplexers +// going on at the moment. Select the remultiplexer here: +#define REMUX_NONE 1 +//#define REMUX_TEST 1 + +// Picture types: +#define NO_PICTURE 0 +#define I_FRAME 1 +#define P_FRAME 2 +#define B_FRAME 3 + +// Start codes: +#define SC_PICTURE 0x00 // "picture header" +#define SC_SEQU 0xB3 // "sequence header" +#define SC_PHEAD 0xBA // "pack header" +#define SC_SHEAD 0xBB // "system header" +#define SC_AUDIO 0xC0 +#define SC_VIDEO 0xE0 + +// The minimum amount of video data necessary to identify frames: +#define MINVIDEODATA (256*1024) // just a safe guess (max. size of any frame block, plus some safety) + +typedef unsigned char uchar; + +class cRemux { +private: +#if defined(REMUX_NONE) + bool synced; + int GetPacketLength(const uchar *Data, int Count, int Offset); + int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); +#elif defined(REMUX_TEST) +#endif +public: + cRemux(void); + ~cRemux(); + const uchar *Process(const uchar *Data, int &Count, int &Result, uchar &PictureType); + }; + +#endif // __REMUX_H diff --git a/ringbuffer.c b/ringbuffer.c new file mode 100644 index 00000000..3b2f5cac --- /dev/null +++ b/ringbuffer.c @@ -0,0 +1,170 @@ +/* + * ringbuffer.c: A threaded ring buffer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * Parts of this file were inspired by the 'ringbuffy.c' from the + * LinuxDVB driver (see linuxtv.org). + * + * $Id: ringbuffer.c 1.1 2001/03/18 16:47:00 kls Exp $ + */ + +#include "ringbuffer.h" +#include "tools.h" + +// --- cRingBufferInputThread ------------------------------------------------- + +class cRingBufferInputThread : public cThread { +private: + cRingBuffer *ringBuffer; +protected: + virtual void Action(void) { ringBuffer->Input(); } +public: + cRingBufferInputThread(cRingBuffer *RingBuffer) { ringBuffer = RingBuffer; } + }; + +// --- cRingBufferOutputThread ------------------------------------------------ + +class cRingBufferOutputThread : public cThread { +private: + cRingBuffer *ringBuffer; +protected: + virtual void Action(void) { ringBuffer->Output(); } +public: + cRingBufferOutputThread(cRingBuffer *RingBuffer) { ringBuffer = RingBuffer; } + }; + +// --- cRingBuffer ------------------------------------------------------------ + +cRingBuffer::cRingBuffer(int Size) +{ + size = Size; + buffer = NULL; + inputThread = NULL; + outputThread = NULL; + maxFill = 0; + busy = false; + if (size > 1) { // 'size - 1' must not be 0! + buffer = new uchar[size]; + if (!buffer) + esyslog(LOG_ERR, "ERROR: can't allocate ring buffer (size=%d)", size); + Clear(); + } + else + esyslog(LOG_ERR, "ERROR: illegal size for ring buffer (%d)", size); +} + +cRingBuffer::~cRingBuffer() +{ + delete inputThread; + delete outputThread; + delete buffer; + dsyslog(LOG_INFO, "buffer stats: %d (%d%%) used", maxFill, maxFill * 100 / (size - 1)); +} + +void cRingBuffer::Clear(void) +{ + mutex.Lock(); + head = tail = 0; + mutex.Unlock(); +} + +int cRingBuffer::Put(const uchar *Data, int Count) +{ + if (Count > 0) { + mutex.Lock(); + int rest = size - head; + int diff = tail - head; + mutex.Unlock(); + int free = (diff > 0) ? diff - 1 : size + diff - 1; + // Statistics: + int fill = size - free - 1 + Count; + if (fill >= size) + fill = size - 1; + if (fill > maxFill) { + maxFill = fill; + int percent = maxFill * 100 / (size - 1); + if (percent > 75) + dsyslog(LOG_INFO, "buffer usage: %d%%", percent); + } + // + if (free <= 0) + return 0; + if (free < Count) + Count = free; + if (Count > maxFill) + maxFill = Count; + if (Count >= rest) { + memcpy(buffer + head, Data, rest); + if (Count - rest) + memcpy(buffer, Data + rest, Count - rest); + head = Count - rest; + } + else { + memcpy(buffer + head, Data, Count); + head += Count; + } + } + return Count; +} + +int cRingBuffer::Get(uchar *Data, int Count) +{ + if (Count > 0) { + mutex.Lock(); + int rest = size - tail; + int diff = head - tail; + mutex.Unlock(); + int cont = (diff >= 0) ? diff : size + diff; + if (rest <= 0) + return 0; + if (cont < Count) + Count = cont; + if (Count >= rest) { + memcpy(Data, buffer + tail, rest); + if (Count - rest) + memcpy(Data + rest, buffer, Count - rest); + tail = Count - rest; + } + else { + memcpy(Data, buffer + tail, Count); + tail += Count; + } + } + return Count; +} + +bool cRingBuffer::Start(void) +{ + if (!busy) { + busy = true; + outputThread = new cRingBufferOutputThread(this); + if (!outputThread->Start()) + DELETENULL(outputThread); + inputThread = new cRingBufferInputThread(this); + if (!inputThread->Start()) { + DELETENULL(inputThread); + DELETENULL(outputThread); + } + busy = outputThread && inputThread; + } + return busy; +} + +bool cRingBuffer::Active(void) +{ + return outputThread && outputThread->Active() && inputThread && inputThread->Active(); +} + +void cRingBuffer::Stop(void) +{ + busy = false; + for (time_t t0 = time(NULL) + 3; time(NULL) < t0; ) { + if (!((outputThread && outputThread->Active()) || (inputThread && inputThread->Active()))) + break; + } + DELETENULL(inputThread); + DELETENULL(outputThread); +} + diff --git a/ringbuffer.h b/ringbuffer.h new file mode 100644 index 00000000..605f553f --- /dev/null +++ b/ringbuffer.h @@ -0,0 +1,55 @@ +/* + * ringbuffer.h: A threaded ring buffer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: ringbuffer.h 1.1 2001/03/18 16:47:00 kls Exp $ + */ + +#ifndef __RINGBUFFER_H +#define __RINGBUFFER_H + +#include "thread.h" + +typedef unsigned char uchar; + +class cRingBufferInputThread; +class cRingBufferOutputThread; + +class cRingBuffer { + friend class cRingBufferInputThread; + friend class cRingBufferOutputThread; +private: + cRingBufferInputThread *inputThread; + cRingBufferOutputThread *outputThread; + cMutex mutex; + int size, head, tail; + uchar *buffer; + int maxFill; + bool busy; +protected: + bool Busy(void) { return busy; } + void Clear(void); + // Immediately clears the ring buffer. + int Put(const uchar *Data, int Count); + // Puts at most Count bytes of Data into the ring buffer. + // Returns the number of bytes actually stored. + int Get(uchar *Data, int Count); + // Gets at most Count bytes of Data from the ring buffer. + // Returns the number of bytes actually retrieved. + virtual void Input(void) = 0; + // Runs as a separate thread and shall continuously read data from + // a source and call Put() to store the data in the ring buffer. + virtual void Output(void) = 0; + // Runs as a separate thread and shall continuously call Get() to + // retrieve data from the ring buffer and write it to a destination. +public: + cRingBuffer(int Size); + virtual ~cRingBuffer(); + bool Start(void); + bool Active(void); + void Stop(void); + }; + +#endif // __RINGBUFFER_H