diff --git a/device.c b/device.c index 4b9c9cc7..c41e99da 100644 --- a/device.c +++ b/device.c @@ -1880,6 +1880,17 @@ void cDevice::DetachAllReceivers(void) ReleaseCamSlot(); } +cRecorder* cDevice::GetPreRecording(const cChannel *Channel) +{ + cMutexLock MutexLock(&mutexReceiver); + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i]) + if (receiver[i]->IsPreRecording(Channel)) + return (cRecorder*)receiver[i]; + } + return NULL; +} + // --- cTSBuffer ------------------------------------------------------------- cTSBuffer::cTSBuffer(int File, int Size, int DeviceNumber) diff --git a/device.h b/device.h index 2d0ac12f..0274e991 100644 --- a/device.h +++ b/device.h @@ -85,6 +85,7 @@ struct tTrackId { class cPlayer; class cReceiver; +class cRecorder; class cLiveSubtitle; class cDeviceHook : public cListObject { @@ -854,6 +855,8 @@ public: ///< Returns true if we are currently receiving. The parameter has no meaning (for backwards compatibility only). bool AttachReceiver(cReceiver *Receiver); ///< Attaches the given receiver to this device. + cRecorder* GetPreRecording(const cChannel *Channel); + ///< Get precocious recording for the channel if there is one. void Detach(cReceiver *Receiver, bool ReleaseCam = true); ///< Detaches the given receiver from this device. ///< If ReleaseCam is true, the CAM slot will be released if it diff --git a/dvbplayer.c b/dvbplayer.c index 2ee846b6..df45a7e3 100644 --- a/dvbplayer.c +++ b/dvbplayer.c @@ -249,13 +249,14 @@ private: cUnbufferedFile *replayFile; double framesPerSecond; bool isPesRecording; - bool pauseLive; + ReplayState replayState; bool eof; bool firstPacket; ePlayModes playMode; ePlayDirs playDir; int trickSpeed; int readIndex; + int startIndex; bool readIndependent; cFrame *readFrame; cFrame *playFrame; @@ -271,6 +272,8 @@ protected: virtual void Action(void); public: cDvbPlayer(const char *FileName, bool PauseLive); + cDvbPlayer(const char *FileName, ReplayState newReplayState); + void Construct(const char *FileName, ReplayState newReplayState); virtual ~cDvbPlayer(); void SetMarks(const cMarks *Marks); bool Active(void) { return cThread::Running(); } @@ -297,6 +300,17 @@ int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 }; cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive) :cThread("dvbplayer") { + Construct(FileName, PauseLive? restPauseLive : restNormal); +} + +cDvbPlayer::cDvbPlayer(const char *FileName, ReplayState newReplayState) +:cThread("dvbplayer") +{ + Construct(FileName, newReplayState); +} + +void cDvbPlayer::Construct(const char *FileName, ReplayState newReplayState) +{ nonBlockingFileReader = NULL; ringBuffer = NULL; marks = NULL; @@ -304,7 +318,8 @@ cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive) cRecording Recording(FileName); framesPerSecond = Recording.FramesPerSecond(); isPesRecording = Recording.IsPesRecording(); - pauseLive = PauseLive; + replayState = newReplayState; + bool reuse = (replayState == restReusePause || replayState == restReuseRewind); eof = false; firstPacket = true; playMode = pmPlay; @@ -323,15 +338,21 @@ cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive) return; ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE); // Create the index file: - index = new cIndexFile(FileName, false, isPesRecording, pauseLive); + index = new cIndexFile(FileName, false, isPesRecording, replayState == restPauseLive); if (!index) esyslog("ERROR: can't allocate index"); else if (!index->Ok()) { delete index; index = NULL; } - else if (PauseLive) + else if (reuse) framesPerSecond = cRecording(FileName).FramesPerSecond(); // the fps rate might have changed from the default + startIndex = 0; + if (replayState == restReuseRewind || replayState == restReusePause) { + int Current, Total; + GetIndex(Current, Total, false); + startIndex = max(Total - 1, 0); + } } cDvbPlayer::~cDvbPlayer() @@ -481,8 +502,21 @@ void cDvbPlayer::Action(void) bool CutIn = false; bool AtLastMark = false; - if (pauseLive) - Goto(0, true); + if (replayState == restPauseLive) { + Goto(0, true); + } + else if (replayState == restReuseRewind || replayState == restReusePause) { + readIndex = startIndex; + Goto(readIndex, true); + playMode = pmPlay; + if (replayState == restReuseRewind) { + Backward(); + } + else if (replayState == restReusePause) { + Pause(); + } + } + while (Running()) { if (WaitingForData) WaitingForData = !nonBlockingFileReader->WaitForDataMs(3); // this keeps the CPU load low, but reacts immediately on new data @@ -985,6 +1019,11 @@ cDvbPlayerControl::cDvbPlayerControl(const char *FileName, bool PauseLive) { } +cDvbPlayerControl::cDvbPlayerControl(const char *FileName, ReplayState replayState) +:cControl(player = new cDvbPlayer(FileName, replayState)) +{ +} + cDvbPlayerControl::~cDvbPlayerControl() { Stop(); diff --git a/dvbplayer.h b/dvbplayer.h index 2fe6e4c9..cb7673a0 100644 --- a/dvbplayer.h +++ b/dvbplayer.h @@ -16,6 +16,14 @@ class cDvbPlayer; +enum ReplayState +{ + restNormal, + restPauseLive, + restReusePause, + restReuseRewind +}; + class cDvbPlayerControl : public cControl { private: cDvbPlayer *player; @@ -25,6 +33,8 @@ public: // If PauseLive is true, special care is taken to make sure the index // file of the recording is long enough to allow the player to display // the first frame in still picture mode. + cDvbPlayerControl(const char *FileName, ReplayState replayState); + // Sets up a player for the given file. replayState represents the initial state. virtual ~cDvbPlayerControl(); void SetMarks(const cMarks *Marks); bool Active(void); diff --git a/menu.c b/menu.c index 8f4eb19e..2767d2fd 100644 --- a/menu.c +++ b/menu.c @@ -5302,6 +5302,16 @@ eOSState cDisplaySubtitleTracks::ProcessKey(eKeys Key) // --- cRecordControl -------------------------------------------------------- cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause) +{ + Construct(Device, Timers, Timer, Pause, NULL); +} + +cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused) +{ + Construct(Device, Timers, Timer, Pause, reused); +} + +void cRecordControl::Construct(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused) { const char *LastReplayed = cReplayControl::LastReplayed(); // must do this before locking schedules! // Whatever happens here, the timers will be modified in some way... @@ -5331,6 +5341,7 @@ cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, timer->SetPending(true); timer->SetRecording(true); event = timer->Event(); + if (reused != NULL) *reused = false; if (event || GetEvent()) dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText()); @@ -5360,8 +5371,21 @@ cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, if (MakeDirs(fileName, true)) { Recording.WriteInfo(); // we write this *before* attaching the recorder to the device, to make sure the info file is present when the recorder needs to update the fps value! const cChannel *ch = timer->Channel(); - recorder = new cRecorder(fileName, ch, timer->Priority()); - if (device->AttachReceiver(recorder)) { + + if (!Timer) { + recorder = device->GetPreRecording(ch); + if (recorder != NULL) { + recorder->ActivatePreRecording(fileName, timer->Priority()); + if (reused != NULL) *reused = true; + } + } + + if (recorder == NULL) { + recorder = new cRecorder(fileName, ch, timer->Priority()); + if (!device->AttachReceiver(recorder)) DELETENULL(recorder); + } + + if (recorder != NULL) { cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); if (!Timer && !LastReplayed) // an instant recording, maybe from cRecordControls::PauseLiveVideo() cReplayControl::SetRecording(fileName); @@ -5371,8 +5395,6 @@ cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, Recordings->AddByName(fileName); return; } - else - DELETENULL(recorder); } else timer->SetDeferred(DEFERTIMER); @@ -5452,7 +5474,7 @@ bool cRecordControl::Process(time_t t) cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; -bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) +bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause, bool* reused) { static time_t LastNoDiskSpaceMessage = 0; int FreeMB = 0; @@ -5490,7 +5512,7 @@ bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) if (!Timer || Timer->Matches()) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { - RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause); + RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause, reused); return RecordControls[i]->Process(time(NULL)); } } @@ -5514,6 +5536,11 @@ bool cRecordControls::Start(bool Pause) return Start(Timers, NULL, Pause); } +bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) +{ + return Start(Timers, Timer, Pause, NULL); +} + void cRecordControls::Stop(const char *InstantId) { LOCK_TIMERS_WRITE; @@ -5548,11 +5575,18 @@ void cRecordControls::Stop(cTimer *Timer) } bool cRecordControls::PauseLiveVideo(void) +{ + return PauseLiveVideo(false); +} + +bool cRecordControls::PauseLiveVideo(bool rewind) { Skins.Message(mtStatus, tr("Pausing live video...")); + bool reused = false; cReplayControl::SetRecording(NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed() - if (Start(true)) { - cReplayControl *rc = new cReplayControl(true); + LOCK_TIMERS_WRITE; + if (Start(Timers, NULL, true, &reused)) { + cReplayControl *rc = new cReplayControl(rewind? restReuseRewind : reused? restReusePause : restPauseLive); cControl::Launch(rc); cControl::Attach(); Skins.Message(mtStatus, NULL); @@ -5695,7 +5729,18 @@ cString cReplayControl::fileName; cReplayControl::cReplayControl(bool PauseLive) :cDvbPlayerControl(fileName, PauseLive) { - cDevice::PrimaryDevice()->SetKeepTracks(PauseLive); + Construct(PauseLive? restPauseLive : restNormal); +} + +cReplayControl::cReplayControl(ReplayState replayState) +:cDvbPlayerControl(fileName, replayState) +{ + Construct(replayState); +} + +void cReplayControl::Construct(ReplayState replayState) +{ + cDevice::PrimaryDevice()->SetKeepTracks(replayState == restPauseLive); currentReplayControl = this; displayReplay = NULL; marksModified = false; diff --git a/menu.h b/menu.h index a192d140..9a7e5f30 100644 --- a/menu.h +++ b/menu.h @@ -246,6 +246,8 @@ private: bool GetEvent(void); public: cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false); + cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused); + void Construct(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused); virtual ~cRecordControl(); bool Process(time_t t); cDevice *Device(void) { return device; } @@ -261,10 +263,12 @@ private: static int state; public: static bool Start(cTimers *Timers, cTimer *Timer, bool Pause = false); + static bool Start(cTimers *Timers, cTimer *Timer, bool Pause, bool* reused); static bool Start(bool Pause = false); static void Stop(const char *InstantId); static void Stop(cTimer *Timer); static bool PauseLiveVideo(void); + static bool PauseLiveVideo(bool rewind); static const char *GetInstantId(const char *LastInstantId); static cRecordControl *GetRecordControl(const char *FileName); static cRecordControl *GetRecordControl(const cTimer *Timer); @@ -320,6 +324,8 @@ private: void EditTest(void); public: cReplayControl(bool PauseLive = false); + cReplayControl(ReplayState replayState); + void Construct(ReplayState replayState); virtual ~cReplayControl(); void Stop(void); virtual cOsdObject *GetInfo(void); diff --git a/receiver.h b/receiver.h index 9bbc6bdb..b76969bb 100644 --- a/receiver.h +++ b/receiver.h @@ -85,6 +85,10 @@ public: ///< case the device is needed otherwise, so code that uses a cReceiver ///< should repeatedly check whether it is still attached, and if ///< it isn't, delete it (or take any other appropriate measures). + virtual bool IsPreRecording(const cChannel *Channel) { return false; } + ///< prerecords given channel; may be turned into a disc recording. + virtual bool ActivatePreRecording(const char* fileName, int Priority) { return false; } + ///< turn prerecording into a disc recording }; #endif //__RECEIVER_H diff --git a/recorder.c b/recorder.c index 30832017..e6de8999 100644 --- a/recorder.c +++ b/recorder.c @@ -164,11 +164,25 @@ void cFrameChecker::ReportBroken(void) cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority) :cReceiver(Channel, Priority) ,cThread("recording") +,tsChecker(NULL), frameChecker(NULL), ringBuffer(NULL), frameDetector(NULL), fileName(NULL), recordingInfo(NULL), index(NULL), recordFile(NULL), recordingName(NULL) { - tsChecker = new cTsChecker; - frameChecker = new cFrameChecker; + if (FileName != NULL) { + InitializeFile(FileName, Channel); + } +} + +void cRecorder::InitializeFile(const char *FileName, const cChannel *Channel) +{ + if (tsChecker == NULL) { + tsChecker = new cTsChecker; + } + if (frameChecker == NULL) { + frameChecker = new cFrameChecker; + } recordingName = strdup(FileName); - recordingInfo = new cRecordingInfo(recordingName); + if (recordingInfo == NULL) { + recordingInfo = new cRecordingInfo(recordingName); + } recordingInfo->Read(); oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording errors = oldErrors; @@ -193,7 +207,9 @@ cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority Pid = Channel->Dpid(0); Type = 0x06; } - frameDetector = new cFrameDetector(Pid, Type); + if (frameDetector == NULL) { + frameDetector = new cFrameDetector(Pid, Type); + } index = NULL; fileSize = 0; lastDiskSpaceCheck = time(NULL); diff --git a/recorder.h b/recorder.h index 825f4af3..9850581d 100644 --- a/recorder.h +++ b/recorder.h @@ -19,8 +19,8 @@ class cTsChecker; class cFrameChecker; -class cRecorder : public cReceiver, cThread { -private: +class cRecorder : public cReceiver, protected cThread { +protected: cTsChecker *tsChecker; cFrameChecker *frameChecker; cRingBufferLinear *ringBuffer; @@ -41,7 +41,6 @@ private: bool RunningLowOnDiskSpace(void); bool NextFile(void); void HandleErrors(bool Force = false); -protected: virtual void Activate(bool On); ///< If you override Activate() you need to call Detach() (which is a ///< member of the cReceiver class) from your own destructor in order @@ -49,6 +48,9 @@ protected: ///< destroyed. virtual void Receive(const uchar *Data, int Length); virtual void Action(void); + void InitializeFile(const char *FileName, const cChannel *Channel); + ///< Starts recording to file. + ///< Called in constructor if file name has been given. public: cRecorder(const char *FileName, const cChannel *Channel, int Priority); ///< Creates a new recorder for the given Channel and diff --git a/ringbuffer.c b/ringbuffer.c index 902c8878..89247d60 100644 --- a/ringbuffer.c +++ b/ringbuffer.c @@ -368,6 +368,25 @@ uchar *cRingBufferLinear::Get(int &Count) return NULL; } +uchar *cRingBufferLinear::GetRest(int &Count) +{ + int Head = head; + if (getThreadTid <= 0) + getThreadTid = cThread::ThreadId(); + int rest = Size() - tail; + int diff = Head - tail; + int cont = (diff >= 0) ? diff : Size() + diff - margin; + if (cont > rest) + cont = rest; + uchar *p = buffer + tail; + if (cont > 0) { + Count = gotten = cont; + return p; + } + WaitForGet(); + return NULL; +} + void cRingBufferLinear::Del(int Count) { if (Count > gotten) { diff --git a/ringbuffer.h b/ringbuffer.h index 746fc51e..6dfe6654 100644 --- a/ringbuffer.h +++ b/ringbuffer.h @@ -98,6 +98,12 @@ public: ///< The data will remain in the buffer until a call to Del() deletes it. ///< Returns a pointer to the data, and stores the number of bytes ///< actually available in Count. If the returned pointer is NULL, Count has no meaning. + uchar *GetRest(int &Count); + ///< Gets data from the ring buffer disregarding the margin. + ///< Might have to be called several times to get all data. + ///< The data will remain in the buffer until a call to Del() deletes it. + ///< Returns a pointer to the data, and stores the number of bytes + ///< actually available in Count. If the returned pointer is NULL, Count has no meaning. void Del(int Count); ///< Deletes at most Count bytes from the ring buffer. ///< Count must be less or equal to the number that was returned by a previous diff --git a/vdr.c b/vdr.c index 06c0c9a9..4424369f 100644 --- a/vdr.c +++ b/vdr.c @@ -1352,13 +1352,24 @@ int main(int argc, char *argv[]) key = kNone; break; // Pausing live video: + case kFastRew: + if (cOsd::IsOpen()) + break; + { + // test if there's a live buffer to rewind into... + LOCK_CHANNELS_READ; + if (cDevice::ActualDevice()->GetPreRecording(Channels->GetByNumber(cDevice::CurrentChannel())) == NULL) { + break; + } + } + // fall through to pause case kPlayPause: case kPause: if (!Control) { DELETE_MENU; if (Setup.PauseKeyHandling) { if (Setup.PauseKeyHandling > 1 || Interface->Confirm(tr("Pause live video?"))) { - if (!cRecordControls::PauseLiveVideo()) + if (!cRecordControls::PauseLiveVideo(int(key) == kFastRew)) Skins.QueueMessage(mtError, tr("No free DVB device to record!")); } }