From patchwork Sat Jan 7 15:27:31 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Marc X-Patchwork-Id: 12921 Received: from mail.tu-berlin.de ([130.149.7.33]) by www.linuxtv.org with esmtp (Exim 4.72) (envelope-from ) id 1RjYBC-0000MD-VM for vdr@linuxtv.org; Sat, 07 Jan 2012 16:28:01 +0100 X-tubIT-Incoming-IP: 188.165.52.147 Received: from 8.mo2.mail-out.ovh.net ([188.165.52.147] helo=mo2.mail-out.ovh.net) by mail.tu-berlin.de (exim-4.75/mailfrontend-4) with esmtp for id 1RjYBC-0001r3-BO; Sat, 07 Jan 2012 16:27:34 +0100 Received: from mail21.ha.ovh.net (b9.ovh.net [213.186.33.59]) by mo2.mail-out.ovh.net (Postfix) with SMTP id D16AEDC2B23 for ; Sat, 7 Jan 2012 16:28:40 +0100 (CET) Received: from b0.ovh.net (HELO queueout) (213.186.33.50) by b0.ovh.net with SMTP; 7 Jan 2012 17:27:33 +0200 Received: from gob75-4-82-226-175-12.fbx.proxad.net (HELO ?192.168.0.10?) (mail?dev@ekass.net@82.226.175.12) by ns0.ovh.net with SMTP; 7 Jan 2012 17:27:31 +0200 Message-ID: <4F086463.3030900@ekass.net> Date: Sat, 07 Jan 2012 16:27:31 +0100 From: Marc User-Agent: Mozilla/5.0 (X11; Linux i686; rv:8.0) Gecko/20111214 Thunderbird/8.0 MIME-Version: 1.0 To: VDR Mailing List X-Ovh-Mailout: 178.32.228.2 (mo2.mail-out.ovh.net) References: <4F078F0E.6050804@hertell.com> <4F07A383.6000601@gmx.de> <4F081770.303@hertell.com> In-Reply-To: <4F081770.303@hertell.com> X-Ovh-Tracer-Id: 7233625426941917277 X-Ovh-Remote: 82.226.175.12 (gob75-4-82-226-175-12.fbx.proxad.net) X-Ovh-Local: 213.186.33.20 (ns0.ovh.net) X-OVH-SPAMSTATE: OK X-OVH-SPAMSCORE: 15 X-OVH-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeefkedrfeefucetggdotefuucfrrhhofhhilhgvmecuqfggjfenuceurghilhhouhhtmecufedttdenucgfrhhlucfvnfffucdludehmdenucfjughrpefkpfffhfgfggfvufhfjggtsehmtdgrrgdtfedunecuhfhrohhmpeforghrtgcuoehvughrsegvkhgrshhsrdhnvghtqeenucffohhmrghinheplhhinhhugihtvhdrohhrghen X-Spam-Check: DONE|U 0.5/N X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: 15 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeefkedrfeefucetggdotefuucfrrhhofhhilhgvmecuqfggjfenuceurghilhhouhhtmecufedttdenucgfrhhlucfvnfffucdludehmdenucfjughrpefkpfffhfgfggfvufhfjggtsehmtdgrrgdtfedunecuhfhrohhmpeforghrtgcuoehvughrsegvkhgrshhsrdhnvghtqeenucffohhmrghinheplhhinhhugihtvhdrohhrghen X-PMX-Version: 5.6.1.2065439, Antispam-Engine: 2.7.2.376379, Antispam-Data: 2012.1.7.151815 X-PMX-Spam: Gauge=IIIIIIII, Probability=8%, Report=' FROM_NAME_ONE_WORD 0.05, MIME_TEXT_ONLY_MP_MIXED 0.05, MSGID_ADDED_BY_MTA 0.05, BODYTEXTP_SIZE_3000_LESS 0, BODY_SIZE_10000_PLUS 0, __ANY_URI 0, __BAT_BOUNDARY 0, __BOUNCE_CHALLENGE_SUBJ 0, __BOUNCE_NDR_SUBJ_EXEMPT 0, __CP_URI_IN_BODY 0, __CT 0, __CTYPE_HAS_BOUNDARY 0, __CTYPE_MULTIPART 0, __CTYPE_MULTIPART_MIXED 0, __HAS_MSGID 0, __MIME_TEXT_ONLY 0, __MIME_VERSION 0, __MOZILLA_MSGID 0, __SANE_MSGID 0, __TO_MALFORMED_2 0, __URI_NS , __USER_AGENT 0' X-LSpam-Score: -1.9 (-) X-LSpam-Report: No, score=-1.9 required=5.0 tests=BAYES_00=-1.9 autolearn=ham Subject: Re: [vdr] problems with playback and plugins with vdr-1.7.22 X-BeenThere: vdr@linuxtv.org X-Mailman-Version: 2.1.13 Precedence: list Reply-To: VDR Mailing List List-Id: VDR Mailing List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 07 Jan 2012 15:28:01 -0000 Status: O X-Status: X-Keywords: X-UID: 25560 On 07/01/2012 10:59, René wrote: > On 07.01.2012 3:44 , Udo Richter wrote: > >> The cryptic symbol decodes to cTimer::cTimer(bool, bool, cChannel*), >> found in timers.h, line 46. The actual code is in timers.c, line 28. >> This function exists in this form since 1.3.38. VDR itself seems to be >> fine, so your VDR seems to have a different function instead. However, >> the plugins rely on the original function because they somehow compiled >> against the original definition in the timers.h. >> >> My guess is that one of the patches is optionally modifying this, and >> the plugins were somehow compiled with different versions of the >> timers.h, or with different compiler flags that cause some #ifdef to >> flip. > > Ok, i'll check if this would would work without any patches.. I doubt > that I would be the only one with this issue if it would be the > patches.. But again i would'nt surprise me if my system is messed up > because of this big jump from 1.6.x to 1.7.22. Til now VDR has been a > "set and forget" installation that just works, and works, and works > and wo... :-) > > Regards, > > René > > _______________________________________________ > vdr mailing list > vdr@linuxtv.org > http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr The problem come from the livebuffer patch. It redefines cTimer::cTimer to add the length of the already buffered stream so when the instant recording start, it records the buffer too : +#ifdef USE_LIVEBUFFER + cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL, int Forerun = 0); +#else cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL); +#endif /*USE_LIVEBUFFER*/ This feature can be removed easily (it's not really useful), I attach a modified version of the livebuffer patch. Thanks Udo for pointing to the right direction. Regards, Marc. diff -Naur vdr-1.7.22/Makefile vdr-1.7.22-livebuffer/Makefile --- vdr-1.7.22/Makefile 2011-12-04 15:41:00.000000000 +0100 +++ vdr-1.7.22-livebuffer/Makefile 2011-12-31 11:16:23.000000000 +0100 @@ -62,6 +62,9 @@ LIBS += $(shell pkg-config --libs fribidi) endif +DEFINES += -DUSE_LIVEBUFFER +OBJS += livebuffer.o + LIRC_DEVICE ?= /var/run/lirc/lircd RCU_DEVICE ?= /dev/ttyS1 diff -Naur vdr-1.7.22/config.c vdr-1.7.22-livebuffer/config.c --- vdr-1.7.22/config.c 2011-12-03 16:21:30.000000000 +0100 +++ vdr-1.7.22-livebuffer/config.c 2011-12-31 11:16:23.000000000 +0100 @@ -461,6 +461,10 @@ InitialChannel = ""; DeviceBondings = ""; InitialVolume = -1; +#ifdef USE_LIVEBUFFER + LiveBufferSize = 30; + LiveBufferMaxFileSize = 100; +#endif /*USE_LIVEBUFFER*/ ChannelsWrap = 0; EmergencyExit = 1; } @@ -655,6 +659,10 @@ else if (!strcasecmp(Name, "InitialChannel")) InitialChannel = Value; else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(Value); else if (!strcasecmp(Name, "DeviceBondings")) DeviceBondings = Value; +#ifdef USE_LIVEBUFFER + else if (!strcasecmp(Name, "LiveBufferSize")) LiveBufferSize = atoi(Value); + else if (!strcasecmp(Name, "LiveBufferMaxFileSize")) LiveBufferMaxFileSize = atoi(Value); +#endif /*USE_LIVEBUFFER*/ else if (!strcasecmp(Name, "ChannelsWrap")) ChannelsWrap = atoi(Value); else if (!strcasecmp(Name, "EmergencyExit")) EmergencyExit = atoi(Value); else @@ -752,6 +760,9 @@ Store("InitialChannel", InitialChannel); Store("InitialVolume", InitialVolume); Store("DeviceBondings", DeviceBondings); +#ifdef USE_LIVEBUFFER + Store("LiveBufferSize", LiveBufferSize); +#endif /* LIVEBUFFER */ Store("ChannelsWrap", ChannelsWrap); Store("EmergencyExit", EmergencyExit); diff -Naur vdr-1.7.22/config.h vdr-1.7.22-livebuffer/config.h --- vdr-1.7.22/config.h 2011-12-03 15:19:52.000000000 +0100 +++ vdr-1.7.22-livebuffer/config.h 2011-12-31 11:16:23.000000000 +0100 @@ -307,6 +307,10 @@ int CurrentVolume; int CurrentDolby; int InitialVolume; +#ifdef USE_LIVEBUFFER + int LiveBufferSize; + int LiveBufferMaxFileSize; +#endif /*USE_LIVEBUFFER*/ int ChannelsWrap; int EmergencyExit; int __EndData__; diff -Naur vdr-1.7.22/device.c vdr-1.7.22-livebuffer/device.c --- vdr-1.7.22/device.c 2011-10-16 16:36:43.000000000 +0200 +++ vdr-1.7.22-livebuffer/device.c 2011-12-31 11:16:23.000000000 +0100 @@ -18,6 +18,10 @@ #include "receiver.h" #include "status.h" #include "transfer.h" +#ifdef USE_LIVEBUFFER +#include "menu.h" +#include "interface.h" +#endif /*USE_LIVEBUFFER*/ // --- cLiveSubtitle --------------------------------------------------------- @@ -663,6 +667,14 @@ return false; case scrNoTransfer: Skins.Message(mtError, tr("Can't start Transfer Mode!")); return false; +#ifdef USE_LIVEBUFFER + case srcStillWritingLiveBuffer: + if(Interface->Confirm(tr("Still writing timeshift data to recording. Abort?"))) + cRecordControls::CancelWritingBuffer(); + else + if(cRecordControls::IsWritingBuffer()) return false; + break; +#endif /*USE_LIVEBUFFER*/ case scrFailed: break; // loop will retry default: esyslog("ERROR: invalid return value from SetChannel"); } @@ -720,8 +732,17 @@ if (NeedsTransferMode) { if (Device && CanReplay()) { +#ifdef USE_LIVEBUFFER + if(LiveView && !cRecordControls::CanSetLiveChannel(Channel)) + return cRecordControls::IsWritingBuffer() ? srcStillWritingLiveBuffer : scrFailed; +#endif /*USE_LIVEBUFFER*/ cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel if (Device->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()! +#ifdef USE_LIVEBUFFER + if(LiveView) + cRecordControls::SetLiveChannel(Device, Channel); + else +#endif /*USE_LIVEBUFFER*/ cControl::Launch(new cTransferControl(Device, Channel)); else Result = scrNoTransfer; diff -Naur vdr-1.7.22/device.h vdr-1.7.22-livebuffer/device.h --- vdr-1.7.22/device.h 2011-12-04 14:38:17.000000000 +0100 +++ vdr-1.7.22-livebuffer/device.h 2011-12-31 11:16:23.000000000 +0100 @@ -32,7 +32,11 @@ #define VOLUMEDELTA 5 // used to increase/decrease the volume #define MAXOCCUPIEDTIMEOUT 99 // max. time (in seconds) a device may be occupied +#ifdef USE_LIVEBUFFER +enum eSetChannelResult { scrOk, scrNotAvailable, scrNoTransfer, scrFailed, srcStillWritingLiveBuffer }; +#else enum eSetChannelResult { scrOk, scrNotAvailable, scrNoTransfer, scrFailed }; +#endif /*USE_LIVEBUFFER*/ enum ePlayMode { pmNone, // audio/video from decoder pmAudioVideo, // audio/video from player diff -Naur vdr-1.7.22/dvbplayer.c vdr-1.7.22-livebuffer/dvbplayer.c --- vdr-1.7.22/dvbplayer.c 2010-03-07 15:24:26.000000000 +0100 +++ vdr-1.7.22-livebuffer/dvbplayer.c 2011-12-31 11:16:23.000000000 +0100 @@ -15,6 +15,9 @@ #include "ringbuffer.h" #include "thread.h" #include "tools.h" +#ifdef USE_LIVEBUFFER +#include "menu.h" +#endif /*USE_LIVEBUFFER*/ // --- cPtsIndex ------------------------------------------------------------- @@ -35,6 +38,9 @@ void Clear(void); void Put(uint32_t Pts, int Index); int FindIndex(uint32_t Pts); +#ifdef USE_LIVEBUFFER + void SetIndex(int Index) {lastFound = Index;}; +#endif /*USE_LIVEBUFFER*/ }; cPtsIndex::cPtsIndex(void) @@ -205,7 +211,12 @@ cRingBufferFrame *ringBuffer; cPtsIndex ptsIndex; cFileName *fileName; +#ifdef USE_LIVEBUFFER + cIndex *index; + cIndexFile *indexFile; +#else cIndexFile *index; +#endif /*USE_LIVEBUFFER*/ cUnbufferedFile *replayFile; double framesPerSecond; bool isPesRecording; @@ -270,18 +281,35 @@ dropFrame = NULL; isyslog("replay %s", FileName); fileName = new cFileName(FileName, false, false, isPesRecording); +#ifndef USE_LIVEBUFFER replayFile = fileName->Open(); if (!replayFile) return; +#endif /*USE_LIVEBUFFER*/ ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE); // Create the index file: +#ifdef USE_LIVEBUFFER + indexFile = NULL; + index = cRecordControls::GetLiveIndex(FileName); + if(!index) + index = indexFile = new cIndexFile(FileName, false, isPesRecording); +#else index = new cIndexFile(FileName, false, isPesRecording); +#endif /*USE_LIVEBUFFER*/ if (!index) esyslog("ERROR: can't allocate index"); else if (!index->Ok()) { delete index; index = NULL; } +#ifdef USE_LIVEBUFFER + readIndex = Resume(); + if (readIndex >= 0) { + ptsIndex.SetIndex(readIndex); + isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); + } else + replayFile = fileName->Open(); +#endif /*USE_LIVEBUFFER*/ } cDvbPlayer::~cDvbPlayer() @@ -289,7 +317,11 @@ Save(); Detach(); delete readFrame; // might not have been stored in the buffer in Action() +#ifdef USE_LIVEBUFFER + delete indexFile; +#else delete index; +#endif /*USE_LIVEBUFFER*/ delete fileName; delete ringBuffer; } @@ -387,9 +419,11 @@ uchar *p = NULL; int pc = 0; +#ifndef USE_LIVEBUFFER readIndex = Resume(); if (readIndex >= 0) isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); +#endif /*USE_LIVEBUFFER*/ nonBlockingFileReader = new cNonBlockingFileReader; int Length = 0; @@ -436,6 +470,10 @@ if (NewIndex <= 0 && readIndex > 0) NewIndex = 1; // make sure the very first frame is delivered NewIndex = index->GetNextIFrame(NewIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length, TimeShiftMode); +#ifdef USE_LIVEBUFFER + if (NewIndex < 0 && TimeShiftMode) // Why should we wait for a timeout if not pdForward + SwitchToPlayFrame = Index; +#endif if (NewIndex < 0 && TimeShiftMode && playDir == pdForward) SwitchToPlayFrame = Index; Index = NewIndex; @@ -454,6 +492,15 @@ off_t FileOffset; if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) readIndex++; +#ifdef USE_LIVEBUFFER + else if(index && index->First() && (readIndex < index->First())) { + int old = readIndex; + readIndex = index->GetNextIFrame(index->First()+1, true, NULL, NULL, NULL, true); + isyslog("Jump before start of livebuffer cortrected %d->%d First %d", old, readIndex, index->First()); + if(readIndex <= index->First()) + eof = true; + } +#endif /*USE_LIVEBUFFER*/ else eof = true; } @@ -587,7 +634,11 @@ else if (Index <= 0 || SwitchToPlayFrame && Index >= SwitchToPlayFrame) SwitchToPlay = true; if (SwitchToPlay) { +#ifdef USE_LIVEBUFFER + if (!SwitchToPlayFrame || (playDir == pdBackward)) +#else if (!SwitchToPlayFrame) +#endif /*USE_LIVEBUFFER*/ Empty(); DevicePlay(); playMode = pmPlay; diff -Naur vdr-1.7.22/livebuffer.c vdr-1.7.22-livebuffer/livebuffer.c --- vdr-1.7.22/livebuffer.c 1970-01-01 01:00:00.000000000 +0100 +++ vdr-1.7.22-livebuffer/livebuffer.c 2011-12-31 11:16:23.000000000 +0100 @@ -0,0 +1,403 @@ +#ifdef USE_LIVEBUFFER +#include "livebuffer.h" +#if VDRVERSNUM >= 10716 + +#include +#include "videodir.h" +#include "recording.h" +#include "skins.h" +#include "player.h" + +#define WAIT_WRITING_COUNT 1000 +#define WAIT_WRITING_SLEEP 10000 + +#define WAIT_TERMINATE_COUNT 300 +#define WAIT_TERMINATE_SLEEP 10000 + +struct tLiveIndex { + int index; + uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!) + int reserved:7; // reserved for future use + int independent:1; // marks frames that can be displayed by themselves (for trick modes) + uint16_t number:16; // up to 64K files per recording + tLiveIndex(int Index, bool Independent, uint16_t Number, off_t Offset) + { + index = Index; + offset = Offset; + reserved = 0; + independent = Independent; + number = Number; + } +}; // tLiveIndex + +class cLiveIndex : public cIndex { +public: + cLiveIndex(const char *FileName): bufferFileName(FileName, false), bufferBaseName(FileName) { + resumePos = -1; + lastPos = lastGet = lastBuf = 0; + lastFileNumber=1; + dropFile = false; + maxSize = Setup.LiveBufferSize * 60 * DEFAULTFRAMESPERSECOND; + idx.reserve(maxSize+1); + }; // cLiveIndex + virtual ~cLiveIndex() { + }; // ~cLiveIndex + virtual bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset) { + cMutexLock lock(&idx_lock); + idx.push_back(tLiveIndex(++lastPos, Independent, FileNumber, FileOffset)); + while(((idx.size() > maxSize) && (lastGet ? (lastGet > First()) : true) && (lastBuf ? (lastBuf > First()) : true)) || dropFile) { + if(idx.front().number != lastFileNumber) { + isyslog("Deleting old livebuffer file #%d (%d)", lastFileNumber, dropFile); + system(cString::sprintf("ls -l %s/%05d.ts | grep -- '->' | sed -e's/.*-> //' | xargs rm -rf", (const char *)bufferBaseName, lastFileNumber)); // for symlink video.xx + unlink(cString::sprintf("%s/%05d.ts", (const char *)bufferBaseName, lastFileNumber)); + lastFileNumber = idx.front().number; + dropFile=false; + } // if + idx.erase(idx.begin()); + } // if + return true; + }; // Write + virtual bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL) { + cMutexLock lock(&idx_lock); + std::vector::iterator item = GetIndex(Index); + if(item == idx.end()) return false; + *FileNumber = item->number; + *FileOffset = item->offset; + if (Independent) + *Independent = item->independent; + item++; + if(item == idx.end()) return false; + if (Length) { + uint16_t fn = item->number; + off_t fo = item->offset; + if (fn == *FileNumber) + *Length = int(fo - *FileOffset); + else + *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly) + } // if + lastGet = Index; + return true; + }; // Get + virtual int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false) { + cMutexLock lock(&idx_lock); + std::vector::iterator item = GetIndex(Index); + if(item == idx.end()) { + if(Index < First() && Forward) + item = idx.begin(); + else + return -1; + } + if(Forward) { + do { + item++; + if(item == idx.end()) return -1; + } while(!item->independent); + } else { + do { + if(item == idx.begin()) return -1; + item--; + } while(!item->independent); + } // if + uint16_t fn; + if (!FileNumber) + FileNumber = &fn; + off_t fo; + if (!FileOffset) + FileOffset = &fo; + *FileNumber = item->number; + *FileOffset = item->offset; + item++; + if(item == idx.end()) return -1; + if (Length) { + // all recordings end with a non-independent frame, so the following should be safe: + uint16_t fn = item->number; + off_t fo = item->offset; + if (fn == *FileNumber) { + *Length = int(fo - *FileOffset); + } else { + esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber); + *Length = -1; + } // if + } // if + return Index; + }; // GetNextIFrame + virtual bool SetBufferStart(int Frames) { + cMutexLock lock(&idx_lock); + abortBuf = false; + if(Frames <= 0) { + lastBuf = 0; + return false; + } // if + lastBuf = Last()-Frames; + if(lastBuf < First()) + lastBuf = First(); + lastBuf = GetNextIFrame(lastBuf, true); + return true; + } // SetBufferStart + virtual cUnbufferedFile *GetNextBuffer(int &Length, bool &Independent) { + if(abortBuf || !lastBuf) return NULL; + cMutexLock lock(&idx_lock); + std::vector::iterator buff = GetIndex(lastBuf); + if((buff == idx.end()) || ((buff+1) == idx.end())) return NULL; + off_t offset = buff->offset; + int number = buff->number; + cUnbufferedFile *ret = bufferFileName.SetOffset(number, offset); + Independent = buff->independent; + buff++; + lastBuf = buff->index; + if(number != buff->number) + Length = -1; + else + Length = buff->offset-offset; + return ret; + } // GetNextBuffer + virtual int Get(uint16_t FileNumber, off_t FileOffset) { + for ( std::vector::iterator item = idx.begin(); item != idx.end(); item++) + if (item->number > FileNumber || ((item->number == FileNumber) && off_t(item->offset) >= FileOffset)) + return item->index; + return lastPos; + }; // Get + virtual bool Ok(void) {return true;}; + virtual int First(void) {return idx.size() ? idx.front().index : -1;}; + virtual int Last(void) {return idx.size() ? idx.back().index : -1;}; + virtual void SetResume(int Index) {resumePos = lastGet = Index;}; + virtual int GetResume(void) {return resumePos;}; + virtual bool StoreResume(int Index) {resumePos=Index; lastGet=0; return true;}; + virtual bool IsStillRecording(void) {return true;}; + virtual void Delete(void) {}; + virtual void DropFile(void) {dropFile=true;}; + virtual bool IsWritingBuffer(void) {return lastBuf != 0;}; + virtual void CancelWritingBuffer(void) {abortBuf = true;}; + virtual bool WritingBufferCanceled(void) {return abortBuf;}; +protected: + int firstPos; + int lastPos; + int resumePos; + int lastFileNumber; + int lastGet; + int lastBuf; + bool abortBuf; + bool dropFile; + unsigned int maxSize; + cFileName bufferFileName; + cString bufferBaseName; + cMutex idx_lock; + std::vector idx; + virtual std::vector::iterator GetIndex(int Index) { + if(!idx.size()) return idx.end(); + std::vector::iterator item = idx.begin(); + + unsigned int guess = Index-First(); // Try to guess the position + if(guess > 0) { + if(guess < idx.size()) + item += guess; + else + item = idx.end()-1; + } // if + while(item->index < Index) { + item++; + if(item == idx.end()) + return idx.end(); + } // while + while(item->index > Index) { + if(item == idx.begin()) + return idx.end(); + item--; + } // while + if(item->index != Index) + return idx.end(); + return item; + }; // GetIndex +}; // cLiveIndex + +/*****************************************************************************/ + +cString cLiveRecorder::liveFileName; + +cLiveRecorder::cLiveRecorder(const cChannel *Channel):cRecorder(FileName(), Channel, -1) + ,broken(false) { + handleError = false; + if(index) delete index; + index = new cLiveIndex(FileName()); + Activate(true); +}; // cLiveRecorder::cLiveRecorder + +cLiveRecorder::~cLiveRecorder() { + int maxWait = WAIT_TERMINATE_COUNT; + CancelWritingBuffer(); + while(IsWritingBuffer() && maxWait--) + usleep(WAIT_TERMINATE_SLEEP); + Activate(false); + Cleanup(); +}; // cLiveRecorder::~cLiveRecorder + +bool cLiveRecorder::IsWritingBuffer() { + return index && ((cLiveIndex *)index)->IsWritingBuffer(); +} // cLiveRecorder::IsWritingBuffer + +void cLiveRecorder::CancelWritingBuffer() { + if(index) ((cLiveIndex *)index)->CancelWritingBuffer(); +} // cLiveRecorder::CancelWritingBuffer + +bool cLiveRecorder::NextFile(void) { + if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame + if(RunningLowOnDiskSpace() && index) + ((cLiveIndex *)index)->DropFile(); + if (fileSize > MEGABYTE(off_t(Setup.LiveBufferMaxFileSize)) || RunningLowOnDiskSpace()) { + recordFile = fileName->NextFile(); + fileSize = 0; + } // if + } // if + return recordFile != NULL; +} // cLiveRecorder::NextFile + +int cLiveRecorder::LastIFrame() { + if(!index) return 0; + int ret = index->GetNextIFrame(index->Last()-1, false); + return (ret > 0) ? ret : 0; +}; // cLiveRecorder::LastIFrame + +int cLiveRecorder::LastFrame() { + return index ? index->Last() : 0; +}; // cLiveRecorder::LastFrame + +void cLiveRecorder::SetResume(int Index) { + if(index) ((cLiveIndex *)index)->SetResume(Index); +}; // cLiveRecorder::SetResume + +bool cLiveRecorder::SetBufferStart(time_t Start) { + if(!index) return false; + if(time(NULL) <= Start) return false; + int Frames = SecondsToFrames(time(NULL)-Start, frameDetector ? frameDetector->FramesPerSecond() : DEFAULTFRAMESPERSECOND); //test stop livebuffer + return ((cLiveIndex *)index)->SetBufferStart(Frames); +} // cLiveRecorder::SetBufferStart + +cIndex *cLiveRecorder::GetIndex() { + return index; +}; // cLiveRecorder::GetIndex + +bool cLiveRecorder::Cleanup() { + if(FileName()) + if(-1 == system(cString::sprintf("ls -l %s/* | grep -- '->' | sed -e's/.*-> //' | xargs rm -rf", FileName()))) // for symlink video.xx + return false; + else + if(-1 == system(cString::sprintf("rm -rf %s/*", FileName()))) + return false; + return true; +}; // cLiveRecorder::Cleanup + +bool cLiveRecorder::Prepare() { + if (!MakeDirs(FileName(), true)) return false; + return Cleanup(); +}; // cLiveRecorder::Prepare + +const char *cLiveRecorder::FileName() { + if(!(const char *)liveFileName && BufferDirectory) + liveFileName = cString::sprintf("%s/LiveBuffer", BufferDirectory); + return liveFileName; +}; // cLiveRecorder::FileName + +void cLiveRecorder::Activate(bool On) { + cRecorder::Activate(On); + if(!On) broken=true; +} // cLiveRecorder::Activate + +void cLiveRecorder::Receive(uchar *Data, int Length) { + if(broken) { + isyslog("Continue live recorder on broken stream (maybe due to switching to same channel on other device)"); + TsSetTeiOnBrokenPackets(Data, Length); + broken = false; + } // if + cRecorder::Receive(Data, Length); +} // cLiveRecorder::Receive + +/*****************************************************************************/ + +cBufferRecorder::cBufferRecorder(const char *FileName, const cChannel *Channel, int Priority, cIndex *LiveBufferIndex) + :cRecorder(FileName, Channel, Priority) + ,liveBufferIndex(LiveBufferIndex) + ,dropData(false) { + if(liveBufferIndex) dropData=true; // Drop new data till we have written most of the live buffer data +} // cBufferRecorder::cBufferRecorder + +cBufferRecorder::~cBufferRecorder() { + if(liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); +} // cBufferRecorder::~cBufferRecorder + +void cBufferRecorder::Action(void) { + if(liveBufferIndex) + FillInitialData(NULL, 0); + dropData=false; + cRecorder::Action(); + if(liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; +} // cBufferRecorder::Action + +void cBufferRecorder::Activate(bool On) { + if(!On && liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + cRecorder::Activate(On); +} // cBufferRecorder::Activate + +void cBufferRecorder::Receive(uchar *Data, int Length) { + if(!dropData) cRecorder::Receive(Data, Length); +} // cBufferRecorder::Receive + +void cBufferRecorder::FillInitialData(uchar *Data, int Size) { + if(liveBufferIndex) { + int64_t search_pts = Data ? TsGetPts(Data, Size) : -1; + int maxWait = WAIT_WRITING_COUNT; + uchar buffer[MAXFRAMESIZE]; + int Length; + bool Independent; + bool found = false; + while(!Data || (Size >= TS_SIZE)) { + cUnbufferedFile *file = ((cLiveIndex *)liveBufferIndex)->GetNextBuffer(Length, Independent); + if(!file) { + if(((cLiveIndex *)liveBufferIndex)->WritingBufferCanceled()) { + isyslog("Writing buffer canceled by user"); + if(fileSize) TsSetTeiOnBrokenPackets(Data, Size); + ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; + return; + } // if + if(!Data || !Size) return; + if(!maxWait--) + break; + usleep(WAIT_WRITING_SLEEP); + continue; + } // if + if (!NextFile()) + break; + int len = ReadFrame(file, buffer, Length, sizeof(buffer)); + if(len < TS_SIZE) { + isyslog("Failed to read live buffer data"); + break; + } // if + if(Data && Independent && (search_pts == TsGetPts(buffer, len))) { + found = true; + break; + } // if + if (index) + index->Write(Independent, fileName->Number(), fileSize); + if (recordFile->Write(buffer, len) < 0) { + isyslog("Failed to write live buffer data"); + break; + } // if + fileSize += len; + } // while + if(Data) { + isyslog("%lld bytes from live buffer %swritten to recording", fileSize, found ? "seamless ": ""); + if(!found && fileSize) TsSetTeiOnBrokenPackets(Data, Size); + ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; + } else if(((cLiveIndex *)liveBufferIndex)->WritingBufferCanceled()) { + isyslog("%lld bytes from live buffer written to recording (aborted)", fileSize); + ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; + } // if + } else if (Data && fileSize) + TsSetTeiOnBrokenPackets(Data, Size); +} // cBufferRecorder::FillInitialData + +#endif /*VDRVERSNUM*/ +#endif /*USE_LIVEBUFFER*/ diff -Naur vdr-1.7.22/livebuffer.h vdr-1.7.22-livebuffer/livebuffer.h --- vdr-1.7.22/livebuffer.h 1970-01-01 01:00:00.000000000 +0100 +++ vdr-1.7.22-livebuffer/livebuffer.h 2011-12-31 11:16:23.000000000 +0100 @@ -0,0 +1,47 @@ +#ifndef LIVEBUFFER_H +#define LIVEBUFFER_H + +#ifdef USE_LIVEBUFFER +#include "config.h" +#if VDRVERSNUM >= 10716 + +#include "recorder.h" + +class cLiveRecorder : public cRecorder { +public: + cLiveRecorder(const cChannel *Channel); + virtual bool NextFile(void); + virtual ~cLiveRecorder(); + virtual bool IsWritingBuffer(); + virtual void CancelWritingBuffer(); + virtual int LastIFrame(); + virtual int LastFrame(); + virtual void SetResume(int Index); + virtual bool SetBufferStart(time_t Start); + virtual cIndex *GetIndex(); + static bool Cleanup(); + static bool Prepare(); + static const char *FileName(); +protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + bool broken; + static cString liveFileName; +}; // cLiveRecorder + +class cBufferRecorder : public cRecorder { +public: + cBufferRecorder(const char *FileName, const cChannel *Channel, int Priority, cIndex *LiveBufferIndex); + virtual ~cBufferRecorder(); + virtual void FillInitialData(uchar *Data, int Size); +protected: + virtual void Action(void); + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + cIndex *liveBufferIndex; + bool dropData; +}; // cBufferRecorder + +#endif /*VDRVERSNUM*/ +#endif /*USE_LIVEBUFFER*/ +#endif /*LIVEBUFFER_H*/ diff -Naur vdr-1.7.22/menu.c vdr-1.7.22-livebuffer/menu.c --- vdr-1.7.22/menu.c 2011-12-04 15:52:38.000000000 +0100 +++ vdr-1.7.22-livebuffer/menu.c 2011-12-31 11:16:23.000000000 +0100 @@ -3066,7 +3066,11 @@ class cMenuSetupRecord : public cMenuSetupBase { private: - const char *pauseKeyHandlingTexts[3]; +#ifdef USE_LIVEBUFFER + const char *pauseKeyHandlingTexts[4]; +#else + const char *pauseKeyHandlingTexts[3]; +#endif /*USE_LIVEBUFFER*/ const char *delTimeshiftRecTexts[3]; public: cMenuSetupRecord(void); @@ -3077,6 +3081,9 @@ pauseKeyHandlingTexts[0] = tr("do not pause live video"); pauseKeyHandlingTexts[1] = tr("confirm pause live video"); pauseKeyHandlingTexts[2] = tr("pause live video"); +#ifdef USE_LIVEBUFFER + pauseKeyHandlingTexts[3] = tr("Timeshift"); +#endif /*USE_LIVEBUFFER*/ delTimeshiftRecTexts[0] = tr("no"); delTimeshiftRecTexts[1] = tr("confirm"); delTimeshiftRecTexts[2] = tr("yes"); @@ -3086,7 +3093,12 @@ Add(new cMenuEditIntItem( tr("Setup.Recording$Primary limit"), &data.PrimaryLimit, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"), &data.DefaultPriority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"), &data.DefaultLifetime, 0, MAXLIFETIME)); - Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 3, pauseKeyHandlingTexts)); +#ifdef USE_LIVEBUFFER + Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 4, pauseKeyHandlingTexts)); + Add(new cMenuEditIntItem( tr("Timeshift size (min)"), &data.LiveBufferSize, 1, 300)); // TODO fix name and min/max values +#else + Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 3, pauseKeyHandlingTexts)); +#endif /*USE_LIVEBUFFER*/ Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"), &data.PausePriority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"), &data.PauseLifetime, 0, MAXLIFETIME)); Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); @@ -4157,7 +4169,11 @@ isyslog("record %s", fileName); if (MakeDirs(fileName, true)) { const cChannel *ch = timer->Channel(); +#ifdef USE_LIVEBUFFER + recorder = new cBufferRecorder(fileName, ch, timer->Priority(), cRecordControls::GetLiveBuffer(timer)); +#else recorder = new cRecorder(fileName, ch, timer->Priority()); +#endif if (device->AttachReceiver(recorder)) { Recording.WriteInfo(); cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); @@ -4242,6 +4258,10 @@ cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; +#ifdef USE_LIVEBUFFER +cLiveRecorder *cRecordControls::liveRecorder = NULL; +#endif /*USE_LIVEBUFFER*/ + bool cRecordControls::Start(cTimer *Timer, bool Pause) { static time_t LastNoDiskSpaceMessage = 0; @@ -4313,8 +4333,31 @@ } } + +#ifdef USE_LIVEBUFFER +bool cRecordControls::StartLiveBuffer(eKeys Key) { + if(Setup.PauseKeyHandling == 3 && liveRecorder) { + int pos = liveRecorder->LastIFrame(); + isyslog("Enter timeshift at %d / %d", pos, liveRecorder->LastFrame()); + liveRecorder->SetResume(pos?pos:liveRecorder->LastFrame()); + cReplayControl::SetRecording(cLiveRecorder::FileName(), tr("Timeshift mode")); + cReplayControl *rc = new cReplayControl; + cControl::Launch(rc); + cControl::Attach(); + rc->ProcessKey(Key); + rc->Show(); // show progressbar at the start of livebuffer + return true; + } // if + return false; +} // cRecordControls::StartLiveBuffer +#endif /*USE_LIVEBUFFER*/ + bool cRecordControls::PauseLiveVideo(void) { +#ifdef USE_LIVEBUFFER + if(StartLiveBuffer(kPause)) + return true; +#endif /*USE_LIVEBUFFER*/ Skins.Message(mtStatus, tr("Pausing live video...")); cReplayControl::SetRecording(NULL, NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed() if (Start(NULL, true)) { @@ -4331,6 +4374,54 @@ return false; } +#ifdef USE_LIVEBUFFER +void cRecordControls::SetLiveChannel(cDevice *Device, const cChannel *Channel) { + if(liveRecorder) { + if(Channel && Device && (liveRecorder->ChannelID()==Channel->GetChannelID())) + Device->AttachReceiver(liveRecorder); + else + DELETENULL(liveRecorder); + } // if + if(Device && Channel) cControl::Launch(new cTransferControl(Device, Channel)); + if(Setup.PauseKeyHandling == 3 && Channel && Device && !liveRecorder) { + if (cLiveRecorder::Prepare()) { + liveRecorder = new cLiveRecorder(Channel); + if(!Device->AttachReceiver(liveRecorder)) + DELETENULL(liveRecorder); + } // if + } // if +} // cRecordControls::SetLiveChannel + +bool cRecordControls::CanSetLiveChannel(const cChannel *Channel) { + if(liveRecorder && Channel && (liveRecorder->ChannelID()==Channel->GetChannelID())) return true; + return !IsWritingBuffer(); +} // cRecordControls::CanSetLiveChannel + +bool cRecordControls::IsWritingBuffer() { + return liveRecorder ? liveRecorder->IsWritingBuffer() : false; +} // cRecordControls::IsWritingBuffer + +void cRecordControls::CancelWritingBuffer() { + if(liveRecorder && liveRecorder->IsWritingBuffer()) { + liveRecorder->CancelWritingBuffer(); + sleep(1); // allow recorder to really stop + } // if +} // cRecordControls::CancelWritingBuffer + +cIndex *cRecordControls::GetLiveBuffer(cTimer *Timer) { + if(!liveRecorder || !Timer || !Timer->Channel()) return NULL; + if(!(liveRecorder->ChannelID() == Timer->Channel()->GetChannelID())) return NULL; + if(!liveRecorder->SetBufferStart(Timer->StartTime())) return NULL; + return liveRecorder->GetIndex(); +} // cRecordControls::GetLiveBuffer + +cIndex *cRecordControls::GetLiveIndex(const char *FileName) { + if(!FileName || strcmp(cLiveRecorder::FileName(), FileName)) return NULL; + return liveRecorder ? liveRecorder->GetIndex() : NULL; +} // cRecordControls::GetLiveIndex + +#endif /* USE_LIVEBUFFER */ + const char *cRecordControls::GetInstantId(const char *LastInstantId) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { @@ -4529,21 +4620,30 @@ void cReplayControl::ShowMode(void) { - if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) { + if (visible || (Setup.ShowReplayMode && !cOsd::IsOpen())) { bool Play, Forward; int Speed; if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) { bool NormalPlay = (Play && Speed == -1); + bool Paused = (!Play && Speed == -1); if (!visible) { if (NormalPlay) return; // no need to do indicate ">" unless there was a different mode displayed before visible = modeOnly = true; + + // if newly paused show full replay osd; ie modeOnly = false + if (Paused) { + modeOnly = (lastPlay == Play); + } + displayReplay = Skins.Current()->DisplayReplay(modeOnly); } - if (modeOnly && !timeoutShow && NormalPlay) + // osd times out when replaying normally OR when paused and full osd is shown + if (!timeoutShow && (NormalPlay|| (!modeOnly && Paused))) timeoutShow = time(NULL) + MODETIMEOUT; + displayReplay->SetMode(Play, Forward, Speed); lastPlay = Play; lastForward = Forward; @@ -4557,6 +4657,45 @@ int Current, Total; if (GetIndex(Current, Total) && Total > 0) { +#ifdef USE_LIVEBUFFER + int first=0; + cIndex *idx = cRecordControls::GetLiveIndex(fileName); + if(idx) first = idx->First(); // Normalize displayed values + Current -= first; + if(Current < 0) Current = 0; + Total -= first; + if(Total < 0) Total = 0; + time_t now = time(NULL); + static time_t last_sched_check = 0; + if(displayReplay && idx && (last_sched_check != now)) { + last_sched_check = now; // Only check every second + cSchedulesLock SchedulesLock; + const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); + if (Schedules) { + const char *display_title = NULL;// = title; + const cSchedule *Schedule = Schedules->GetSchedule(Channels.GetByNumber(cDevice::CurrentChannel())); + if (Schedule) { + time_t Time = now - round(((double)Total - Current) / FramesPerSecond()); + const cEvent *event = Schedule->GetEventAround(Time); + if (event) display_title = event->Title(); + } // if + + // no event title; show channel name + if (!display_title) { + cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); + display_title = channel->Name(); + } + + // set title as "Timeshift mode: " + // OR "Timeshift mode: " + // if neither is possible leave title as such + if (display_title) + displayReplay->SetTitle(cString::sprintf("%s: %s", + tr("Timeshift mode"), + display_title)); + } // if + } // if +#endif /*USE_LIVEBUFFER*/ if (!visible) { displayReplay = Skins.Current()->DisplayReplay(modeOnly); displayReplay->SetMarks(&marks); @@ -4658,6 +4797,9 @@ void cReplayControl::TimeSearch(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ timeSearchTime = timeSearchPos = 0; timeSearchHide = false; if (modeOnly) @@ -4676,6 +4818,9 @@ void cReplayControl::MarkToggle(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ int Current, Total; if (GetIndex(Current, Total, true)) { cMark *m = marks.Get(Current); @@ -4696,6 +4841,9 @@ void cReplayControl::MarkJump(bool Forward) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ if (marks.Count()) { int Current, Total; if (GetIndex(Current, Total)) { @@ -4710,6 +4858,9 @@ void cReplayControl::MarkMove(bool Forward) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ int Current, Total; if (GetIndex(Current, Total)) { cMark *m = marks.Get(Current); @@ -4734,6 +4885,9 @@ void cReplayControl::EditCut(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ if (fileName) { Hide(); if (!cCutter::Active()) { @@ -4752,6 +4906,9 @@ void cReplayControl::EditTest(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ int Current, Total; if (GetIndex(Current, Total)) { cMark *m = marks.Get(Current); @@ -4783,7 +4940,14 @@ if (Key == kNone) marks.Update(); if (visible) { + + if (Key != kNone /*&& !modeOnly*/ && timeoutShow) { + printf("timeout reset +%d\n", MODETIMEOUT); + timeoutShow = time(NULL) + MODETIMEOUT; + } + if (timeoutShow && time(NULL) > timeoutShow) { + printf("timed out \n"); Hide(); ShowMode(); timeoutShow = 0; @@ -4800,12 +4964,34 @@ return osContinue; } bool DoShowMode = true; + +#ifdef USE_LIVEBUFFER + if (cRecordControls::GetLiveIndex(fileName) && (Key >= k0) && (Key <= k9)) + return osSwitchChannel; +#endif /*USE_LIVEBUFFER*/ switch (int(Key)) { // Positioning: +#ifdef USE_LIVEBUFFER + case kUp: if(cRecordControls::GetLiveIndex(fileName)) { + cDevice::SwitchChannel(1); + return osEnd; + } // if + // NO break + case kPlay: + Play(); break; + case kDown: if(cRecordControls::GetLiveIndex(fileName)) { + cDevice::SwitchChannel(-1); + return osEnd; + } // if + // NO break + case kPause: Pause(); + break; +#else case kPlay: case kUp: Play(); break; case kPause: case kDown: Pause(); break; +#endif /*USE_LIVEBUFFER*/ case kFastRew|k_Release: case kLeft|k_Release: if (Setup.MultiSpeedMode) break; @@ -4816,15 +5002,52 @@ if (Setup.MultiSpeedMode) break; case kFastFwd: case kRight: Forward(); break; - case kRed: TimeSearch(); break; + //case kRed: TimeSearch(); break; case kGreen|k_Repeat: case kGreen: SkipSeconds(-60); break; case kYellow|k_Repeat: case kYellow: SkipSeconds( 60); break; +#ifdef USE_LIVEBUFFER + case kRed: if(cRecordControls::GetLiveIndex(fileName)) { + + if (!(visible && !modeOnly)) return osUnknown; + else {} // fall through to case kRecord + // since Timeshift ON and replay OSD is shown + } // if + else { //timeshift off + TimeSearch(); + break; + } // else + // No break + case kRecord: if(cRecordControls::GetLiveIndex(fileName)) { + int frames = 0; + int Current, Total; + if(GetIndex(Current, Total)) + frames = Total-Current; + cTimer *timer = new cTimer(true, false, Channels.GetByNumber(cDevice::CurrentChannel())); + Timers.Add(timer); + Timers.SetModified(); + if (cRecordControls::Start(timer)) + Skins.Message(mtInfo, tr("Recording started")); + else + Timers.Del(timer); + } // if + break; + case kBlue: if(cRecordControls::GetLiveIndex(fileName)) + if(!(visible && !modeOnly)) + return osUnknown; + //NO break + case kStop: Hide(); + Stop(); + return osEnd; +#else + case kRed: TimeSearch(); break; case kStop: case kBlue: Hide(); Stop(); return osEnd; +#endif /*USE_LIVEBUFFER*/ + default: { DoShowMode = false; switch (int(Key)) { @@ -4855,7 +5078,20 @@ else Show(); break; - case kBack: if (Setup.DelTimeshiftRec) { + case kBack: +#ifdef USE_LIVEBUFFER + if (visible && !modeOnly) { + Hide(); + DoShowMode = true; + break; + } + if(cRecordControls::GetLiveIndex(fileName)) { + Hide(); + Stop(); + return osEnd; + } // if +#endif /*USE_LIVEBUFFER*/ + if (Setup.DelTimeshiftRec) { cRecordControl* rc = cRecordControls::GetRecordControl(fileName); return rc && rc->InstantId() ? osEnd : osRecordings; } diff -Naur vdr-1.7.22/menu.h vdr-1.7.22-livebuffer/menu.h --- vdr-1.7.22/menu.h 2010-03-06 17:15:59.000000000 +0100 +++ vdr-1.7.22-livebuffer/menu.h 2011-12-31 11:16:23.000000000 +0100 @@ -18,6 +18,12 @@ #include "menuitems.h" #include "recorder.h" #include "skins.h" +#ifdef USE_LIVEBUFFER +#include "livebuffer.h" +#endif /*USE_LIVEBUFFER*/ + + + class cMenuText : public cOsdMenu { private: @@ -236,10 +242,18 @@ private: static cRecordControl *RecordControls[]; static int state; +#ifdef USE_LIVEBUFFER +protected: + friend class cRecordControl; + static cLiveRecorder *liveRecorder; +#endif /*USE_LIVEBUFFER*/ public: static bool Start(cTimer *Timer = NULL, bool Pause = false); static void Stop(const char *InstantId); static bool PauseLiveVideo(void); +#ifdef USE_LIVEBUFFER + static bool StartLiveBuffer(eKeys Key); +#endif /*USE_LIVEBUFFER*/ static const char *GetInstantId(const char *LastInstantId); static cRecordControl *GetRecordControl(const char *FileName); static void Process(time_t t); @@ -248,6 +262,14 @@ static void Shutdown(void); static void ChangeState(void) { state++; } static bool StateChanged(int &State); +#ifdef USE_LIVEBUFFER + static void SetLiveChannel(cDevice *Device, const cChannel *Channel); + static bool CanSetLiveChannel(const cChannel *Channel); + static bool IsWritingBuffer(); + static void CancelWritingBuffer(); + static cIndex *GetLiveBuffer(cTimer *Timer); + static cIndex *GetLiveIndex(const char *FileName); +#endif /*USE_LIVEBUFFER*/ }; class cReplayControl : public cDvbPlayerControl { diff -Naur vdr-1.7.22/osdbase.h vdr-1.7.22-livebuffer/osdbase.h --- vdr-1.7.22/osdbase.h 2010-01-16 15:25:31.000000000 +0100 +++ vdr-1.7.22-livebuffer/osdbase.h 2011-12-31 11:16:23.000000000 +0100 @@ -33,6 +33,9 @@ osSwitchDvb, osBack, osEnd, +#ifdef USE_LIVEBUFFER + osSwitchChannel, +#endif /*USE_LIVEBUFFER*/ os_User, // the following values can be used locally osUser1, osUser2, diff -Naur vdr-1.7.22/player.c vdr-1.7.22-livebuffer/player.c --- vdr-1.7.22/player.c 2007-07-20 17:25:24.000000000 +0200 +++ vdr-1.7.22-livebuffer/player.c 2011-12-31 11:16:23.000000000 +0100 @@ -10,6 +10,11 @@ #include "player.h" #include "i18n.h" +#ifdef USE_LIVEBUFFER +#include "menu.h" +#include "transfer.h" +#endif /*USE_LIVEBUFFER*/ + // --- cPlayer --------------------------------------------------------------- cPlayer::cPlayer(ePlayMode PlayMode) @@ -68,6 +73,12 @@ void cControl::Launch(cControl *Control) { +#ifdef USE_LIVEBUFFER + if(!dynamic_cast(Control)) { + if(!dynamic_cast(Control) || strcmp(cLiveRecorder::FileName(), cReplayControl::NowReplaying())) + cRecordControls::SetLiveChannel(NULL, NULL); + } // if +#endif /*USE_LIVEBUFFER*/ cMutexLock MutexLock(&mutex); cControl *c = control; // keeps control from pointing to uninitialized memory control = Control; diff -Naur vdr-1.7.22/po/de_DE.po vdr-1.7.22-livebuffer/po/de_DE.po --- vdr-1.7.22/po/de_DE.po 2011-12-03 16:35:34.000000000 +0100 +++ vdr-1.7.22-livebuffer/po/de_DE.po 2011-12-31 11:16:23.000000000 +0100 @@ -25,6 +25,9 @@ msgid "Can't start Transfer Mode!" msgstr "Transfer-Mode kann nicht gestartet werden!" +msgid "Still writing timeshift data to recording. Abort?" +msgstr "Timeshift-Daten werden noch in Aufnahme kopiert. Abbrechen?" + msgid "off" msgstr "aus" diff -Naur vdr-1.7.22/recorder.c vdr-1.7.22-livebuffer/recorder.c --- vdr-1.7.22/recorder.c 2011-09-04 11:26:44.000000000 +0200 +++ vdr-1.7.22-livebuffer/recorder.c 2011-12-31 11:16:23.000000000 +0100 @@ -24,6 +24,9 @@ cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority) :cReceiver(Channel, Priority) ,cThread("recording") +#ifdef USE_LIVEBUFFER +,handleError(true) +#endif /*USE_LIVEBUFFER*/ { recordingName = strdup(FileName); @@ -140,6 +143,9 @@ InfoWritten = true; } if (FirstIframeSeen || frameDetector->IndependentFrame()) { +#ifdef USE_LIVEBUFFER + if(!FirstIframeSeen) FillInitialData(b, r); +#endif /*USE_LIVEBUFFER*/ FirstIframeSeen = true; // start recording with the first I-frame if (!NextFile()) break; @@ -165,7 +171,11 @@ ringBuffer->Del(Count); } } +#ifdef USE_LIVEBUFFER + if (handleError && (time(NULL) - t > MAXBROKENTIMEOUT)) { +#else if (time(NULL) - t > MAXBROKENTIMEOUT) { +#endif esyslog("ERROR: video data stream broken"); ShutdownHandler.RequestEmergencyExit(); t = time(NULL); diff -Naur vdr-1.7.22/recorder.h vdr-1.7.22-livebuffer/recorder.h --- vdr-1.7.22/recorder.h 2010-12-27 12:17:04.000000000 +0100 +++ vdr-1.7.22-livebuffer/recorder.h 2011-12-31 11:16:23.000000000 +0100 @@ -17,18 +17,33 @@ #include "thread.h" class cRecorder : public cReceiver, cThread { +#ifdef USE_LIVEBUFFER +protected: +#else private: +#endif /*USE_LIVEBUFFER*/ cRingBufferLinear *ringBuffer; cFrameDetector *frameDetector; cPatPmtGenerator patPmtGenerator; cFileName *fileName; +#ifdef USE_LIVEBUFFER + cIndex *index; + bool handleError; +#else cIndexFile *index; +#endif /*USE_LIVEBUFFER*/ cUnbufferedFile *recordFile; char *recordingName; off_t fileSize; time_t lastDiskSpaceCheck; +#ifdef USE_LIVEBUFFER + virtual bool RunningLowOnDiskSpace(void); + virtual bool NextFile(void); + virtual void FillInitialData(uchar *Data, int Size) {}; +#else bool RunningLowOnDiskSpace(void); bool NextFile(void); +#endif /*USE_LIVEBUFFER*/ protected: virtual void Activate(bool On); virtual void Receive(uchar *Data, int Length); diff -Naur vdr-1.7.22/recording.h vdr-1.7.22-livebuffer/recording.h --- vdr-1.7.22/recording.h 2011-12-04 14:38:17.000000000 +0100 +++ vdr-1.7.22-livebuffer/recording.h 2011-12-31 11:16:23.000000000 +0100 @@ -264,7 +264,26 @@ struct tIndexTs; class cIndexFileGenerator; +#ifdef USE_LIVEBUFFER +class cIndex { +public: + virtual bool Ok(void) =0; + virtual bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset) =0; + virtual bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL) =0; + virtual int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false) =0; + virtual int Get(uint16_t FileNumber, off_t FileOffset) =0; + virtual int First(void) {return 0;}; + virtual int Last(void) =0; + virtual int GetResume(void) =0; + virtual bool StoreResume(int Index) =0; + virtual bool IsStillRecording(void) =0; + virtual void Delete(void) =0; + }; + +class cIndexFile : public cIndex { +#else class cIndexFile { +#endif /*USE_LIVEBUFFER*/ private: int f; cString fileName; diff -Naur vdr-1.7.22/vdr.c vdr-1.7.22-livebuffer/vdr.c --- vdr-1.7.22/vdr.c 2011-12-03 16:35:09.000000000 +0100 +++ vdr-1.7.22-livebuffer/vdr.c 2011-12-31 11:16:23.000000000 +0100 @@ -218,6 +218,9 @@ static struct option long_options[] = { { "audio", required_argument, NULL, 'a' }, +#ifdef USE_LIVEBUFFER + { "buffer", required_argument, NULL, 'b' }, +#endif /* USE_LIVEBUFFER */ { "config", required_argument, NULL, 'c' }, { "daemon", no_argument, NULL, 'd' }, { "device", required_argument, NULL, 'D' }, @@ -251,10 +254,20 @@ }; int c; +#ifdef USE_LIVEBUFFER + while ((c = getopt_long(argc, argv, "a:b:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { +#else while ((c = getopt_long(argc, argv, "a:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { +#endif /* USE_LIVEBUFFER */ switch (c) { case 'a': AudioCommand = optarg; break; +#ifdef USE_LIVEBUFFER + case 'b': BufferDirectory = optarg; + if(optarg && *optarg && optarg[strlen(optarg)-1] == '/') + optarg[strlen(optarg)-1] = 0; + break; +#endif /* USE_LIVEBUFFER */ case 'c': ConfigDirectory = optarg; break; case 'd': DaemonMode = true; break; @@ -420,6 +433,9 @@ if (DisplayHelp) { printf("Usage: vdr [OPTIONS]\n\n" // for easier orientation, this is column 80| " -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n" +#ifdef USE_LIVEBUFFER + " -b DIR, --buffer=DIR use DIR as LiveBuffer directory\n" +#endif /*USE_LIVEBUFFER*/ " -c DIR, --config=DIR read config files from DIR (default: %s)\n" " -d, --daemon run in daemon mode\n" " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n" @@ -586,9 +602,12 @@ if (!PluginManager.LoadPlugins(true)) EXIT(2); - // Configuration data: +#ifdef USE_LIVEBUFFER + if (!BufferDirectory) + BufferDirectory = VideoDirectory; +#endif /*USE_LIVEBUFFER*/ if (!ConfigDirectory) ConfigDirectory = DEFAULTCONFDIR; @@ -1091,6 +1110,15 @@ cDisplaySubtitleTracks::Process(key); key = kNone; break; +#ifdef USE_LIVEBUFFER + case kFastRew: + if (!Interact) { + DELETE_MENU; + if(cRecordControls::StartLiveBuffer(key)) + key = kNone; + } // if + break; +#endif /*USE_LIVEBUFFER*/ // Pausing live video: case kPause: if (!cControl::Control()) { @@ -1198,6 +1226,28 @@ else cControl::Shutdown(); break; +#ifdef USE_LIVEBUFFER + case osSwitchChannel: + switch (key) { + // Toggle channels: + case kChanPrev: + case k0: { + if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel + || (LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])) + PreviousChannelIndex ^= 1; + Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]); + break; + } + case k1 ... k9: + DELETE_MENU; + cControl::Shutdown(); + Menu = new cDisplayChannel(NORMALKEY(key)); + break; + default: + break; + } // switch + break; +#endif /*USE_LIVEBUFFER*/ default: ; } } diff -Naur vdr-1.7.22/videodir.c vdr-1.7.22-livebuffer/videodir.c --- vdr-1.7.22/videodir.c 2008-02-16 14:00:03.000000000 +0100 +++ vdr-1.7.22-livebuffer/videodir.c 2011-12-31 11:16:23.000000000 +0100 @@ -20,6 +20,9 @@ #include "tools.h" const char *VideoDirectory = VIDEODIR; +#ifdef USE_LIVEBUFFER +const char *BufferDirectory = NULL; +#endif /*USE_LIVEBUFFER*/ class cVideoDirectory { private: @@ -106,17 +109,32 @@ cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags) { const char *ActualFileName = FileName; +#ifdef USE_LIVEBUFFER + bool SepBufferDir = false; // Incoming name must be in base video directory: + if (strstr(FileName, VideoDirectory) != FileName) { + if (strstr(FileName, BufferDirectory) == FileName) + SepBufferDir = true; + else { +#else if (strstr(FileName, VideoDirectory) != FileName) { +#endif /*USE_LIVEBUFFER*/ esyslog("ERROR: %s not in %s", FileName, VideoDirectory); errno = ENOENT; // must set 'errno' - any ideas for a better value? return NULL; } +#ifdef USE_LIVEBUFFER + } +#endif /*USE_LIVEBUFFER*/ // Are we going to create a new file? if ((Flags & O_CREAT) != 0) { cVideoDirectory Dir; +#ifdef USE_LIVEBUFFER + if (Dir.IsDistributed() && !SepBufferDir) { +#else if (Dir.IsDistributed()) { +#endif /*USE_LIVEBUFFER*/ // Find the directory with the most free space: int MaxFree = Dir.FreeMB(); while (Dir.Next()) { diff -Naur vdr-1.7.22/videodir.h vdr-1.7.22-livebuffer/videodir.h --- vdr-1.7.22/videodir.h 2008-02-16 13:53:11.000000000 +0100 +++ vdr-1.7.22-livebuffer/videodir.h 2011-12-31 11:16:23.000000000 +0100 @@ -14,6 +14,9 @@ #include "tools.h" extern const char *VideoDirectory; +#ifdef USE_LIVEBUFFER +extern const char *BufferDirectory; +#endif /*USE_LIVEBUFFER*/ cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags); int CloseVideoFile(cUnbufferedFile *File);