vdr 2.6.8
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.6 2024/01/20 20:04:03 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 new error%s (total %d)\n", *TimeToString(time(NULL)), recordingName, d, d > 1 ? "s" : "", errors);
239 esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", errors);
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) {
337 Recordings->UpdateByName(recordingName);
338 }
339 InfoWritten = true;
342 }
344 firstIframeSeen = true; // start recording with the first I-frame
345 if (!NextFile())
346 break;
347 if (frameDetector->NewFrame()) {
348 if (index)
350 if (frameChecker)
351 frameChecker->CheckFrame(b, Count);
352 }
355 fileSize += TS_SIZE;
356 int Index = 0;
357 while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
358 recordFile->Write(pmt, TS_SIZE);
359 fileSize += TS_SIZE;
360 }
362 }
363 if (recordFile->Write(b, Count) < 0) {
365 break;
366 }
367 HandleErrors();
368 fileSize += Count;
369 }
370 }
371 ringBuffer->Del(Count);
372 }
373 }
374 if (t.TimedOut()) {
376 HandleErrors(true);
377 esyslog("ERROR: video data stream broken");
380 }
381 }
382 HandleErrors(true);
383}
int Vpid(void) const
Definition channels.h:154
int Dpid(int i) const
Definition channels.h:161
int Vtype(void) const
Definition channels.h:156
int Apid(int i) const
Definition channels.h:160
cUnbufferedFile * NextFile(void)
Definition recording.c:3236
uint16_t Number(void)
Definition recording.h:534
cUnbufferedFile * Open(void)
Definition recording.c:3160
const char * Name(void)
Definition recording.h:533
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition recording.c:3109
uint32_t backRefs
Definition recorder.c:88
void CheckFrame(const uchar *Data, int Length)
Definition recorder.c:116
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
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:561
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition remux.h:566
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition remux.h:570
uint16_t FrameWidth(void)
Returns the frame width, or 0 if this information is not available.
Definition remux.h:573
eScanType ScanType(void)
Returns the scan type, or stUnknown if this information is not available.
Definition remux.h:577
uint16_t FrameHeight(void)
Returns the frame height, or 0 if this information is not available.
Definition remux.h:575
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
Definition remux.c:1989
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
Definition remux.h:563
eAspectRatio AspectRatio(void)
Returns the aspect ratio, or arUnknown if this information is not available.
Definition remux.h:579
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
Definition recording.c:2900
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:463
uint16_t FrameHeight(void) const
Definition recording.h:96
int Errors(void) const
Definition recording.h:105
bool Write(FILE *f, const char *Prefix="") const
Definition recording.c:578
bool Read(FILE *f)
Definition recording.c:488
uint16_t FrameWidth(void) const
Definition recording.h:95
void SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio)
Definition recording.c:468
void SetErrors(int Errors)
Definition recording.c:483
eAspectRatio AspectRatio(void) const
Definition recording.h:99
double FramesPerSecond(void) const
Definition recording.h:94
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition recording.c:2491
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:346
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.
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
void Set(int Ms=0)
Sets the timer.
Definition tools.c:797
bool TimedOut(void) const
Definition tools.c:802
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:1978
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:376
#define LOCK_RECORDINGS_WRITE
Definition recording.h:328
#define RUC_STARTRECORDING
Definition recording.h:450
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:469
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:1256
bool SpinUpDisk(const char *FileName)
Definition tools.c:690
#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