/* * recorder.c: The actual DVB recorder * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * * $Id: recorder.c 4.1 2015/08/03 10:23:35 kls Exp $ */ #include "recorder.h" #include "shutdown.h" #define RECORDERBUFSIZE (MEGABYTE(20) / TS_SIZE * TS_SIZE) // multiple of TS_SIZE // The maximum time we wait before assuming that a recorded video data stream // is broken: #define MAXBROKENTIMEOUT 30000 // milliseconds #define MINFREEDISKSPACE (512) // MB #define DISKCHECKINTERVAL 100 // seconds // --- cRecorder ------------------------------------------------------------- cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority) :cReceiver(Channel, Priority) ,cThread("recording") { recordingName = strdup(FileName); // Make sure the disk is up and running: SpinUpDisk(FileName); ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE, true, "Recorder"); ringBuffer->SetTimeouts(0, 100); ringBuffer->SetIoThrottle(); int Pid = Channel->Vpid(); int Type = Channel->Vtype(); if (!Pid && Channel->Apid(0)) { Pid = Channel->Apid(0); Type = 0x04; } if (!Pid && Channel->Dpid(0)) { Pid = Channel->Dpid(0); Type = 0x06; } frameDetector = new cFrameDetector(Pid, Type); index = NULL; fileSize = 0; lastDiskSpaceCheck = time(NULL); fileName = new cFileName(FileName, true); int PatVersion, PmtVersion; if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion)) patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1); patPmtGenerator.SetChannel(Channel); recordFile = fileName->Open(); if (!recordFile) return; // Create the index file: index = new cIndexFile(FileName, true); if (!index) esyslog("ERROR: can't allocate index"); // let's continue without index, so we'll at least have the recording } cRecorder::~cRecorder() { Detach(); delete index; delete fileName; delete frameDetector; delete ringBuffer; free(recordingName); } bool cRecorder::RunningLowOnDiskSpace(void) { if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { int Free = FreeDiskSpaceMB(fileName->Name()); lastDiskSpaceCheck = time(NULL); if (Free < MINFREEDISKSPACE) { dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE); return true; } } return false; } bool cRecorder::NextFile(void) { if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) { recordFile = fileName->NextFile(); fileSize = 0; } } return recordFile != NULL; } void cRecorder::Activate(bool On) { if (On) Start(); else Cancel(3); } void cRecorder::Receive(uchar *Data, int Length) { if (Running()) { int p = ringBuffer->Put(Data, Length); if (p != Length && Running()) ringBuffer->ReportOverflow(Length - p); } } void cRecorder::Action(void) { cTimeMs t(MAXBROKENTIMEOUT); bool InfoWritten = false; bool FirstIframeSeen = false; while (Running()) { int r; uchar *b = ringBuffer->Get(r); if (b) { int Count = frameDetector->Analyze(b, r); if (Count) { if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame break; if (frameDetector->Synced()) { if (!InfoWritten) { cRecordingInfo RecordingInfo(recordingName); if (RecordingInfo.Read()) { if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) { RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond()); RecordingInfo.Write(); LOCK_RECORDINGS_WRITE; Recordings->UpdateByName(recordingName); } } InfoWritten = true; cRecordingUserCommand::InvokeCommand(RUC_STARTRECORDING, recordingName); } if (FirstIframeSeen || frameDetector->IndependentFrame()) { FirstIframeSeen = true; // start recording with the first I-frame if (!NextFile()) break; if (index && frameDetector->NewFrame()) index->Write(frameDetector->IndependentFrame(), fileName->Number(), fileSize); if (frameDetector->IndependentFrame()) { recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE); fileSize += TS_SIZE; int Index = 0; while (uchar *pmt = patPmtGenerator.GetPmt(Index)) { recordFile->Write(pmt, TS_SIZE); fileSize += TS_SIZE; } t.Set(MAXBROKENTIMEOUT); } if (recordFile->Write(b, Count) < 0) { LOG_ERROR_STR(fileName->Name()); break; } fileSize += Count; } } ringBuffer->Del(Count); } } if (t.TimedOut()) { esyslog("ERROR: video data stream broken"); ShutdownHandler.RequestEmergencyExit(); t.Set(MAXBROKENTIMEOUT); } } }