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
22static 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
36private:
38 int errors;
39 void Report(int Pid, const char *Message);
40public:
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
52void 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
59void 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
85private:
87 int64_t lastPts;
88 uint32_t backRefs;
90 int errors;
91 void Report(const char *Message, int NumErrors = 1);
92public:
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
109void 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
116void 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
164cRecorder::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);
173 oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
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);
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
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" : "");
243 Recordings->UpdateByName(recordingName);
244 }
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
275{
276 if (On)
277 Start();
278 else
279 Cancel(3);
280}
281
282void 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) {
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)
cUnbufferedFile * Open(void)
Definition: recording.c:3002
const char * Name(void)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:2951
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.
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
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.
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
cUnbufferedFile * recordFile
bool NextFile(void)
Definition: recorder.c:263
cFrameChecker * frameChecker
cFileName * fileName
cIndexFile * index
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
cFrameDetector * frameDetector
void HandleErrors(bool Force=false)
Definition: recorder.c:230
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
time_t lastDiskSpaceCheck
cTsChecker * tsChecker
cRecordingInfo * recordingInfo
cRingBufferLinear * ringBuffer
bool RunningLowOnDiskSpace(void)
Definition: recorder.c:250
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
cPatPmtGenerator patPmtGenerator
void SetFramesPerSecond(double FramesPerSecond)
Definition: recording.c:449
int Errors(void) const
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
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
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.
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
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 DEFAULTFRAMESPERSECOND
#define LOCK_RECORDINGS_WRITE
#define RUC_STARTRECORDING
bool TsError(const uchar *p)
int TsPid(const uchar *p)
bool TsHasPayload(const uchar *p)
int64_t PtsDiff(int64_t Pts1, int64_t Pts2)
Returns the difference between two PTS values.
Definition: remux.c:234
bool TsIsScrambled(const uchar *p)
uchar TsContinuityCounter(const uchar *p)
#define TS_SIZE
#define MAXPID
#define PTSTICKS
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
int64_t TsGetPts(const uchar *p, int l)
Definition: remux.c:160
#define TS_CONT_CNT_MASK
cShutdownHandler ShutdownHandler
Definition: shutdown.c:27
#define MEGABYTE(n)
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 LOG_ERROR_STR(s)
unsigned char uchar
#define dsyslog(a...)
int FreeDiskSpaceMB(const char *Directory, int *UsedMB=NULL)
Definition: tools.c:464
bool DoubleEqual(double a, double b)
T max(T a, T b)
#define esyslog(a...)
#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