vdr 2.6.4
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:3092
uint16_t Number(void)
Definition recording.h:506
cUnbufferedFile * Open(void)
Definition recording.c:3016
const char * Name(void)
Definition recording.h:505
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition recording.c:2965
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: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:1993
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:2756
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:2312
uchar * GetBuffer(int &OutLength)
Definition remux.c:2321
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:456
int Errors(void) const
Definition recording.h:92
bool Write(FILE *f, const char *Prefix="") const
Definition recording.c:534
bool Read(FILE *f)
Definition recording.c:473
void SetErrors(int Errors)
Definition recording.c:468
double FramesPerSecond(void) const
Definition recording.h:89
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition recording.c:2351
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
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: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