From 6415cc900de3361925d22f879077be687fce3858 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sat, 16 Oct 2004 09:36:28 +0200 Subject: [PATCH] Improved buffer handling --- CONTRIBUTORS | 4 + HISTORY | 32 +++- PLUGINS/src/sky/HISTORY | 4 + PLUGINS/src/sky/sky.c | 34 ++--- device.c | 98 ++++++------ device.h | 16 +- dvbdevice.c | 36 ++--- dvbplayer.c | 26 ++-- recorder.c | 170 +++++++++++++-------- recorder.h | 13 +- remux.c | 326 +++++++++++++++++----------------------- remux.h | 30 ++-- ringbuffer.c | 230 +++++++++++++++++++++------- ringbuffer.h | 42 ++++-- thread.c | 61 +++++++- thread.h | 20 ++- transfer.c | 118 +++++++-------- transfer.h | 3 +- vdr.c | 5 +- 19 files changed, 724 insertions(+), 544 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 1e7f92a5..ba2661b3 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -723,6 +723,7 @@ Sascha Volkenandt for suggesting to ignore unused "none" color entries in XPM files written by some broken graphics tools for fixing a memory leak in theme description handling + for pointing out a "near miss" condition in cCondVar Malcolm Caldwell for modifying LOF handling to allow for C-band reception @@ -1037,6 +1038,9 @@ Marco Schl for suggesting to avoiding flashing effects in the OSD of full featured DVB cards by explicitly clearing the OSD windows before opening them for fixing a possible NULL pointer assignment in cMenuText::SetText() + for doing some testing regarding buffer performance and giving me some hints that + finally lead to finding out that the basic problem causing buffer overflows was in + EnableGet()/EnablePut() being called too often Jürgen Schmitz for reporting a bug in displaying the current channel when switching via the SVDRP diff --git a/HISTORY b/HISTORY index 1b533929..b0b71be5 100644 --- a/HISTORY +++ b/HISTORY @@ -2967,7 +2967,7 @@ Video Disk Recorder Revision History - The 'radio' channel icon is now only displayed in the ST:TNG skin if the channel actually has an APID. -2004-08-08: Version 1.3.13 +2004-10-16: Version 1.3.13 - Fixed checking for the presence of NPTL (thanks to Jouni Karvo). - Making sure section filters are only set if the device actually has a lock @@ -2976,3 +2976,33 @@ Video Disk Recorder Revision History Marco Schlüssler). - Fixed a crash in case the last line in channels.conf is a group separator and that group is selected in the channel display (thanks to Dick Streefland). +- Added cRingBufferLinear::Read() to read directly from a file handle into the + ring buffer. +- Using timeouts in ring buffers to avoid 'usleep()'. +- Clearing the 'Transfer Mode' ring buffer after clearing the device to avoid + an "almost full" ring buffer. +- Removed locking from cRingBufferLinear for better performance under high load. +- Using a cRingBufferLinear in cRemux to avoid unnecessary copying of data. +- Using a cRingBufferLinear in cTSBuffer and filling it in a separate thread + to avoid buffer overflows. Plugins using cTSBuffer will need to remove the + call to the now obsolete Read() function (see cDvbDevice::GetTSPacket() for + the new usage of cTSBuffer). +- cRemux::Process() has been split into Put(), Get() and Del() to allow for a + better decoupling of the remuxing and disk writing process. Plugins using + cRemux will need to be modified accordingly. +- The actual disk writing in recordings is now done in a separate thread to + improve the overall throughput. +- Changed cRemux so that it returns the maximum available amount of data with + each call, not just 2048 byte. +- Added a visual display of all cRingBufferLinear buffers for debugging. To + activate it, define DEBUGRINGBUFFERS in ringbuffer.h. +- Instead of cCondVar now using the new cCondWait (which also avoids a possible + "near miss" condition; thanks to Sascha Volkenandt for pointing out this one). + cCondVar is still present for plugins that use it (and VDR itself also still + uses it in cRemote). +- The cRingBuffer now does EnableGet()/EnablePut() only if the buffer is more than + one third full or empty, respectively. This dramatically improves recording + performance and reduces system load (thanks to Marco Schlüßler for doing some + testing regarding buffer performance and giving me some hints that finally led + to finding out that this was the basic problem causing buffer overflows). +- Improved Transfer Mode (thanks to Marco Schlüßler for suggestions and testing). diff --git a/PLUGINS/src/sky/HISTORY b/PLUGINS/src/sky/HISTORY index c1c5f98e..2d15da7e 100644 --- a/PLUGINS/src/sky/HISTORY +++ b/PLUGINS/src/sky/HISTORY @@ -24,3 +24,7 @@ VDR Plugin 'sky' Revision History - Added automatic DST detection to getskyepg.pl. - Fixed handling receivers, so that a recording on the same channel won't interrupt an ongoing Transfer mode. + +2004-10-16: Version 0.3.1 + +- Improved buffer handling. diff --git a/PLUGINS/src/sky/sky.c b/PLUGINS/src/sky/sky.c index 6196eefc..eea697ea 100644 --- a/PLUGINS/src/sky/sky.c +++ b/PLUGINS/src/sky/sky.c @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: sky.c 1.6 2004/02/15 14:59:46 kls Exp $ + * $Id: sky.c 1.7 2004/10/16 09:10:06 kls Exp $ */ #include @@ -14,7 +14,7 @@ #include #include -static const char *VERSION = "0.3.0"; +static const char *VERSION = "0.3.1"; static const char *DESCRIPTION = "Sky Digibox interface"; // --- cDigiboxDevice -------------------------------------------------------- @@ -140,7 +140,7 @@ bool cDigiboxDevice::OpenDvr(void) CloseDvr(); fd_dvr = open("/dev/video2", O_RDONLY | O_NONBLOCK);//XXX parameter??? if (fd_dvr >= 0) - tsBuffer = new cTSBuffer(fd_dvr, KILOBYTE(256), CardIndex() + 1); + tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(2), CardIndex() + 1); return fd_dvr >= 0; } @@ -157,24 +157,16 @@ void cDigiboxDevice::CloseDvr(void) bool cDigiboxDevice::GetTSPacket(uchar *&Data) { if (tsBuffer) { - int r = tsBuffer->Read(); - if (r >= 0) { - Data = tsBuffer->Get(); - if (Data) { - // insert the actual PIDs: - int Pid = (((uint16_t)Data[1] & PID_MASK_HI) << 8) | Data[2]; - if (Pid == DUMMYAPID) - Pid = apid; - else if (Pid == DUMMYVPID) - Pid = vpid; - Data[1] = ((Pid >> 8) & 0xFF) | (Data[1] & ~PID_MASK_HI); - Data[2] = Pid & 0xFF; - } - return true; - } - else if (FATALERRNO) { - LOG_ERROR; - return false; + Data = tsBuffer->Get(); + if (Data) { + // insert the actual PIDs: + int Pid = (((uint16_t)Data[1] & PID_MASK_HI) << 8) | Data[2]; + if (Pid == DUMMYAPID) + Pid = apid; + else if (Pid == DUMMYVPID) + Pid = vpid; + Data[1] = ((Pid >> 8) & 0xFF) | (Data[1] & ~PID_MASK_HI); + Data[2] = Pid & 0xFF; } return true; } diff --git a/device.c b/device.c index d62b4563..1813df8a 100644 --- a/device.c +++ b/device.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.c 1.56 2004/06/19 08:51:05 kls Exp $ + * $Id: device.c 1.57 2004/10/09 12:53:02 kls Exp $ */ #include "device.h" @@ -801,79 +801,67 @@ void cDevice::Detach(cReceiver *Receiver) cTSBuffer::cTSBuffer(int File, int Size, int CardIndex) { + SetDescription("TS buffer on device %d", CardIndex); f = File; - size = Size / TS_SIZE * TS_SIZE; cardIndex = CardIndex; - tsRead = tsWrite = 0; - buf = (f >= 0 && size >= TS_SIZE) ? MALLOC(uchar, size + TS_SIZE) : NULL; - // the '+ TS_SIZE' allocates some extra space for handling packets that got split by a buffer roll-over - firstRead = true; + active = false; + delivered = false; + ringBuffer = new cRingBufferLinear(Size, TS_SIZE, true, "TS"); + ringBuffer->SetTimeouts(100, 100); + Start(); } cTSBuffer::~cTSBuffer() { - free(buf); + active = false; + Cancel(3); + delete ringBuffer; } -int cTSBuffer::Read(void) +void cTSBuffer::Action(void) { - if (buf) { - cPoller Poller(f, false); - bool repeat; - int total = 0; - do { - repeat = false; - if (firstRead || Used() > TS_SIZE || Poller.Poll(100)) { // only wait if there's not enough data in the buffer - firstRead = false; - if (tsRead == tsWrite) - tsRead = tsWrite = 0; // keep the maximum buffer space available - if (tsWrite >= size && tsRead > 0) - tsWrite = 0; - int free = tsRead <= tsWrite ? size - tsWrite : tsRead - tsWrite - 1; - if (free > 0) { - int r = read(f, buf + tsWrite, free); - if (r > 0) { - total += r; - tsWrite += r; - if (tsWrite >= size && tsRead > 0) { - tsWrite = 0; - repeat = true; // read again after a boundary roll-over - } - } - } - } - } while (repeat); - return total; + if (ringBuffer) { + bool firstRead = true; + cPoller Poller(f); + active = true; + for (; active;) { + if (firstRead || Poller.Poll(100)) { + firstRead = false; + int r = ringBuffer->Read(f); + if (r < 0 && FATALERRNO) { + if (errno == EOVERFLOW) + esyslog("ERROR: driver buffer overflow on device %d", cardIndex); + else { + LOG_ERROR; + break; + } + } + } + } } - return -1; } uchar *cTSBuffer::Get(void) { - if (Used() >= TS_SIZE) { - uchar *p = buf + tsRead; + int Count = 0; + if (delivered) { + ringBuffer->Del(TS_SIZE); + delivered = false; + } + uchar *p = ringBuffer->Get(Count); + if (p && Count >= TS_SIZE) { if (*p != TS_SYNC_BYTE) { - esyslog("ERROR: not sync'ed to TS packet on device %d", cardIndex); - int tsMax = tsRead < tsWrite ? tsWrite : size; - for (int i = tsRead; i < tsMax; i++) { - if (buf[i] == TS_SYNC_BYTE) { - esyslog("ERROR: skipped %d bytes to sync on TS packet on device %d", i - tsRead, cardIndex); - tsRead = i; - return NULL; + for (int i = 1; i < Count; i++) { + if (p[i] == TS_SYNC_BYTE) { + Count = i; + break; } } - if ((tsRead = tsMax) >= size) - tsRead = 0; + ringBuffer->Del(Count); + esyslog("ERROR: skipped %d bytes to sync on TS packet on device %d", Count, cardIndex); return NULL; } - if (tsRead + TS_SIZE > size) { - // the packet rolled over the buffer boundary, so let's fetch the rest from the beginning (which MUST be there, since Used() >= TS_SIZE) - int rest = TS_SIZE - (size - tsRead); - memcpy(buf + size, buf, rest); - tsRead = rest; - } - else if ((tsRead += TS_SIZE) >= size) - tsRead = 0; + delivered = true; return p; } return NULL; diff --git a/device.h b/device.h index 10bca28c..bec6bf5b 100644 --- a/device.h +++ b/device.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.h 1.44 2004/06/19 08:50:37 kls Exp $ + * $Id: device.h 1.45 2004/09/24 14:07:22 kls Exp $ */ #ifndef __DEVICE_H @@ -15,6 +15,7 @@ #include "filter.h" #include "nit.h" #include "pat.h" +#include "ringbuffer.h" #include "sdt.h" #include "sections.h" #include "thread.h" @@ -437,20 +438,17 @@ public: /// sure the returned data points to a TS packet and automatically /// re-synchronizes after broken packets. -class cTSBuffer { +class cTSBuffer : public cThread { private: int f; - int size; int cardIndex; - int tsRead; - int tsWrite; - uchar *buf; - bool firstRead; - int Used(void) { return tsRead <= tsWrite ? tsWrite - tsRead : size - tsRead + tsWrite; } + bool active; + bool delivered; + cRingBufferLinear *ringBuffer; + virtual void Action(void); public: cTSBuffer(int File, int Size, int CardIndex); ~cTSBuffer(); - int Read(void); uchar *Get(void); }; diff --git a/dvbdevice.c b/dvbdevice.c index 04af94b9..56564afb 100644 --- a/dvbdevice.c +++ b/dvbdevice.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbdevice.c 1.93 2004/06/19 13:48:00 kls Exp $ + * $Id: dvbdevice.c 1.94 2004/10/15 13:07:52 kls Exp $ */ #include "dvbdevice.h" @@ -78,8 +78,7 @@ private: bool useCa; time_t startTime; eTunerStatus tunerStatus; - cMutex mutex; - cCondVar newSet; + cCondWait newSet; bool SetFrontend(void); virtual void Action(void); public: @@ -111,7 +110,7 @@ cDvbTuner::~cDvbTuner() { active = false; tunerStatus = tsIdle; - newSet.Broadcast(); + newSet.Signal(); Cancel(3); } @@ -122,7 +121,7 @@ bool cDvbTuner::IsTunedTo(const cChannel *Channel) const void cDvbTuner::Set(const cChannel *Channel, bool Tune, bool UseCa) { - cMutexLock MutexLock(&mutex); + Lock(); if (Tune) tunerStatus = tsSet; else if (tunerStatus == tsCam) @@ -131,7 +130,8 @@ void cDvbTuner::Set(const cChannel *Channel, bool Tune, bool UseCa) if (Channel->Ca() && tunerStatus != tsCam) startTime = time(NULL); channel = *Channel; - newSet.Broadcast(); + Unlock(); + newSet.Signal(); } static unsigned int FrequencyToHz(unsigned int f) @@ -252,7 +252,7 @@ void cDvbTuner::Action(void) { active = true; while (active) { - cMutexLock MutexLock(&mutex); + Lock(); if (tunerStatus == tsSet) tunerStatus = SetFrontend() ? tsTuned : tsIdle; if (tunerStatus == tsTuned) { @@ -267,7 +267,6 @@ void cDvbTuner::Action(void) if (event.status & FE_REINIT) { tunerStatus = tsSet; esyslog("ERROR: frontend %d was reinitialized - re-tuning", cardIndex); - continue; } } } @@ -292,8 +291,9 @@ void cDvbTuner::Action(void) else if (tunerStatus > tsLocked) tunerStatus = tsLocked; } + Unlock(); // in the beginning we loop more often to let the CAM connection start up fast - newSet.TimedWait(mutex, (ciHandler && (time(NULL) - startTime < 20)) ? 100 : 1000); + newSet.Wait((ciHandler && (time(NULL) - startTime < 20)) ? 100 : 1000); } } @@ -1101,29 +1101,17 @@ bool cDvbDevice::OpenDvr(void) void cDvbDevice::CloseDvr(void) { if (fd_dvr >= 0) { - close(fd_dvr); - fd_dvr = -1; delete tsBuffer; tsBuffer = NULL; + close(fd_dvr); + fd_dvr = -1; } } bool cDvbDevice::GetTSPacket(uchar *&Data) { if (tsBuffer) { - int r = tsBuffer->Read(); - if (r >= 0) { - Data = tsBuffer->Get(); - return true; - } - else if (FATALERRNO) { - if (errno == EOVERFLOW) - esyslog("ERROR: DVB driver buffer overflow on device %d", CardIndex() + 1); - else { - LOG_ERROR; - return false; - } - } + Data = tsBuffer->Get(); return true; } return false; diff --git a/dvbplayer.c b/dvbplayer.c index 8b60c2e1..c9cdc1ab 100644 --- a/dvbplayer.c +++ b/dvbplayer.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbplayer.c 1.24 2004/06/19 08:55:49 kls Exp $ + * $Id: dvbplayer.c 1.25 2004/10/15 13:07:55 kls Exp $ */ #include "dvbplayer.h" @@ -80,8 +80,7 @@ private: int length; bool hasData; bool active; - cMutex mutex; - cCondVar newSet; + cCondWait newSet; protected: void Action(void); public: @@ -106,20 +105,21 @@ cNonBlockingFileReader::cNonBlockingFileReader(void) cNonBlockingFileReader::~cNonBlockingFileReader() { active = false; - newSet.Broadcast(); + newSet.Signal(); Cancel(3); free(buffer); } void cNonBlockingFileReader::Clear(void) { - cMutexLock MutexLock(&mutex); + Lock(); f = -1; free(buffer); buffer = NULL; wanted = length = 0; hasData = false; - newSet.Broadcast(); + Unlock(); + newSet.Signal(); } int cNonBlockingFileReader::Read(int FileHandle, uchar *Buffer, int Length) @@ -139,7 +139,7 @@ int cNonBlockingFileReader::Read(int FileHandle, uchar *Buffer, int Length) wanted = Length; length = 0; hasData = false; - newSet.Broadcast(); + newSet.Signal(); } errno = EAGAIN; return -1; @@ -149,7 +149,7 @@ void cNonBlockingFileReader::Action(void) { active = true; while (active) { - cMutexLock MutexLock(&mutex); + Lock(); if (!hasData && f >= 0 && buffer) { int r = safe_read(f, buffer + length, wanted - length); if (r >= 0) { @@ -163,16 +163,14 @@ void cNonBlockingFileReader::Action(void) hasData = true; } } - newSet.TimedWait(mutex, 1000); + Unlock(); + newSet.Wait(1000); } } // --- cDvbPlayer ------------------------------------------------------------ -//XXX+ also used in recorder.c - find a better place??? -// The size of the array used to buffer video data: -// (must be larger than MINVIDEODATA - see remux.h) -#define VIDEOBUFSIZE MEGABYTE(1) +#define PLAYERBUFSIZE MEGABYTE(1) // The number of frames to back up when resuming an interrupted replay session: #define RESUMEBACKUP (10 * FRAMESPERSEC) @@ -257,7 +255,7 @@ cDvbPlayer::cDvbPlayer(const char *FileName) replayFile = fileName->Open(); if (replayFile < 0) return; - ringBuffer = new cRingBufferFrame(VIDEOBUFSIZE); + ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE); // Create the index file: index = new cIndexFile(FileName, false); if (!index) diff --git a/recorder.c b/recorder.c index 4270fbed..3508b0ae 100644 --- a/recorder.c +++ b/recorder.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.c 1.10 2004/03/20 10:33:21 kls Exp $ + * $Id: recorder.c 1.11 2004/10/16 09:23:01 kls Exp $ */ #include @@ -12,9 +12,7 @@ #include #include "recorder.h" -// The size of the array used to buffer video data: -// (must be larger than MINVIDEODATA - see remux.h) -#define VIDEOBUFSIZE MEGABYTE(5) +#define RECORDERBUFSIZE MEGABYTE(5) // The maximum time we wait before assuming that a recorded video data stream // is broken: @@ -23,25 +21,35 @@ #define MINFREEDISKSPACE (512) // MB #define DISKCHECKINTERVAL 100 // seconds -cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2) -:cReceiver(Ca, Priority, Setup.RecordDolbyDigital ? 5 : 3, VPid, APid1, APid2, DPid1, DPid2) -,cThread("recording") +class cFileWriter : public cThread { +private: + cRemux *remux; + cFileName *fileName; + cIndexFile *index; + uchar pictureType; + int fileSize; + int recordFile; + bool active; + time_t lastDiskSpaceCheck; + bool RunningLowOnDiskSpace(void); + bool NextFile(void); +protected: + virtual void Action(void); +public: + cFileWriter(const char *FileName, cRemux *Remux); + virtual ~cFileWriter(); + }; + +cFileWriter::cFileWriter(const char *FileName, cRemux *Remux) +:cThread("file writer") { - ringBuffer = NULL; - remux = NULL; + active = false; fileName = NULL; + remux = Remux; index = NULL; pictureType = NO_PICTURE; fileSize = 0; - active = false; lastDiskSpaceCheck = time(NULL); - - // Make sure the disk is up and running: - - SpinUpDisk(FileName); - - ringBuffer = new cRingBufferLinear(VIDEOBUFSIZE, TS_SIZE * 2, true); - remux = new cRemux(VPid, APid1, APid2, DPid1, DPid2, true); fileName = new cFileName(FileName, true); recordFile = fileName->Open(); if (recordFile < 0) @@ -53,28 +61,15 @@ cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, int A // let's continue without index, so we'll at least have the recording } -cRecorder::~cRecorder() +cFileWriter::~cFileWriter() { - Detach(); + active = false; + Cancel(3); delete index; delete fileName; - delete remux; - delete ringBuffer; } -void cRecorder::Activate(bool On) -{ - if (On) { - if (recordFile >= 0) - Start(); - } - else if (active) { - active = false; - Cancel(3); - } -} - -bool cRecorder::RunningLowOnDiskSpace(void) +bool cFileWriter::RunningLowOnDiskSpace(void) { if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { int Free = FreeDiskSpaceMB(fileName->Name()); @@ -87,7 +82,7 @@ bool cRecorder::RunningLowOnDiskSpace(void) return false; } -bool cRecorder::NextFile(void) +bool cFileWriter::NextFile(void) { if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) { @@ -98,40 +93,29 @@ bool cRecorder::NextFile(void) return recordFile >= 0; } -void cRecorder::Receive(uchar *Data, int Length) -{ - int p = ringBuffer->Put(Data, Length); - if (p != Length && active) - ringBuffer->ReportOverflow(Length - p); -} - -void cRecorder::Action(void) +void cFileWriter::Action(void) { time_t t = time(NULL); active = true; while (active) { - int r; - const uchar *b = ringBuffer->Get(r); - if (b) { - int Count = r, Result; - uchar *p = remux->Process(b, Count, Result, &pictureType); - ringBuffer->Del(Count); - if (p) { - //XXX+ active??? see old version (Busy) - if (!active && pictureType == I_FRAME) // finish the recording before the next 'I' frame + int Count; + uchar *p = remux->Get(Count, &pictureType); + if (p) { + //XXX+ active??? see old version (Busy) + if (!active && 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, Count) < 0) { + LOG_ERROR_STR(fileName->Name()); 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()); - break; - } - fileSize += Result; } - else - break; + fileSize += Count; + remux->Del(Count); } + else + break; t = time(NULL); } else if (time(NULL) - t > MAXBROKENTIMEOUT) { @@ -139,7 +123,65 @@ void cRecorder::Action(void) cThread::EmergencyExit(true); t = time(NULL); } - else - usleep(1); // this keeps the CPU load low + } + active = false; +} + +cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2) +:cReceiver(Ca, Priority, Setup.RecordDolbyDigital ? 5 : 3, VPid, APid1, APid2, DPid1, DPid2) +,cThread("recording") +{ + active = false; + + // Make sure the disk is up and running: + + SpinUpDisk(FileName); + + ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder"); + ringBuffer->SetTimeouts(0, 100); + remux = new cRemux(VPid, APid1, APid2, DPid1, DPid2, true); + writer = new cFileWriter(FileName, remux); +} + +cRecorder::~cRecorder() +{ + Detach(); + delete writer; + delete remux; + delete ringBuffer; +} + +void cRecorder::Activate(bool On) +{ + if (On) { + writer->Start(); + Start(); + } + else if (active) { + active = false; + Cancel(3); + } +} + +void cRecorder::Receive(uchar *Data, int Length) +{ + if (active) { + int p = ringBuffer->Put(Data, Length); + if (p != Length && active) + ringBuffer->ReportOverflow(Length - p); + } +} + +void cRecorder::Action(void) +{ + active = true; + while (active) { + int r; + uchar *b = ringBuffer->Get(r); + if (b) { + int Count = remux->Put(b, r); + if (Count) + ringBuffer->Del(Count); + } } } diff --git a/recorder.h b/recorder.h index 7493f0b2..2a0875dd 100644 --- a/recorder.h +++ b/recorder.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.h 1.1 2002/06/10 16:30:00 kls Exp $ + * $Id: recorder.h 1.2 2004/10/10 11:22:38 kls Exp $ */ #ifndef __RECORDER_H @@ -16,19 +16,14 @@ #include "ringbuffer.h" #include "thread.h" +class cFileWriter; + class cRecorder : public cReceiver, cThread { private: cRingBufferLinear *ringBuffer; cRemux *remux; - cFileName *fileName; - cIndexFile *index; - uchar pictureType; - int fileSize; - int recordFile; + cFileWriter *writer; bool active; - time_t lastDiskSpaceCheck; - bool RunningLowOnDiskSpace(void); - bool NextFile(void); protected: virtual void Activate(bool On); virtual void Receive(uchar *Data, int Length); diff --git a/remux.c b/remux.c index 65544b5e..9b3b2864 100644 --- a/remux.c +++ b/remux.c @@ -8,63 +8,9 @@ * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit * VDR's needs. * - * $Id: remux.c 1.18 2004/02/14 10:40:37 kls Exp $ + * $Id: remux.c 1.19 2004/10/16 09:11:52 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 #include "thread.h" @@ -133,8 +79,7 @@ private: uint8_t check; int which; bool done; - uint8_t *resultBuffer; - int *resultCount; + cRingBufferLinear *resultBuffer; int tsErrors; int ccErrors; int ccCounter; @@ -145,7 +90,7 @@ private: void write_ipack(const uint8_t *Data, int Count); void instant_repack(const uint8_t *Buf, int Count); public: - cTS2PES(uint8_t *ResultBuffer, int *ResultCount, int Size, uint8_t AudioCid = 0x00); + cTS2PES(cRingBufferLinear *ResultBuffer, int Size, uint8_t AudioCid = 0x00); ~cTS2PES(); void ts_to_pes(const uint8_t *Buf); // don't need count (=188) void Clear(void); @@ -153,10 +98,9 @@ public: uint8_t cTS2PES::headr[] = { 0x00, 0x00, 0x01 }; -cTS2PES::cTS2PES(uint8_t *ResultBuffer, int *ResultCount, int Size, uint8_t AudioCid) +cTS2PES::cTS2PES(cRingBufferLinear *ResultBuffer, int Size, uint8_t AudioCid) { resultBuffer = ResultBuffer; - resultCount = ResultCount; size = Size; audioCid = AudioCid; @@ -184,12 +128,9 @@ void cTS2PES::Clear(void) void cTS2PES::store(uint8_t *Data, int Count) { - if (*resultCount + Count > RESULTBUFFERSIZE) { - esyslog("ERROR: result buffer overflow (%d + %d > %d)", *resultCount, Count, RESULTBUFFERSIZE); - Count = RESULTBUFFERSIZE - *resultCount; - } - memcpy(resultBuffer + *resultCount, Data, Count); - *resultCount += Count; + int n = resultBuffer->Put(Data, Count); + if (n != Count) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); } void cTS2PES::reset_ipack(void) @@ -452,6 +393,8 @@ void cTS2PES::ts_to_pes(const uint8_t *Buf) // don't need count (=188) // --- cRemux ---------------------------------------------------------------- +#define RESULTBUFFERSIZE KILOBYTE(256) + cRemux::cRemux(int VPid, int APid1, int APid2, int DPid1, int DPid2, bool ExitOnFailure) { vPid = VPid; @@ -463,13 +406,15 @@ cRemux::cRemux(int VPid, int APid1, int APid2, int DPid1, int DPid2, bool ExitOn numUPTerrors = 0; synced = false; skipped = 0; - resultCount = resultDelivered = 0; - vTS2PES = new cTS2PES(resultBuffer, &resultCount, IPACKS); - aTS2PES1 = new cTS2PES(resultBuffer, &resultCount, IPACKS, 0xC0); - aTS2PES2 = aPid2 ? new cTS2PES(resultBuffer, &resultCount, IPACKS, 0xC1) : NULL; - dTS2PES1 = dPid1 ? new cTS2PES(resultBuffer, &resultCount, IPACKS) : NULL; + resultSkipped = 0; + resultBuffer = new cRingBufferLinear(RESULTBUFFERSIZE, IPACKS, false, "Result"); + resultBuffer->SetTimeouts(0, 100); + vTS2PES = new cTS2PES(resultBuffer, IPACKS); + aTS2PES1 = new cTS2PES(resultBuffer, IPACKS, 0xC0); + aTS2PES2 = aPid2 ? new cTS2PES(resultBuffer, IPACKS, 0xC1) : NULL; + dTS2PES1 = dPid1 ? new cTS2PES(resultBuffer, IPACKS) : NULL; //XXX don't yet know how to tell apart primary and secondary DD data... - dTS2PES2 = /*XXX dPid2 ? new cTS2PES(resultBuffer, &resultCount, IPACKS) : XXX*/ NULL; + dTS2PES2 = /*XXX dPid2 ? new cTS2PES(resultBuffer, IPACKS) : XXX*/ NULL; } cRemux::~cRemux() @@ -479,6 +424,7 @@ cRemux::~cRemux() delete aTS2PES2; delete dTS2PES1; delete dTS2PES2; + delete resultBuffer; } int cRemux::GetPid(const uchar *Data) @@ -488,27 +434,32 @@ int cRemux::GetPid(const uchar *Data) 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; + // Returns the length of the packet starting at Offset, or -1 if Count is + // too small to contain the entire packet. + int Length = (Offset + 5 < Count) ? (Data[Offset + 4] << 8) + Data[Offset + 5] + 6 : -1; + if (Length > 0 && Offset + Length <= Count) + return Length; + return -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; - } + if (Length > 0) { + if (Length >= 8) { + 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; } @@ -517,28 +468,8 @@ int cRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &Pic #define TS_SYNC_BYTE 0x47 -uchar *cRemux::Process(const uchar *Data, int &Count, int &Result, uchar *PictureType) +int cRemux::Put(const uchar *Data, int Count) { - uchar dummyPictureType; - if (!PictureType) - PictureType = &dummyPictureType; - -/*XXX - // test recording the raw TS: - Result = Count; - *PictureType = I_FRAME; - return Data; -XXX*/ - - // Remove any previously delivered data from the result buffer: - - if (resultDelivered) { - if (resultDelivered < resultCount) - memmove(resultBuffer, resultBuffer + resultDelivered, resultCount - resultDelivered); - resultCount -= resultDelivered; - resultDelivered = 0; - } - int used = 0; // Make sure we are looking at a TS packet: @@ -560,6 +491,8 @@ XXX*/ break; if (Data[i] != TS_SYNC_BYTE) break; + if (resultBuffer->Free() < IPACKS) + break; int pid = GetPid(Data + i + 1); if (Data[i + 3] & 0x10) { // got payload if (pid == vPid) vTS2PES->ts_to_pes(Data + i); @@ -569,31 +502,9 @@ XXX*/ else if (pid == dPid2 && dTS2PES2) dTS2PES2->ts_to_pes(Data + i); } used += TS_SIZE; - if (resultCount > (int)sizeof(resultBuffer) / 2) - break; } - Count = used; - -/*XXX - // test recording without determining the real frame borders: - *PictureType = I_FRAME; - Result = resultDelivered = resultCount; - return Result ? resultBuffer : NULL; -XXX*/ - - // Special VPID case to enable recording radio channels: - - if (vPid == 0 || vPid == 1 || vPid == 0x1FFF) { - // XXX actually '0' should be enough, but '1' must be used with encrypted channels (driver bug?) - // XXX also allowing 0x1FFF to not break Michael Paar's original patch, - // XXX but it would probably be best to only use '0' - *PictureType = I_FRAME; - Result = resultDelivered = resultCount; - return Result ? resultBuffer : NULL; - } // Check if we're getting anywhere here: - if (!synced && skipped >= 0) { if (skipped > MAXNONUSEFULDATA) { esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); @@ -602,77 +513,112 @@ XXX*/ cThread::EmergencyExit(true); } else - skipped += Count; + skipped += used; + } + + return used; +} + +uchar *cRemux::Get(int &Count, uchar *PictureType) +{ + // Remove any previously skipped data from the result buffer: + + if (resultSkipped > 0) { + resultBuffer->Del(resultSkipped); + resultSkipped = 0; + } + +#if 0 + // Test recording without determining the real frame borders: + if (PictureType) + *PictureType = I_FRAME; + return resultBuffer->Get(Count); +#endif + + // Special VPID case to enable recording radio channels: + + if (vPid == 0 || vPid == 1 || vPid == 0x1FFF) { + // XXX actually '0' should be enough, but '1' must be used with encrypted channels (driver bug?) + // XXX also allowing 0x1FFF to not break Michael Paar's original patch, + // XXX but it would probably be best to only use '0' + if (PictureType) + *PictureType = I_FRAME; + return resultBuffer->Get(Count); } // Check for frame borders: - *PictureType = NO_PICTURE; + if (PictureType) + *PictureType = NO_PICTURE; - if (resultCount >= MINVIDEODATA) { - for (int i = 0; i < resultCount; i++) { - if (resultBuffer[i] == 0 && resultBuffer[i + 1] == 0 && resultBuffer[i + 2] == 1) { - switch (resultBuffer[i + 3]) { - case VIDEO_STREAM_S ... VIDEO_STREAM_E: - { - uchar pt = NO_PICTURE; - int l = ScanVideoPacket(resultBuffer, resultCount, i, pt); - if (l < 0) - return NULL; // no useful data found, wait for more - if (pt != NO_PICTURE) { - if (pt < I_FRAME || B_FRAME < pt) { - esyslog("ERROR: unknown picture type '%d'", pt); - if (++numUPTerrors > MAXNUMUPTERRORS && exitOnFailure) - cThread::EmergencyExit(true); - } - else if (!synced) { - if (pt == I_FRAME) { - resultDelivered = i; // will drop everything before this position - SetBrokenLink(resultBuffer + i, l); - synced = true; - } - else { - resultDelivered = i + l; // will drop everything before and including this packet - return NULL; - } - } + Count = 0; + uchar *resultData = NULL; + int resultCount = 0; + uchar *data = resultBuffer->Get(resultCount); + if (data) { + for (int i = 0; i < resultCount - 3; i++) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + int l = 0; + uchar StreamType = data[i + 3]; + if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { + uchar pt = NO_PICTURE; + l = ScanVideoPacket(data, resultCount, i, pt); + if (l < 0) + return resultData; + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog("ERROR: unknown picture type '%d'", pt); + if (++numUPTerrors > MAXNUMUPTERRORS && exitOnFailure) + cThread::EmergencyExit(true); + } + else if (!synced) { + if (pt == I_FRAME) { + if (PictureType) + *PictureType = pt; + resultSkipped = i; // will drop everything before this position + SetBrokenLink(data + i, l); + synced = true; } - if (synced) { - *PictureType = pt; - Result = l; - uchar *p = resultBuffer + resultDelivered; - resultDelivered += l; - return p; - } - else { - resultDelivered = i + l; // will drop everything before and including this packet - return NULL; - } - } - break; - case PRIVATE_STREAM1: - case AUDIO_STREAM_S ... AUDIO_STREAM_E: - { - int l = GetPacketLength(resultBuffer, resultCount, i); - if (l < 0) - return NULL; // no useful data found, wait for more - if (synced) { - Result = l; - uchar *p = resultBuffer + resultDelivered; - resultDelivered += l; - return p; - } - else { - resultDelivered = i + l; // will drop everything before and including this packet - return NULL; - } - } - break; - } + } + else if (Count) + return resultData; + else if (PictureType) + *PictureType = pt; + } + } + else { //if (AUDIO_STREAM_S <= StreamType && StreamType <= AUDIO_STREAM_E || StreamType == PRIVATE_STREAM1) { + l = GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; + } + if (synced) { + if (!Count) + resultData = data + i; + Count += l; + } + else + resultSkipped = i; + if (l > 0) + i += l - 1; // the loop increments, too } } } - return NULL; // no useful data found, wait for more + return resultData; +} + +void cRemux::Del(int Count) +{ + resultBuffer->Del(Count); +} + +void cRemux::Clear(void) +{ + if (vTS2PES) vTS2PES->Clear(); + if (aTS2PES1) aTS2PES1->Clear(); + if (aTS2PES2) aTS2PES2->Clear(); + if (dTS2PES1) dTS2PES1->Clear(); + if (dTS2PES2) dTS2PES2->Clear(); + resultBuffer->Clear(); } void cRemux::SetBrokenLink(uchar *Data, int Length) diff --git a/remux.h b/remux.h index 31e64dfb..0475c012 100644 --- a/remux.h +++ b/remux.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 1.11 2004/02/14 10:40:41 kls Exp $ + * $Id: remux.h 1.12 2004/10/15 12:31:16 kls Exp $ */ #ifndef __REMUX_H @@ -12,6 +12,7 @@ #include //XXX FIXME: DVB/linux/dvb/dmx.h should include itself!!! #include +#include "ringbuffer.h" #include "tools.h" // Picture types: @@ -20,11 +21,6 @@ #define P_FRAME 2 #define B_FRAME 3 -// The minimum amount of video data necessary to identify frames: -#define MINVIDEODATA (16*1024) // just a safe guess (max. size of any frame block, plus some safety) - -#define RESULTBUFFERSIZE (MINVIDEODATA * 4) - class cTS2PES; class cRemux { @@ -35,16 +31,30 @@ private: int skipped; int vPid, aPid1, aPid2, dPid1, dPid2; cTS2PES *vTS2PES, *aTS2PES1, *aTS2PES2, *dTS2PES1, *dTS2PES2; - uchar resultBuffer[RESULTBUFFERSIZE]; - int resultCount; - int resultDelivered; + cRingBufferLinear *resultBuffer; + int resultSkipped; int GetPid(const uchar *Data); int GetPacketLength(const uchar *Data, int Count, int Offset); int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); public: cRemux(int VPid, int APid1, int APid2, int DPid1, int DPid2, bool ExitOnFailure = false); ~cRemux(); - uchar *Process(const uchar *Data, int &Count, int &Result, uchar *PictureType = NULL); + int Put(const uchar *Data, int Count); + ///< Puts at most Count bytes of Data into the remuxer. + ///< \return Returns the number of bytes actually consumed from Data. + uchar *Get(int &Count, uchar *PictureType = NULL); + ///< Gets all currently available data from the remuxer. + ///< \return Count contains the number of bytes the result points to, and + ///< PictureType (if not NULL) will contain one of NO_PICTURE, I_FRAME, P_FRAME + ///< or B_FRAME. + void Del(int Count); + ///< Deletes Count bytes from the remuxer. Count must be the number returned + ///< from a previous call to Get(). Several calls to Del() with fractions of + ///< a previously returned Count may be made, but the total sum of all Count + ///< values must be exactly what the previous Get() has returned. + void Clear(void); + ///< Clears the remuxer of all data it might still contain, keeping the PID + ///< settings as they are. static void SetBrokenLink(uchar *Data, int Length); }; diff --git a/ringbuffer.c b/ringbuffer.c index b52c37e5..3473a9e7 100644 --- a/ringbuffer.c +++ b/ringbuffer.c @@ -7,7 +7,7 @@ * Parts of this file were inspired by the 'ringbuffy.c' from the * LinuxDVB driver (see linuxtv.org). * - * $Id: ringbuffer.c 1.20 2004/06/19 12:27:56 kls Exp $ + * $Id: ringbuffer.c 1.21 2004/10/15 13:49:25 kls Exp $ */ #include "ringbuffer.h" @@ -18,11 +18,14 @@ // --- cRingBuffer ----------------------------------------------------------- #define OVERFLOWREPORTDELTA 5 // seconds between reports +#define PERCENTAGEDELTA 10 +#define PERCENTAGETHRESHOLD 70 cRingBuffer::cRingBuffer(int Size, bool Statistics) { size = Size; statistics = Statistics; + getThreadTid = 0; maxFill = 0; lastPercent = 0; putTimeout = getTimeout = 0; @@ -36,34 +39,41 @@ cRingBuffer::~cRingBuffer() dsyslog("buffer stats: %d (%d%%) used", maxFill, maxFill * 100 / (size - 1)); } +void cRingBuffer::UpdatePercentage(int Fill) +{ + if (Fill > maxFill) + maxFill = Fill; + int percent = Fill * 100 / (Size() - 1) / PERCENTAGEDELTA * PERCENTAGEDELTA; + if (percent != lastPercent) { + if (percent >= PERCENTAGETHRESHOLD && percent > lastPercent || percent < PERCENTAGETHRESHOLD && lastPercent >= PERCENTAGETHRESHOLD) { + dsyslog("buffer usage: %d%% (tid=%ld)", percent, getThreadTid); + lastPercent = percent; + } + } +} + void cRingBuffer::WaitForPut(void) { - if (putTimeout) { - putMutex.Lock(); - readyForPut.TimedWait(putMutex, putTimeout); - putMutex.Unlock(); - } + if (putTimeout) + readyForPut.Wait(putTimeout); } void cRingBuffer::WaitForGet(void) { - if (getTimeout) { - getMutex.Lock(); - readyForGet.TimedWait(getMutex, getTimeout); - getMutex.Unlock(); - } + if (getTimeout) + readyForGet.Wait(getTimeout); } void cRingBuffer::EnablePut(void) { - if (putTimeout) - readyForPut.Broadcast(); + if (putTimeout && Free() > Size() / 3) + readyForPut.Signal(); } void cRingBuffer::EnableGet(void) { - if (getTimeout) - readyForGet.Broadcast(); + if (getTimeout && Available() > Size() / 3) + readyForGet.Signal(); } void cRingBuffer::SetTimeouts(int PutTimeout, int GetTimeout) @@ -85,70 +95,168 @@ void cRingBuffer::ReportOverflow(int Bytes) // --- cRingBufferLinear ----------------------------------------------------- -cRingBufferLinear::cRingBufferLinear(int Size, int Margin, bool Statistics) +#ifdef DEBUGRINGBUFFERS +#define MAXRBLS 30 +#define DEBUGRBLWIDTH 45 + +cRingBufferLinear *cRingBufferLinear::RBLS[MAXRBLS] = { NULL }; + +void cRingBufferLinear::AddDebugRBL(cRingBufferLinear *RBL) +{ + for (int i = 0; i < MAXRBLS; i++) { + if (!RBLS[i]) { + RBLS[i] = RBL; + break; + } + } +} + +void cRingBufferLinear::DelDebugRBL(cRingBufferLinear *RBL) +{ + for (int i = 0; i < MAXRBLS; i++) { + if (RBLS[i] == RBL) { + RBLS[i] = NULL; + break; + } + } +} + +void cRingBufferLinear::PrintDebugRBL(void) +{ + bool printed = false; + for (int i = 0; i < MAXRBLS; i++) { + cRingBufferLinear *p = RBLS[i]; + if (p) { + printed = true; + int lh = p->lastHead; + int lt = p->lastTail; + int h = lh * DEBUGRBLWIDTH / p->Size(); + int t = lt * DEBUGRBLWIDTH / p->Size(); + char buf[DEBUGRBLWIDTH + 10]; + memset(buf, '-', DEBUGRBLWIDTH); + if (lt <= lh) + memset(buf + t, '*', max(h - t, 1)); + else { + memset(buf, '*', h); + memset(buf + t, '*', DEBUGRBLWIDTH - t); + } + buf[t] = '<'; + buf[h] = '>'; + buf[DEBUGRBLWIDTH] = 0; + printf("%2d %s %8d %8d %s\n", i, buf, p->lastPut, p->lastGet, p->description); + } + } + if (printed) + printf("\n"); + } +#endif + +cRingBufferLinear::cRingBufferLinear(int Size, int Margin, bool Statistics, const char *Description) :cRingBuffer(Size, Statistics) { - margin = Margin; + description = Description ? strdup(Description) : NULL; + tail = head = margin = Margin; buffer = NULL; - getThreadTid = 0; if (Size > 1) { // 'Size - 1' must not be 0! - buffer = MALLOC(uchar, Size); - if (!buffer) - esyslog("ERROR: can't allocate ring buffer (size=%d)", Size); - Clear(); + if (Margin <= Size / 2) { + buffer = MALLOC(uchar, Size); + if (!buffer) + esyslog("ERROR: can't allocate ring buffer (size=%d)", Size); + Clear(); + } + else + esyslog("ERROR: illegal margin for ring buffer (%d > %d)", Margin, Size / 2); } else esyslog("ERROR: illegal size for ring buffer (%d)", Size); +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastTail = tail; + lastPut = lastGet = -1; + AddDebugRBL(this); +#endif } cRingBufferLinear::~cRingBufferLinear() { +#ifdef DEBUGRINGBUFFERS + DelDebugRBL(this); +#endif free(buffer); + free(description); } int cRingBufferLinear::Available(void) { - Lock(); int diff = head - tail; - Unlock(); return (diff >= 0) ? diff : Size() + diff - margin; } void cRingBufferLinear::Clear(void) { - Lock(); - head = tail = margin; - lastGet = -1; - Unlock(); + tail = head; +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastTail = tail; + lastPut = lastGet = -1; +#endif + maxFill = 0; EnablePut(); +} + +int cRingBufferLinear::Read(int FileHandle, int Max) +{ + int Tail = tail; + int diff = Tail - head; + int free = (diff > 0) ? diff - 1 : Size() - head; + if (Tail <= margin) + free--; + int Count = 0; + if (free > 0) { + if (0 < Max && Max < free) + free = Max; + Count = safe_read(FileHandle, buffer + head, free); + if (Count > 0) { + int Head = head + Count; + if (Head >= Size()) + Head = margin; + head = Head; + if (statistics) { + int fill = head - Tail; + if (fill < 0) + fill = Size() + fill; + else if (fill >= Size()) + fill = Size() - 1; + UpdatePercentage(fill); + } + } + } +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastPut = Count; +#endif EnableGet(); + if (free == 0) + WaitForPut(); + return Count; } int cRingBufferLinear::Put(const uchar *Data, int Count) { if (Count > 0) { - Lock(); + int Tail = tail; int rest = Size() - head; - int diff = tail - head; - int free = ((tail < margin) ? rest : (diff > 0) ? diff : Size() + diff - margin) - 1; + int diff = Tail - head; + int free = ((Tail < margin) ? rest : (diff > 0) ? diff : Size() + diff - margin) - 1; if (statistics) { int fill = Size() - free - 1 + Count; if (fill >= Size()) fill = Size() - 1; - if (fill > maxFill) - maxFill = fill; - int percent = maxFill * 100 / (Size() - 1) / 5 * 5; - if (abs(lastPercent - percent) >= 5) { - if (percent > 75) - dsyslog("buffer usage: %d%% (tid=%ld)", percent, getThreadTid); - lastPercent = percent; - } + UpdatePercentage(fill); } if (free > 0) { if (free < Count) Count = free; - if (Count > maxFill) - maxFill = Count; if (Count >= rest) { memcpy(buffer + head, Data, rest); if (Count - rest) @@ -162,7 +270,10 @@ int cRingBufferLinear::Put(const uchar *Data, int Count) } else Count = 0; - Unlock(); +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastPut = Count; +#endif EnableGet(); if (Count == 0) WaitForPut(); @@ -173,25 +284,24 @@ int cRingBufferLinear::Put(const uchar *Data, int Count) uchar *cRingBufferLinear::Get(int &Count) { uchar *p = NULL; - Lock(); + int Head = head; if (getThreadTid <= 0) getThreadTid = pthread_self(); int rest = Size() - tail; - if (rest < margin && head < tail) { + if (rest < margin && Head < tail) { int t = margin - rest; memcpy(buffer + t, buffer + tail, rest); tail = t; - rest = head - tail; + rest = Head - tail; } - int diff = head - tail; + int diff = Head - tail; int cont = (diff >= 0) ? diff : Size() + diff - margin; if (cont > rest) cont = rest; if (cont >= margin) { p = buffer + tail; - Count = lastGet = cont; + Count = gotten = cont; } - Unlock(); if (!p) WaitForGet(); return p; @@ -199,17 +309,23 @@ uchar *cRingBufferLinear::Get(int &Count) void cRingBufferLinear::Del(int Count) { - if (Count > 0 && Count <= lastGet) { - Lock(); - tail += Count; - lastGet -= Count; - if (tail >= Size()) - tail = margin; - Unlock(); + if (Count > gotten) { + esyslog("ERROR: invalid Count in cRingBufferLinear::Del: %d (limited to %d)", Count, gotten); + Count = gotten; + } + if (Count > 0) { + int Tail = tail; + Tail += Count; + gotten -= Count; + if (Tail >= Size()) + Tail = margin; + tail = Tail; EnablePut(); } - else - esyslog("ERROR: invalid Count in cRingBufferLinear::Del: %d", Count); +#ifdef DEBUGRINGBUFFERS + lastTail = tail; + lastGet = Count; +#endif } // --- cFrame ---------------------------------------------------------------- diff --git a/ringbuffer.h b/ringbuffer.h index f4074886..c47aac3f 100644 --- a/ringbuffer.h +++ b/ringbuffer.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ringbuffer.h 1.15 2004/06/19 10:32:15 kls Exp $ + * $Id: ringbuffer.h 1.16 2004/10/15 13:50:46 kls Exp $ */ #ifndef __RINGBUFFER_H @@ -15,9 +15,7 @@ class cRingBuffer { private: - cMutex mutex; - cCondVar readyForPut, readyForGet; - cMutex putMutex, getMutex; + cCondWait readyForPut, readyForGet; int putTimeout; int getTimeout; int size; @@ -25,18 +23,18 @@ private: int overflowCount; int overflowBytes; protected: + pthread_t getThreadTid; int maxFill;//XXX int lastPercent; bool statistics;//XXX + void UpdatePercentage(int Fill); void WaitForPut(void); void WaitForGet(void); void EnablePut(void); void EnableGet(void); virtual void Clear(void) = 0; virtual int Available(void) = 0; - int Free(void) { return size - Available() - 1; } - void Lock(void) { mutex.Lock(); } - void Unlock(void) { mutex.Unlock(); } + virtual int Free(void) { return Size() - Available() - 1; } int Size(void) { return size; } public: cRingBuffer(int Size, bool Statistics = false); @@ -46,20 +44,39 @@ public: }; class cRingBufferLinear : public cRingBuffer { +//#define DEBUGRINGBUFFERS +#ifdef DEBUGRINGBUFFERS +private: + int lastHead, lastTail; + int lastPut, lastGet; + static cRingBufferLinear *RBLS[]; + static void AddDebugRBL(cRingBufferLinear *RBL); + static void DelDebugRBL(cRingBufferLinear *RBL); +public: + static void PrintDebugRBL(void); +#endif private: int margin, head, tail; - int lastGet; + int gotten; uchar *buffer; - pthread_t getThreadTid; + char *description; public: - cRingBufferLinear(int Size, int Margin = 0, bool Statistics = false); + cRingBufferLinear(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL); ///< Creates a linear ring buffer. ///< The buffer will be able to hold at most Size-Margin-1 bytes of data, and will ///< be guaranteed to return at least Margin bytes in one consecutive block. + ///< The optional Description is used for debugging only. virtual ~cRingBufferLinear(); virtual int Available(void); + virtual int Free(void) { return Size() - Available() - 1 - margin; } virtual void Clear(void); ///< Immediately clears the ring buffer. + int Read(int FileHandle, int Max = 0); + ///< Reads at most Max bytes from FileHandle and stores them in the + ///< ring buffer. If Max is 0, reads as many bytes as possible. + ///< Only one actual read() call is done. + ///< \return Returns the number of bytes actually read and stored, or + ///< an error value from the actual read() call. int Put(const uchar *Data, int Count); ///< Puts at most Count bytes of Data into the ring buffer. ///< \return Returns the number of bytes actually stored. @@ -67,7 +84,7 @@ public: ///< Gets data from the ring buffer. ///< The data will remain in the buffer until a call to Del() deletes it. ///< \return Returns a pointer to the data, and stores the number of bytes - ///< actually retrieved in Count. If the returned pointer is NULL, Count has no meaning. + ///< actually available in Count. If the returned pointer is NULL, Count has no meaning. void Del(int Count); ///< Deletes at most Count bytes from the ring buffer. ///< Count must be less or equal to the number that was returned by a previous @@ -98,9 +115,12 @@ public: class cRingBufferFrame : public cRingBuffer { private: + cMutex mutex; cFrame *head; int currentFill; void Delete(cFrame *Frame); + void Lock(void) { mutex.Lock(); } + void Unlock(void) { mutex.Unlock(); } public: cRingBufferFrame(int Size, bool Statistics = false); virtual ~cRingBufferFrame(); diff --git a/thread.c b/thread.c index 084a4295..64b944b6 100644 --- a/thread.c +++ b/thread.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.c 1.31 2004/03/14 16:48:30 kls Exp $ + * $Id: thread.c 1.32 2004/10/15 13:15:02 kls Exp $ */ #include "thread.h" @@ -18,6 +18,58 @@ #include #include "tools.h" +// --- cCondWait ------------------------------------------------------------- + +cCondWait::cCondWait(void) +{ + signaled = false; + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); +} + +cCondWait::~cCondWait() +{ + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); +} + +bool cCondWait::Wait(int TimeoutMs) +{ + pthread_mutex_lock(&mutex); + if (!signaled) { + if (TimeoutMs) { + struct timeval now; + if (gettimeofday(&now, NULL) == 0) { // get current time + now.tv_usec += TimeoutMs * 1000; // add the timeout + int sec = now.tv_usec / 1000000; + now.tv_sec += sec; + now.tv_usec -= sec * 1000000; + struct timespec abstime; // build timespec for timedwait + abstime.tv_sec = now.tv_sec; // seconds + abstime.tv_nsec = now.tv_usec * 1000; // nano seconds + while (!signaled) { + if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT) + break; + } + } + } + else + pthread_cond_wait(&cond, &mutex); + } + bool r = signaled; + signaled = false; + pthread_mutex_unlock(&mutex); + return r; +} + +void cCondWait::Signal(void) +{ + pthread_mutex_lock(&mutex); + signaled = true; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); +} + // --- cCondVar -------------------------------------------------------------- cCondVar::cCondVar(void) @@ -73,13 +125,6 @@ void cCondVar::Broadcast(void) pthread_cond_broadcast(&cond); } -/* -void cCondVar::Signal(void) -{ - pthread_cond_signal(&cond); -} -*/ - // --- cRwLock --------------------------------------------------------------- cRwLock::cRwLock(bool PreferWriter) diff --git a/thread.h b/thread.h index 1804a5a6..7d9254c0 100644 --- a/thread.h +++ b/thread.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.h 1.20 2004/01/03 16:58:50 kls Exp $ + * $Id: thread.h 1.21 2004/10/15 13:16:39 kls Exp $ */ #ifndef __THREAD_H @@ -14,6 +14,23 @@ #include #include +class cCondWait { +private: + pthread_mutex_t mutex; + pthread_cond_t cond; + bool signaled; +public: + cCondWait(void); + ~cCondWait(); + bool Wait(int TimeoutMs = 0); + ///< Waits at most TimeoutMs milliseconds for a call to Signal(), or + ///< forever if TimeoutMs is 0. + ///< \return Returns true if Signal() has been called, false it the given + ///< timeout has expired. + void Signal(void); + ///< Signals a caller of Wait() that the condition it is waiting for is met. + }; + class cMutex; class cCondVar { @@ -25,7 +42,6 @@ public: void Wait(cMutex &Mutex); bool TimedWait(cMutex &Mutex, int TimeoutMs); void Broadcast(void); - //void Signal(void); }; class cRwLock { diff --git a/transfer.c b/transfer.c index c709928e..a7914392 100644 --- a/transfer.c +++ b/transfer.c @@ -4,15 +4,12 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: transfer.c 1.16 2004/03/07 14:40:15 kls Exp $ + * $Id: transfer.c 1.17 2004/10/16 09:22:58 kls Exp $ */ #include "transfer.h" -//XXX+ also used in recorder.c - find a better place??? -// The size of the array used to buffer video data: -// (must be larger than MINVIDEODATA - see remux.h) -#define VIDEOBUFSIZE MEGABYTE(1) +#define TRANSFERBUFSIZE MEGABYTE(2) #define POLLTIMEOUTS_BEFORE_DEVICECLEAR 3 // --- cTransfer ------------------------------------------------------------- @@ -21,11 +18,10 @@ cTransfer::cTransfer(int VPid, int APid1, int APid2, int DPid1, int DPid2) :cReceiver(0, -1, 5, VPid, APid1, APid2, DPid1, DPid2) ,cThread("transfer") { - ringBuffer = new cRingBufferLinear(VIDEOBUFSIZE, TS_SIZE * 2, true); + ringBuffer = new cRingBufferLinear(TRANSFERBUFSIZE, TS_SIZE * 2, true, "Transfer"); remux = new cRemux(VPid, APid1, APid2, DPid1, DPid2); canToggleAudioTrack = false; audioTrack = 0xC0; - gotBufferReserve = false; active = false; } @@ -51,79 +47,69 @@ void cTransfer::Activate(bool On) void cTransfer::Receive(uchar *Data, int Length) { - if (IsAttached()) { - int i = 0; - while (active && Length > 0) { - if (i++ > 10) { - ringBuffer->ReportOverflow(Length); - break; - } - int p = ringBuffer->Put(Data, Length); - Length -= p; - Data += p; - } + if (IsAttached() && active) { + int p = ringBuffer->Put(Data, Length); + if (p != Length && active) + ringBuffer->ReportOverflow(Length - p); + return; } } void cTransfer::Action(void) { int PollTimeouts = 0; + uchar *p = NULL; + int Result = 0; active = true; while (active) { - - //XXX+ Maybe we need this to avoid buffer underruns in driver. - //XXX+ But then again, it appears to play just fine without this... - /* - if (!gotBufferReserve) { - if (ringBuffer->Available() < 4 * MAXFRAMESIZE) { - usleep(100000); // allow the buffer to collect some reserve + int Count; + uchar *b = ringBuffer->Get(Count); + if (b) { + if (Count > TRANSFERBUFSIZE * 2 / 3) { + // If the buffer runs full, we have no chance of ever catching up + // since the data comes in at the same rate as it goes out (it's "live"). + // So let's clear the buffer instead of suffering from permanent + // overflows. + dsyslog("clearing transfer buffer to avoid overflows"); + ringBuffer->Clear(); + remux->Clear(); + p = NULL; continue; } - else - gotBufferReserve = true; + Count = remux->Put(b, Count); + if (Count) + ringBuffer->Del(Count); } - */ - - // Get data from the buffer: - - int r; - const uchar *b = ringBuffer->Get(r); - - // Play the data: - - if (b) { - int Count = r, Result; - uchar *p = remux->Process(b, Count, Result); - ringBuffer->Del(Count); - if (p) { - StripAudioPackets(p, Result, audioTrack); - while (Result > 0 && active) { - cPoller Poller; - if (DevicePoll(Poller, 100)) { - PollTimeouts = 0; - int w = PlayVideo(p, Result); - if (w > 0) { - p += w; - Result -= w; - } - else if (w < 0 && FATALERRNO) { - LOG_ERROR; - break; - } - } - else { - PollTimeouts++; - if (PollTimeouts == POLLTIMEOUTS_BEFORE_DEVICECLEAR) { - dsyslog("clearing device because of consecutive poll timeouts"); - DeviceClear(); - } - } - } + if (!p && (p = remux->Get(Result)) != NULL) + StripAudioPackets(p, Result, audioTrack); + if (p) { + cPoller Poller; + if (DevicePoll(Poller, 100)) { + PollTimeouts = 0; + int w = PlayVideo(p, Result); + if (w > 0) { + p += w; + Result -= w; + remux->Del(w); + if (Result <= 0) + p = NULL; + } + else if (w < 0 && FATALERRNO) + LOG_ERROR; + } + else { + PollTimeouts++; + if (PollTimeouts == POLLTIMEOUTS_BEFORE_DEVICECLEAR) { + dsyslog("clearing device because of consecutive poll timeouts"); + DeviceClear(); + ringBuffer->Clear(); + remux->Clear(); + p = NULL; + } } } - else - usleep(1); // this keeps the CPU load low } + active = false; } void cTransfer::StripAudioPackets(uchar *b, int Length, uchar Except) diff --git a/transfer.h b/transfer.h index 5ecb96ad..f33cb27b 100644 --- a/transfer.h +++ b/transfer.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: transfer.h 1.4 2003/05/11 08:48:36 kls Exp $ + * $Id: transfer.h 1.5 2004/10/15 12:39:54 kls Exp $ */ #ifndef __TRANSFER_H @@ -22,7 +22,6 @@ private: cRemux *remux; bool canToggleAudioTrack; uchar audioTrack; - bool gotBufferReserve; bool active; void StripAudioPackets(uchar *b, int Length, uchar Except = 0x00); protected: diff --git a/vdr.c b/vdr.c index 1215fcbb..bb4276a6 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/vdr * - * $Id: vdr.c 1.185 2004/07/27 07:21:22 kls Exp $ + * $Id: vdr.c 1.186 2004/10/10 12:47:56 kls Exp $ */ #include @@ -516,6 +516,9 @@ int main(int argc, char *argv[]) esyslog("emergency exit requested - shutting down"); break; } +#ifdef DEBUGRINGBUFFERS + cRingBufferLinear::PrintDebugRBL(); +#endif // Attach launched player control: cControl::Attach(); // Make sure we have a visible programme in case device usage has changed: