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 if ( Type == 0x1B // MPEG4 video
198 && (Setup.DumpNaluFill ? (strstr(FileName, "NALUKEEP") == NULL) : (strstr(FileName, "NALUDUMP") != NULL))) { // MPEG4
199 isyslog("Starting NALU fill dumper");
202 }
203 else
204 naluStreamProcessor = NULL;
205 index = NULL;
206 fileSize = 0;
207 lastDiskSpaceCheck = time(NULL);
208 lastErrorLog = 0;
209 fileName = new cFileName(FileName, true);
210 int PatVersion, PmtVersion;
211 if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion))
212 patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1);
215 if (!recordFile)
216 return;
217 // Create the index file:
218 index = new cIndexFile(FileName, true);
219 if (!index)
220 esyslog("ERROR: can't allocate index");
221 // let's continue without index, so we'll at least have the recording
222}
223
225{
226 Detach();
228 long long int TotalPackets = naluStreamProcessor->GetTotalPackets();
229 long long int DroppedPackets = naluStreamProcessor->GetDroppedPackets();
230 isyslog("NALU fill dumper: %lld of %lld packets dropped, %lli%%", DroppedPackets, TotalPackets, TotalPackets ? DroppedPackets*100/TotalPackets : 0);
231 delete naluStreamProcessor;
232 }
233 delete index;
234 delete fileName;
235 delete frameDetector;
236 delete ringBuffer;
237 delete frameChecker;
238 delete tsChecker;
239 free(recordingName);
240}
241
242#define ERROR_LOG_DELTA 1 // seconds between logging errors
243
245{
246 // We don't log every single error separately, to avoid spamming the log file:
247 if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) {
249 if (errors > lastErrors) {
250 int d = errors - lastErrors;
251 if (DebugChecks)
252 fprintf(stderr, "%s: %s: %d error%s\n", *TimeToString(time(NULL)), recordingName, d, d > 1 ? "s" : "");
253 esyslog("%s: %d error%s", recordingName, d, d > 1 ? "s" : "");
257 Recordings->UpdateByName(recordingName);
258 }
260 lastErrorLog = time(NULL);
261 }
262}
263
265{
266 if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
267 int Free = FreeDiskSpaceMB(fileName->Name());
268 lastDiskSpaceCheck = time(NULL);
269 if (Free < MINFREEDISKSPACE) {
270 dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
271 return true;
272 }
273 }
274 return false;
275}
276
278{
279 if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
282 fileSize = 0;
283 }
284 }
285 return recordFile != NULL;
286}
287
289{
290 if (On)
291 Start();
292 else
293 Cancel(3);
294}
295
296void cRecorder::Receive(const uchar *Data, int Length)
297{
298 if (Running()) {
299 static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
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, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
305 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
306 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
307 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
308 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
309 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
310 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
311 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
312 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
313 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
314 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
315 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
316 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
317 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
318 0xFF, 0xFF}; // Length is always TS_SIZE!
319 if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
320 return; // Adaptation Field Filler found, skipping
321 int p = ringBuffer->Put(Data, Length);
322 if (p != Length && Running())
323 ringBuffer->ReportOverflow(Length - p);
324 else if (firstIframeSeen) // we ignore any garbage before the first I-frame
325 tsChecker->CheckTs(Data, Length);
326 }
327}
328
330{
332 bool InfoWritten = false;
333 while (Running()) {
334 int r;
335 uchar *b = ringBuffer->Get(r);
336 if (b) {
337 int Count = frameDetector->Analyze(b, r);
338 if (Count) {
339 if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
340 break;
341 if (frameDetector->Synced()) {
342 if (!InfoWritten) {
347 Recordings->UpdateByName(recordingName);
348 }
349 InfoWritten = true;
352 }
354 firstIframeSeen = true; // start recording with the first I-frame
355 if (!NextFile())
356 break;
357 if (frameDetector->NewFrame()) {
358 if (index)
360 if (frameChecker)
361 frameChecker->CheckFrame(b, Count);
362 }
365 fileSize += TS_SIZE;
366 int Index = 0;
367 while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
368 recordFile->Write(pmt, TS_SIZE);
369 fileSize += TS_SIZE;
370 }
372 }
375 bool Fail = false;
376 while (true) {
377 int OutLength = 0;
378 uchar *OutData = naluStreamProcessor->GetBuffer(OutLength);
379 if (!OutData || OutLength <= 0)
380 break;
381 if (recordFile->Write(OutData, OutLength) < 0) {
383 Fail = true;
384 break;
385 }
386 fileSize += OutLength;
387 }
388 if (Fail)
389 break;
390 }
391 else {
392 if (recordFile->Write(b, Count) < 0) {
394 break;
395 }
396 fileSize += Count;
397 }
398 HandleErrors();
399 }
400 }
401 ringBuffer->Del(Count);
402 }
403 }
404 if (t.TimedOut()) {
406 HandleErrors(true);
407 esyslog("ERROR: video data stream broken");
410 }
411 }
412 HandleErrors(true);
413}
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
const char * Name(void)
Definition: recording.h:505
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.
Definition: remux.h:544
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition: remux.h:549
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition: remux.h:553
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
Definition: remux.c:1690
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
Definition: remux.h:546
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
Definition: recording.c:2742
void SetPid(int VPid)
Definition: remux.h:618
long long int GetTotalPackets()
Definition: remux.h:628
void PutBuffer(uchar *Data, int Length)
Definition: remux.c:2004
uchar * GetBuffer(int &OutLength)
Definition: remux.c:2013
long long int GetDroppedPackets()
Definition: remux.h:629
uchar * GetPmt(int &Index)
Returns a pointer to the Index'th TS packet of the PMT section.
Definition: remux.c:636
void SetChannel(const cChannel *Channel)
Sets the Channel for which the PAT/PMT shall be generated.
Definition: remux.c:621
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:615
uchar * GetPat(void)
Returns a pointer to the PAT section, which consists of exactly one TS packet.
Definition: remux.c:630
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:277
cFileName * fileName
Definition: recorder.h:30
cIndexFile * index
Definition: recorder.h:32
cTsChecker * tsChecker
Definition: recorder.h:24
time_t lastErrorLog
Definition: recorder.h:38
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:296
void HandleErrors(bool Force=false)
Definition: recorder.c:244
off_t fileSize
Definition: recorder.h:36
cRecordingInfo * recordingInfo
Definition: recorder.h:31
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:329
cFrameDetector * frameDetector
Definition: recorder.h:27
time_t lastDiskSpaceCheck
Definition: recorder.h:37
cUnbufferedFile * recordFile
Definition: recorder.h:33
bool firstIframeSeen
Definition: recorder.h:35
cRingBufferLinear * ringBuffer
Definition: recorder.h:26
char * recordingName
Definition: recorder.h:34
int oldErrors
Definition: recorder.h:39
bool RunningLowOnDiskSpace(void)
Definition: recorder.c:264
int errors
Definition: recorder.h:40
virtual ~cRecorder()
Definition: recorder.c:224
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:288
cFrameChecker * frameChecker
Definition: recorder.h:25
cNaluStreamProcessor * naluStreamProcessor
Definition: recorder.h:29
cPatPmtGenerator patPmtGenerator
Definition: recorder.h:28
int lastErrors
Definition: recorder.h:41
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 DumpNaluFill
Definition: config.h:347
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:242
#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:500
int64_t TsGetPts(const uchar *p, int l)
Definition: remux.c:160
bool TsError(const uchar *p)
Definition: remux.h:82
int TsPid(const uchar *p)
Definition: remux.h:87
bool TsHasPayload(const uchar *p)
Definition: remux.h:62
bool TsIsScrambled(const uchar *p)
Definition: remux.h:98
uchar TsContinuityCounter(const uchar *p)
Definition: remux.h:103
#define TS_SIZE
Definition: remux.h:34
#define PTSTICKS
Definition: remux.h:57
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:509
#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
#define isyslog(a...)
Definition: tools.h:36