On 23/12/2011 12:11, Dominic Evans wrote:
It's a patch from yaVDR and it doesn't apply to the vdr source tree as is
(at least for me).
Here's the patch from yaVDR rebased to apply to vanilla 1.7.21 sources:
https://gist.github.com/1513894
https://raw.github.com/gist/1513894
_______________________________________________
vdr mailing list
vdr@xxxxxxxxxxx
http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr
It works, thanks !
There is a typo error in vdr.c but that's all.
For gentoo users, I attached the patches I use. I relocated some hunk of
config.c and config.h and renamed USE_LIVEBUFFER to LIVEBUFFER in Makefile.
Patch the ebuild and put the other patch in
/etc/portage/patches/media-video/vdr-1.7.21 (I use the epatch_user tool
instead of the script, see the ebuild patch).
Marc.
>From d99a28abfd108690028d7847b8736e542b089fd0 Mon Sep 17 00:00:00 2001
From: Dominic Evans <oldmanuk@xxxxxxxxx>
Date: Fri, 23 Dec 2011 10:51:30 +0000
Subject: [PATCH] opt-96-livebuffer12-rmm.dpatch rebased onto 1.7.21
---
Makefile | 5 +
config.c | 7 +
config.h | 4 +
device.c | 21 +++
device.h | 4 +
dvbplayer.c | 51 ++++++++
livebuffer.c | 403 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
livebuffer.h | 47 +++++++
menu.c | 248 +++++++++++++++++++++++++++++++++++-
menu.h | 22 +++
osdbase.h | 3 +
player.c | 11 ++
po/de_DE.po | 3 +
recorder.c | 10 ++
recorder.h | 15 ++
recording.h | 19 +++
timers.c | 8 +
timers.h | 4 +
vdr.c | 52 ++++++++-
videodir.c | 18 +++
videodir.h | 3 +
21 files changed, 951 insertions(+), 7 deletions(-)
create mode 100644 livebuffer.c
create mode 100644 livebuffer.h
diff --git a/Makefile b/Makefile
index 18d7eb9..bc49dac 100644
--- a/Makefile
+++ b/Makefile
@@ -60,6 +60,11 @@ DEFINES += -DBIDI
LIBS += $(shell pkg-config --libs fribidi)
endif
+ifdef LIVEBUFFER
+DEFINES += -DUSE_LIVEBUFFER
+OBJS += livebuffer.o
+endif
+
LIRC_DEVICE ?= /dev/lircd
RCU_DEVICE ?= /dev/ttyS1
diff --git a/config.c b/config.c
index 73bb00d..14146a0 100644
--- a/config.c
+++ b/config.c
@@ -397,6 +397,10 @@
CurrentDolby = 0;
InitialChannel = "";
InitialVolume = -1;
+#ifdef USE_LIVEBUFFER
+ LiveBufferSize = 30;
+ LiveBufferMaxFileSize = 100;
+#endif /*USE_LIVEBUFFER*/
ChannelsWrap = 0;
EmergencyExit = 1;
}
@@ -589,6 +589,10 @@ bool cSetup::Parse(const char *Name, const char *Value)
else if (!strcasecmp(Name, "CurrentDolby")) CurrentDolby = atoi(Value);
else if (!strcasecmp(Name, "InitialChannel")) InitialChannel = Value;
else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(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
@@ -685,6 +689,9 @@
Store("CurrentDolby", CurrentDolby);
Store("InitialChannel", InitialChannel);
Store("InitialVolume", InitialVolume);
+#ifdef USE_LIVEBUFFER
+ Store("LiveBufferSize", LiveBufferSize);
+#endif /* LIVEBUFFER */
Store("ChannelsWrap", ChannelsWrap);
Store("EmergencyExit", EmergencyExit);
diff --git a/config.h b/config.h
index c51e3df..1972195 100644
--- a/config.h
+++ b/config.h
@@ -288,6 +288,10 @@
int CurrentVolume;
int CurrentDolby;
int InitialVolume;
+#ifdef USE_LIVEBUFFER
+ int LiveBufferSize;
+ int LiveBufferMaxFileSize;
+#endif /*USE_LIVEBUFFER*/
int ChannelsWrap;
int EmergencyExit;
int __EndData__;
diff --git a/device.c b/device.c
index ba098d8..172f3b3 100644
--- a/device.c
+++ b/device.c
@@ -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 ---------------------------------------------------------
@@ -661,6 +665,14 @@ bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView)
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");
}
@@ -718,8 +730,17 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
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 --git a/device.h b/device.h
index fd587a8..2bebe89 100644
--- a/device.h
+++ b/device.h
@@ -31,7 +31,11 @@
#define MAXVOLUME 255
#define VOLUMEDELTA 5 // used to increase/decrease the volume
+#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 --git a/dvbplayer.c b/dvbplayer.c
index 017df6d..800a31d 100644
--- a/dvbplayer.c
+++ b/dvbplayer.c
@@ -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 @@ public:
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 @@ private:
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 @@ cDvbPlayer::cDvbPlayer(const char *FileName)
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 @@ cDvbPlayer::~cDvbPlayer()
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 @@ void cDvbPlayer::Action(void)
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 @@ void cDvbPlayer::Action(void)
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 @@ void cDvbPlayer::Action(void)
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 @@ void cDvbPlayer::Action(void)
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 --git a/livebuffer.c b/livebuffer.c
new file mode 100644
index 0000000..afc988d
--- /dev/null
+++ b/livebuffer.c
@@ -0,0 +1,403 @@
+#ifdef USE_LIVEBUFFER
+#include "livebuffer.h"
+#if VDRVERSNUM >= 10716
+
+#include <vector>
+#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<tLiveIndex>::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<tLiveIndex>::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<tLiveIndex>::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<tLiveIndex>::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<tLiveIndex> idx;
+ virtual std::vector<tLiveIndex>::iterator GetIndex(int Index) {
+ if(!idx.size()) return idx.end();
+ std::vector<tLiveIndex>::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 --git a/livebuffer.h b/livebuffer.h
new file mode 100644
index 0000000..8382d7d
--- /dev/null
+++ b/livebuffer.h
@@ -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 --git a/menu.c b/menu.c
index ef2bb46..621d2ce 100644
--- a/menu.c
+++ b/menu.c
@@ -3043,7 +3043,11 @@ eOSState cMenuSetupCAM::ProcessKey(eKeys Key)
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);
@@ -3054,6 +3058,9 @@ cMenuSetupRecord::cMenuSetupRecord(void)
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");
@@ -3063,7 +3070,12 @@ cMenuSetupRecord::cMenuSetupRecord(void)
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));
@@ -4134,7 +4146,11 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause)
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);
@@ -4219,6 +4235,10 @@ bool cRecordControl::Process(time_t t)
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;
@@ -4290,8 +4310,31 @@ void cRecordControls::Stop(const char *InstantId)
}
}
+
+#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)) {
@@ -4308,6 +4351,54 @@ bool cRecordControls::PauseLiveVideo(void)
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++) {
@@ -4506,21 +4597,30 @@ void cReplayControl::Hide(void)
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;
@@ -4534,6 +4634,45 @@ bool cReplayControl::ShowProgress(bool Initial)
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: <event title> "
+ // OR "Timeshift mode: <channel name>"
+ // 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);
@@ -4635,6 +4774,9 @@ void cReplayControl::TimeSearchProcess(eKeys Key)
void cReplayControl::TimeSearch(void)
{
+#ifdef USE_LIVEBUFFER
+ if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
timeSearchTime = timeSearchPos = 0;
timeSearchHide = false;
if (modeOnly)
@@ -4653,6 +4795,9 @@ void cReplayControl::TimeSearch(void)
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);
@@ -4673,6 +4818,9 @@ void cReplayControl::MarkToggle(void)
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)) {
@@ -4687,6 +4835,9 @@ void cReplayControl::MarkJump(bool Forward)
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);
@@ -4711,6 +4862,9 @@ void cReplayControl::MarkMove(bool Forward)
void cReplayControl::EditCut(void)
{
+#ifdef USE_LIVEBUFFER
+ if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
if (fileName) {
Hide();
if (!cCutter::Active()) {
@@ -4729,6 +4883,9 @@ void cReplayControl::EditCut(void)
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);
@@ -4760,7 +4917,14 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
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;
@@ -4777,12 +4941,34 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
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;
@@ -4793,15 +4979,52 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
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()), frames / FramesPerSecond());
+ 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)) {
@@ -4832,7 +5055,20 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
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 --git a/menu.h b/menu.h
index ec1c175..2d55470 100644
--- a/menu.h
+++ b/menu.h
@@ -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 @@ class cRecordControls {
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 @@ public:
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 --git a/osdbase.h b/osdbase.h
index 91c5ff7..27c22b7 100644
--- a/osdbase.h
+++ b/osdbase.h
@@ -33,6 +33,9 @@ enum eOSState { osUnknown,
osSwitchDvb,
osBack,
osEnd,
+#ifdef USE_LIVEBUFFER
+ osSwitchChannel,
+#endif /*USE_LIVEBUFFER*/
os_User, // the following values can be used locally
osUser1,
osUser2,
diff --git a/player.c b/player.c
index 3490565..9aa2956 100644
--- a/player.c
+++ b/player.c
@@ -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 @@ cControl *cControl::Control(void)
void cControl::Launch(cControl *Control)
{
+#ifdef USE_LIVEBUFFER
+ if(!dynamic_cast<cTransferControl *>(Control)) {
+ if(!dynamic_cast<cReplayControl *>(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 --git a/po/de_DE.po b/po/de_DE.po
index 6d5b822..355348b 100644
--- a/po/de_DE.po
+++ b/po/de_DE.po
@@ -25,6 +25,9 @@ msgstr "Kanal nicht verf
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 --git a/recorder.c b/recorder.c
index a6cab47..70e4659 100644
--- a/recorder.c
+++ b/recorder.c
@@ -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 @@ void cRecorder::Action(void)
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 @@ void cRecorder::Action(void)
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 --git a/recorder.h b/recorder.h
index 05cc42b..1d2aa04 100644
--- a/recorder.h
+++ b/recorder.h
@@ -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 --git a/recording.h b/recording.h
index 37979ec..52d57c8 100644
--- a/recording.h
+++ b/recording.h
@@ -264,7 +264,26 @@ public:
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 --git a/timers.c b/timers.c
index 54ed1ba..c2298fa 100644
--- a/timers.c
+++ b/timers.c
@@ -25,7 +25,11 @@
// --- cTimer ----------------------------------------------------------------
+#ifdef USE_LIVEBUFFER
+cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel, int Forerun)
+#else
cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
+#endif /*USE_LIVEBUFFER*/
{
startTime = stopTime = 0;
lastSetEvent = 0;
@@ -35,7 +39,11 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
if (Instant)
SetFlags(tfActive | tfInstant);
channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
+#ifdef USE_LIVEBUFFER
+ time_t t = time(NULL) - Forerun;
+#else
time_t t = time(NULL);
+#endif /*USE_LIVEBUFFER*/
struct tm tm_r;
struct tm *now = localtime_r(&t, &tm_r);
day = SetTime(t, 0);
diff --git a/timers.h b/timers.h
index 1d733ee..d35e042 100644
--- a/timers.h
+++ b/timers.h
@@ -43,7 +43,11 @@ private:
char *aux;
const cEvent *event;
public:
+#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*/
cTimer(const cEvent *Event);
cTimer(const cTimer &Timer);
virtual ~cTimer();
diff --git a/vdr.c b/vdr.c
index c32e45f..b9c4460 100644
--- a/vdr.c
+++ b/vdr.c
@@ -218,6 +218,9 @@ int main(int argc, char *argv[])
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 main(int argc, char *argv[])
};
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 @@ int main(int argc, char *argv[])
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 @@ int main(int argc, char *argv[])
if (!PluginManager.LoadPlugins(true))
EXIT(2);
-
// Configuration data:
+#ifdef USE_LIVEBUFFER
+ if (!BufferDirectory)
+ BufferDirectory = VideoDirectory;
+#endif /*USE_LIVEBUFFER*/
if (!ConfigDirectory)
ConfigDirectory = DEFAULTCONFDIR;
@@ -1092,6 +1111,15 @@ int main(int argc, char *argv[])
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()) {
@@ -1199,6 +1227,28 @@ int main(int argc, char *argv[])
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 --git a/videodir.c b/videodir.c
index 7331a85..14d4373 100644
--- a/videodir.c
+++ b/videodir.c
@@ -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 @@ const char *cVideoDirectory::Adjust(const char *FileName)
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 --git a/videodir.h b/videodir.h
index 5e9aef5..57b8652 100644
--- a/videodir.h
+++ b/videodir.h
@@ -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);
--
1.7.5.4
--- vdr-1.7.21-r1.ebuild 2011-10-25 03:10:08.000000000 +0200
+++ vdr-1.7.21-r2.ebuild 2011-12-23 13:10:44.000000000 +0100
@@ -7,7 +7,7 @@
inherit eutils flag-o-matic multilib
# Switches supported by extensions-patch
-EXT_PATCH_FLAGS="alternatechannel cutterlimit
+EXT_PATCH_FLAGS="alternatechannel cutterlimit livebuffer
ddepgentry dvlvidprefer graphtft hardlinkcutter jumpplay
lnbshare liemikuutio mainmenuhooks menuorg noepg pinplugin setup timerinfo
yaepg lircsettings"
@@ -295,27 +295,8 @@
fi
- # apply local patches defined by variable VDR_LOCAL_PATCHES_DIR
- if test -n "${VDR_LOCAL_PATCHES_DIR}"; then
- local dir_tmp_var
- local LOCALPATCHES_SUBDIR=${PV}
- for dir_tmp_var in allversions-fallback ${PV%_p*} ${PV} ; do
- if [[ -d ${VDR_LOCAL_PATCHES_DIR}/${dir_tmp_var} ]]; then
- LOCALPATCHES_SUBDIR="${dir_tmp_var}"
- fi
- done
-
- echo
- if [[ ${LOCALPATCHES_SUBDIR} == ${PV} ]]; then
- einfo "Applying local patches"
- else
- einfo "Applying local patches (Using subdirectory: ${LOCALPATCHES_SUBDIR})"
- fi
-
- for LOCALPATCH in ${VDR_LOCAL_PATCHES_DIR}/${LOCALPATCHES_SUBDIR}/*.{diff,patch}; do
- test -f "${LOCALPATCH}" && epatch "${LOCALPATCH}"
- done
- fi
+ # apply local patches
+ epatch_user
if [[ -n "${VDRSOURCE_DIR}" ]]; then
cp -r "${S}" "${T}"/source-tree
_______________________________________________
vdr mailing list
vdr@xxxxxxxxxxx
http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr