vdr  2.6.1
recorder.c
Go to the documentation of this file.
1 /*
2  * recorder.c: The actual DVB recorder
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * $Id: recorder.c 5.4 2021/06/19 14:21:16 kls Exp $
8  */
9 
10 #include "recorder.h"
11 #include "shutdown.h"
12 
13 #define RECORDERBUFSIZE (MEGABYTE(20) / TS_SIZE * TS_SIZE) // multiple of TS_SIZE
14 
15 // The maximum time we wait before assuming that a recorded video data stream
16 // is broken:
17 #define MAXBROKENTIMEOUT 30000 // milliseconds
18 
19 #define MINFREEDISKSPACE (512) // MB
20 #define DISKCHECKINTERVAL 100 // seconds
21 
22 static bool DebugChecks = false;
23 
24 // cTsChecker and cFrameChecker are used to detect errors in the recorded data stream.
25 // While cTsChecker checks the continuity counter of the incoming TS packets, cFrameChecker
26 // works on entire frames, checking their PTS (Presentation Time Stamps) to see whether
27 // all expected frames arrive. The resulting number of errors is not a precise value.
28 // If it is zero, the recording can be safely considered error free. The higher the value,
29 // the more damaged the recording is.
30 
31 // --- cTsChecker ------------------------------------------------------------
32 
33 #define TS_CC_UNKNOWN 0xFF
34 
35 class cTsChecker {
36 private:
38  int errors;
39  void Report(int Pid, const char *Message);
40 public:
41  cTsChecker(void);
42  void CheckTs(const uchar *Data, int Length);
43  int Errors(void) { return errors; }
44  };
45 
47 {
48  memset(counter, TS_CC_UNKNOWN, sizeof(counter));
49  errors = 0;
50 }
51 
52 void cTsChecker::Report(int Pid, const char *Message)
53 {
54  errors++;
55  if (DebugChecks)
56  fprintf(stderr, "%s: TS error #%d on PID %d (%s)\n", *TimeToString(time(NULL)), errors, Pid, Message);
57 }
58 
59 void cTsChecker::CheckTs(const uchar *Data, int Length)
60 {
61  int Pid = TsPid(Data);
62  uchar Cc = TsContinuityCounter(Data);
63  if (TsHasPayload(Data)) {
64  if (TsError(Data))
65  Report(Pid, "tei");
66  else if (TsIsScrambled(Data))
67  Report(Pid, "scrambled");
68  else {
69  uchar OldCc = counter[Pid];
70  if (OldCc != TS_CC_UNKNOWN) {
71  uchar NewCc = (OldCc + 1) & TS_CONT_CNT_MASK;
72  if (Cc != NewCc)
73  Report(Pid, "continuity");
74  }
75  }
76  }
77  counter[Pid] = Cc;
78 }
79 
80 // --- cFrameChecker ---------------------------------------------------------
81 
82 #define MAX_BACK_REFS 32
83 
85 private:
87  int64_t lastPts;
88  uint32_t backRefs;
90  int errors;
91  void Report(const char *Message, int NumErrors = 1);
92 public:
93  cFrameChecker(void);
94  void SetFrameDelta(int FrameDelta) { frameDelta = FrameDelta; }
95  void CheckFrame(const uchar *Data, int Length);
96  void ReportBroken(void);
97  int Errors(void) { return errors; }
98  };
99 
101 {
103  lastPts = -1;
104  backRefs = 0;
105  lastFwdRef = 0;
106  errors = 0;
107 }
108 
109 void cFrameChecker::Report(const char *Message, int NumErrors)
110 {
111  errors += NumErrors;
112  if (DebugChecks)
113  fprintf(stderr, "%s: frame error #%d (%s)\n", *TimeToString(time(NULL)), errors, Message);
114 }
115 
116 void cFrameChecker::CheckFrame(const uchar *Data, int Length)
117 {
118  int64_t Pts = TsGetPts(Data, Length);
119  if (Pts >= 0) {
120  if (lastPts >= 0) {
121  int Diff = int(round((PtsDiff(lastPts, Pts) / double(frameDelta))));
122  if (Diff > 0) {
123  if (Diff <= MAX_BACK_REFS) {
124  if (lastFwdRef > 1) {
125  if (backRefs != uint32_t((1 << (lastFwdRef - 1)) - 1))
126  Report("missing backref");
127  }
128  }
129  else
130  Report("missed", Diff);
131  backRefs = 0;
132  lastFwdRef = Diff;
133  lastPts = Pts;
134  }
135  else if (Diff < 0) {
136  Diff = -Diff;
137  if (Diff <= MAX_BACK_REFS) {
138  int b = 1 << (Diff - 1);
139  if ((backRefs & b) != 0)
140  Report("duplicate backref");
141  backRefs |= b;
142  }
143  else
144  Report("rev diff too big");
145  }
146  else
147  Report("zero diff");
148  }
149  else
150  lastPts = Pts;
151  }
152  else
153  Report("no PTS");
154 }
155 
157 {
158  int MissedFrames = MAXBROKENTIMEOUT / 1000 * PTSTICKS / frameDelta;
159  Report("missed", MissedFrames);
160 }
161 
162 // --- cRecorder -------------------------------------------------------------
163 
164 cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
165 :cReceiver(Channel, Priority)
166 ,cThread("recording")
167 {
168  tsChecker = new cTsChecker;
170  recordingName = strdup(FileName);
172  recordingInfo->Read();
173  oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
174  errors = oldErrors;
175  lastErrors = errors;
176  firstIframeSeen = false;
177 
178  // Make sure the disk is up and running:
179 
180  SpinUpDisk(FileName);
181 
183  ringBuffer->SetTimeouts(0, 100);
185 
186  int Pid = Channel->Vpid();
187  int Type = Channel->Vtype();
188  if (!Pid && Channel->Apid(0)) {
189  Pid = Channel->Apid(0);
190  Type = 0x04;
191  }
192  if (!Pid && Channel->Dpid(0)) {
193  Pid = Channel->Dpid(0);
194  Type = 0x06;
195  }
196  frameDetector = new cFrameDetector(Pid, Type);
197  index = NULL;
198  fileSize = 0;
199  lastDiskSpaceCheck = time(NULL);
200  lastErrorLog = 0;
201  fileName = new cFileName(FileName, true);
202  int PatVersion, PmtVersion;
203  if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion))
204  patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1);
205  patPmtGenerator.SetChannel(Channel);
206  recordFile = fileName->Open();
207  if (!recordFile)
208  return;
209  // Create the index file:
210  index = new cIndexFile(FileName, true);
211  if (!index)
212  esyslog("ERROR: can't allocate index");
213  // let's continue without index, so we'll at least have the recording
214 }
215 
217 {
218  Detach();
219  delete index;
220  delete fileName;
221  delete frameDetector;
222  delete ringBuffer;
223  delete frameChecker;
224  delete tsChecker;
225  free(recordingName);
226 }
227 
228 #define ERROR_LOG_DELTA 1 // seconds between logging errors
229 
230 void cRecorder::HandleErrors(bool Force)
231 {
232  // We don't log every single error separately, to avoid spamming the log file:
233  if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) {
235  if (errors > lastErrors) {
236  int d = errors - lastErrors;
237  if (DebugChecks)
238  fprintf(stderr, "%s: %s: %d error%s\n", *TimeToString(time(NULL)), recordingName, d, d > 1 ? "s" : "");
239  esyslog("%s: %d error%s", recordingName, d, d > 1 ? "s" : "");
241  recordingInfo->Write();
243  Recordings->UpdateByName(recordingName);
244  }
245  lastErrors = errors;
246  lastErrorLog = time(NULL);
247  }
248 }
249 
251 {
252  if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
253  int Free = FreeDiskSpaceMB(fileName->Name());
254  lastDiskSpaceCheck = time(NULL);
255  if (Free < MINFREEDISKSPACE) {
256  dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
257  return true;
258  }
259  }
260  return false;
261 }
262 
264 {
265  if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
268  fileSize = 0;
269  }
270  }
271  return recordFile != NULL;
272 }
273 
274 void cRecorder::Activate(bool On)
275 {
276  if (On)
277  Start();
278  else
279  Cancel(3);
280 }
281 
282 void cRecorder::Receive(const uchar *Data, int Length)
283 {
284  if (Running()) {
285  static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
286  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
287  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
288  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
289  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
290  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
291  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
292  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
293  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
294  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
295  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
296  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
297  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
298  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
299  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
300  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
301  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
302  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
303  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
304  0xFF, 0xFF}; // Length is always TS_SIZE!
305  if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
306  return; // Adaptation Field Filler found, skipping
307  int p = ringBuffer->Put(Data, Length);
308  if (p != Length && Running())
309  ringBuffer->ReportOverflow(Length - p);
310  else if (firstIframeSeen) // we ignore any garbage before the first I-frame
311  tsChecker->CheckTs(Data, Length);
312  }
313 }
314 
316 {
318  bool InfoWritten = false;
319  while (Running()) {
320  int r;
321  uchar *b = ringBuffer->Get(r);
322  if (b) {
323  int Count = frameDetector->Analyze(b, r);
324  if (Count) {
325  if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
326  break;
327  if (frameDetector->Synced()) {
328  if (!InfoWritten) {
331  recordingInfo->Write();
333  Recordings->UpdateByName(recordingName);
334  }
335  InfoWritten = true;
338  }
340  firstIframeSeen = true; // start recording with the first I-frame
341  if (!NextFile())
342  break;
343  if (frameDetector->NewFrame()) {
344  if (index)
346  if (frameChecker)
347  frameChecker->CheckFrame(b, Count);
348  }
351  fileSize += TS_SIZE;
352  int Index = 0;
353  while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
354  recordFile->Write(pmt, TS_SIZE);
355  fileSize += TS_SIZE;
356  }
358  }
359  if (recordFile->Write(b, Count) < 0) {
361  break;
362  }
363  HandleErrors();
364  fileSize += Count;
365  }
366  }
367  ringBuffer->Del(Count);
368  }
369  }
370  if (t.TimedOut()) {
372  HandleErrors(true);
373  esyslog("ERROR: video data stream broken");
376  }
377  }
378  HandleErrors(true);
379 }
int Vpid(void) const
Definition: channels.h:153
int Dpid(int i) const
Definition: channels.h:160
int Vtype(void) const
Definition: channels.h:155
int Apid(int i) const
Definition: channels.h:159
cUnbufferedFile * NextFile(void)
Definition: recording.c:3078
uint16_t Number(void)
Definition: recording.h:506
cUnbufferedFile * Open(void)
Definition: recording.c:3002
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:2951
const char * Name(void)
Definition: recording.h:505
uint32_t backRefs
Definition: recorder.c:88
void CheckFrame(const uchar *Data, int Length)
Definition: recorder.c:116
int frameDelta
Definition: recorder.c:86
void Report(const char *Message, int NumErrors=1)
Definition: recorder.c:109
cFrameChecker(void)
Definition: recorder.c:100
void SetFrameDelta(int FrameDelta)
Definition: recorder.c:94
int lastFwdRef
Definition: recorder.c:89
int64_t lastPts
Definition: recorder.c:87
void ReportBroken(void)
Definition: recorder.c:156
int Errors(void)
Definition: recorder.c:97
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
Definition: remux.h:538
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition: remux.h:543
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition: remux.h:547
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
Definition: remux.c:1654
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
Definition: remux.h:540
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
Definition: recording.c:2742
uchar * GetPmt(int &Index)
Returns a pointer to the Index'th TS packet of the PMT section.
Definition: remux.c:600
void SetChannel(const cChannel *Channel)
Sets the Channel for which the PAT/PMT shall be generated.
Definition: remux.c:585
void SetVersions(int PatVersion, int PmtVersion)
Sets the version numbers for the generated PAT and PMT, in case this generator is used to,...
Definition: remux.c:579
uchar * GetPat(void)
Returns a pointer to the PAT section, which consists of exactly one TS packet.
Definition: remux.c:594
void Detach(void)
Definition: receiver.c:125
cRecorder(const char *FileName, const cChannel *Channel, int Priority)
Creates a new recorder for the given Channel and the given Priority that will record into the file Fi...
Definition: recorder.c:164
bool NextFile(void)
Definition: recorder.c:263
cFileName * fileName
Definition: recorder.h:29
cIndexFile * index
Definition: recorder.h:31
cTsChecker * tsChecker
Definition: recorder.h:24
time_t lastErrorLog
Definition: recorder.h:37
virtual void Receive(const uchar *Data, int Length)
This function is called from the cDevice we are attached to, and delivers one TS packet from the set ...
Definition: recorder.c:282
void HandleErrors(bool Force=false)
Definition: recorder.c:230
off_t fileSize
Definition: recorder.h:35
cRecordingInfo * recordingInfo
Definition: recorder.h:30
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: recorder.c:315
cFrameDetector * frameDetector
Definition: recorder.h:27
time_t lastDiskSpaceCheck
Definition: recorder.h:36
cUnbufferedFile * recordFile
Definition: recorder.h:32
bool firstIframeSeen
Definition: recorder.h:34
cRingBufferLinear * ringBuffer
Definition: recorder.h:26
char * recordingName
Definition: recorder.h:33
int oldErrors
Definition: recorder.h:38
bool RunningLowOnDiskSpace(void)
Definition: recorder.c:250
int errors
Definition: recorder.h:39
virtual ~cRecorder()
Definition: recorder.c:216
virtual void Activate(bool On)
If you override Activate() you need to call Detach() (which is a member of the cReceiver class) from ...
Definition: recorder.c:274
cFrameChecker * frameChecker
Definition: recorder.h:25
cPatPmtGenerator patPmtGenerator
Definition: recorder.h:28
int lastErrors
Definition: recorder.h:40
void SetFramesPerSecond(double FramesPerSecond)
Definition: recording.c:449
int Errors(void) const
Definition: recording.h:92
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:527
bool Read(FILE *f)
Definition: recording.c:466
void SetErrors(int Errors)
Definition: recording.c:461
double FramesPerSecond(void) const
Definition: recording.h:89
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:2339
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
Definition: ringbuffer.c:371
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
Definition: ringbuffer.c:306
uchar * Get(int &Count)
Gets data from the ring buffer.
Definition: ringbuffer.c:346
void SetTimeouts(int PutTimeout, int GetTimeout)
Definition: ringbuffer.c:89
void SetIoThrottle(void)
Definition: ringbuffer.c:95
void ReportOverflow(int Bytes)
Definition: ringbuffer.c:101
int MaxVideoFileSize
Definition: config.h:344
void RequestEmergencyExit(void)
Requests an emergency exit of the VDR main loop.
Definition: shutdown.c:93
Definition: thread.h:79
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition: thread.c:304
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
Definition: thread.h:101
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
Definition: thread.c:354
Definition: tools.h:401
void Set(int Ms=0)
Sets the timer.
Definition: tools.c:792
bool TimedOut(void) const
Definition: tools.c:797
uchar counter[MAXPID]
Definition: recorder.c:37
cTsChecker(void)
Definition: recorder.c:46
void CheckTs(const uchar *Data, int Length)
Definition: recorder.c:59
int errors
Definition: recorder.c:38
int Errors(void)
Definition: recorder.c:43
void Report(int Pid, const char *Message)
Definition: recorder.c:52
ssize_t Write(const void *Data, size_t Size)
Definition: tools.c:1947
cSetup Setup
Definition: config.c:372
#define MAXBROKENTIMEOUT
Definition: recorder.c:17
#define DISKCHECKINTERVAL
Definition: recorder.c:20
static bool DebugChecks
Definition: recorder.c:22
#define ERROR_LOG_DELTA
Definition: recorder.c:228
#define TS_CC_UNKNOWN
Definition: recorder.c:33
#define MAX_BACK_REFS
Definition: recorder.c:82
#define MINFREEDISKSPACE
Definition: recorder.c:19
#define RECORDERBUFSIZE
Definition: recorder.c:13
#define DEFAULTFRAMESPERSECOND
Definition: recording.h:352
#define LOCK_RECORDINGS_WRITE
Definition: recording.h:308
#define RUC_STARTRECORDING
Definition: recording.h:422
int64_t PtsDiff(int64_t Pts1, int64_t Pts2)
Returns the difference between two PTS values.
Definition: remux.c:234
#define MAXPID
Definition: remux.c:464
int64_t TsGetPts(const uchar *p, int l)
Definition: remux.c:160
bool TsError(const uchar *p)
Definition: remux.h:77
int TsPid(const uchar *p)
Definition: remux.h:82
bool TsHasPayload(const uchar *p)
Definition: remux.h:62
bool TsIsScrambled(const uchar *p)
Definition: remux.h:93
uchar TsContinuityCounter(const uchar *p)
Definition: remux.h:98
#define TS_SIZE
Definition: remux.h:34
#define PTSTICKS
Definition: remux.h:57
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:503
#define TS_CONT_CNT_MASK
Definition: remux.h:42
cShutdownHandler ShutdownHandler
Definition: shutdown.c:27
int FreeDiskSpaceMB(const char *Directory, int *UsedMB)
Definition: tools.c:464
cString TimeToString(time_t t)
Converts the given time to a string of the form "www mmm dd hh:mm:ss yyyy".
Definition: tools.c:1225
bool SpinUpDisk(const char *FileName)
Definition: tools.c:685
#define MEGABYTE(n)
Definition: tools.h:45
#define LOG_ERROR_STR(s)
Definition: tools.h:40
unsigned char uchar
Definition: tools.h:31
#define dsyslog(a...)
Definition: tools.h:37
bool DoubleEqual(double a, double b)
Definition: tools.h:97
T max(T a, T b)
Definition: tools.h:64
#define esyslog(a...)
Definition: tools.h:35