vdr  2.7.6
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.10 2024/09/19 09:49:02 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 // --- cRecorder -------------------------------------------------------------
23 
24 cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
25 :cReceiver(Channel, Priority)
26 ,cThread("recording")
27 {
28  recordingName = strdup(FileName);
31  oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
32  errors = 0;
33  lastErrors = 0;
34  firstIframeSeen = false;
35 
36  // Make sure the disk is up and running:
37 
38  SpinUpDisk(FileName);
39 
41  ringBuffer->SetTimeouts(0, 100);
43 
44  int Pid = Channel->Vpid();
45  int Type = Channel->Vtype();
46  if (!Pid && Channel->Apid(0)) {
47  Pid = Channel->Apid(0);
48  Type = 0x04;
49  }
50  if (!Pid && Channel->Dpid(0)) {
51  Pid = Channel->Dpid(0);
52  Type = 0x06;
53  }
54  frameDetector = new cFrameDetector(Pid, Type);
55  index = NULL;
56  fileSize = 0;
57  lastDiskSpaceCheck = time(NULL);
58  lastErrorLog = 0;
59  fileName = new cFileName(FileName, true);
60  int PatVersion, PmtVersion;
61  if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion))
62  patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1);
63  patPmtGenerator.SetChannel(Channel);
65  if (!recordFile)
66  return;
67  // Create the index file:
68  index = new cIndexFile(FileName, true);
69  if (!index)
70  esyslog("ERROR: can't allocate index");
71  // let's continue without index, so we'll at least have the recording
72 }
73 
75 {
76  Detach();
77  delete index;
78  delete fileName;
79  delete frameDetector;
80  delete ringBuffer;
81  free(recordingName);
82 }
83 
84 #define ERROR_LOG_DELTA 1 // seconds between logging errors
85 
86 void cRecorder::HandleErrors(bool Force)
87 {
88  // We don't log every single error separately, to avoid spamming the log file:
89  if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) {
90  if (errors > lastErrors) {
91  int d = errors - lastErrors;
92  esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", oldErrors + errors);
96  Recordings->UpdateByName(recordingName);
98  }
99  lastErrorLog = time(NULL);
100  }
101 }
102 
104 {
105  if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
106  int Free = FreeDiskSpaceMB(fileName->Name());
107  lastDiskSpaceCheck = time(NULL);
108  if (Free < MINFREEDISKSPACE) {
109  dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
110  return true;
111  }
112  }
113  return false;
114 }
115 
117 {
118  if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
121  fileSize = 0;
122  }
123  }
124  return recordFile != NULL;
125 }
126 
127 void cRecorder::Activate(bool On)
128 {
129  if (On)
130  Start();
131  else
132  Cancel(3);
133 }
134 
135 void cRecorder::Receive(const uchar *Data, int Length)
136 {
137  if (Running()) {
138  static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
139  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
140  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
141  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
142  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
143  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
144  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
145  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
146  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
147  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
148  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
149  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
150  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
151  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
152  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
153  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
154  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
155  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
156  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
157  0xFF, 0xFF}; // Length is always TS_SIZE!
158  if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
159  return; // Adaptation Field Filler found, skipping
160  int p = ringBuffer->Put(Data, Length);
161  if (p != Length && Running())
162  ringBuffer->ReportOverflow(Length - p);
163  }
164 }
165 
167 {
169  bool InfoWritten = false;
170  bool pendIndependentFrame = false;
171  uint16_t pendNumber = 0;
172  off_t pendFileSize = 0;
173  bool pendErrors = false;
174  bool pendMissing = false;
175  // Check if this is a resumed recording, in which case we definitely missed frames:
176  NextFile();
177  if (fileName->Number() > 1 || oldErrors)
179  while (Running()) {
180  int r;
181  uchar *b = ringBuffer->Get(r);
182  if (b) {
183  int Count = frameDetector->Analyze(b, r);
184  if (Count) {
185  if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
186  break;
187  if (frameDetector->Synced()) {
188  if (!InfoWritten) {
195  recordingInfo->Write();
197  Recordings->UpdateByName(recordingName);
198  }
199  InfoWritten = true;
201  }
203  firstIframeSeen = true; // start recording with the first I-frame
204  if (!NextFile())
205  break;
206  int PreviousErrors = 0;
207  int MissingFrames = 0;
208  if (frameDetector->NewFrame(&PreviousErrors, &MissingFrames)) {
209  if (index) {
210  if (pendNumber > 0)
211  index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
212  pendIndependentFrame = frameDetector->IndependentFrame();
213  pendNumber = fileName->Number();
214  pendFileSize = fileSize;
215  pendErrors = PreviousErrors;
216  pendMissing = MissingFrames;
217  }
218  if (PreviousErrors)
219  errors++;
220  if (MissingFrames)
221  errors++;
222  }
225  fileSize += TS_SIZE;
226  int Index = 0;
227  while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
228  recordFile->Write(pmt, TS_SIZE);
229  fileSize += TS_SIZE;
230  }
232  }
233  if (recordFile->Write(b, Count) < 0) {
235  break;
236  }
237  HandleErrors();
238  fileSize += Count;
239  }
240  }
241  ringBuffer->Del(Count);
242  }
243  }
244  if (t.TimedOut()) {
245  if (pendNumber > 0)
246  index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
249  HandleErrors(true);
250  esyslog("ERROR: video data stream broken");
253  }
254  }
255  if (pendNumber > 0)
256  index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
257  HandleErrors(true);
258 }
int Vpid(void) const
Definition: channels.h:156
int Dpid(int i) const
Definition: channels.h:163
int Vtype(void) const
Definition: channels.h:158
int Apid(int i) const
Definition: channels.h:162
cUnbufferedFile * NextFile(void)
Definition: recording.c:3300
uint16_t Number(void)
Definition: recording.h:543
cUnbufferedFile * Open(void)
Definition: recording.c:3224
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:3173
const char * Name(void)
Definition: recording.h:542
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
Definition: remux.h:571
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition: remux.h:580
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition: remux.h:584
uint16_t FrameWidth(void)
Returns the frame width, or 0 if this information is not available.
Definition: remux.h:587
eScanType ScanType(void)
Returns the scan type, or stUnknown if this information is not available.
Definition: remux.h:591
bool NewFrame(int *PreviousErrors=NULL, int *MissingFrames=NULL)
Returns true if the data given to the last call to Analyze() started a new frame.
Definition: remux.c:2160
int Analyze(const uchar *Data, int Length, bool ErrorCheck=true)
Analyzes the TS packets pointed to by Data.
Definition: remux.c:2171
uint16_t FrameHeight(void)
Returns the frame height, or 0 if this information is not available.
Definition: remux.h:589
void SetMissing(void)
Call if this is a resumed recording, which has missing frames.
Definition: remux.c:2155
eAspectRatio AspectRatio(void)
Returns the aspect ratio, or arUnknown if this information is not available.
Definition: remux.h:593
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset, bool Errors=false, bool Missing=false)
Definition: recording.c:2949
uchar * GetPmt(int &Index)
Returns a pointer to the Index'th TS packet of the PMT section.
Definition: remux.c:602
void SetChannel(const cChannel *Channel)
Sets the Channel for which the PAT/PMT shall be generated.
Definition: remux.c:587
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:581
uchar * GetPat(void)
Returns a pointer to the PAT section, which consists of exactly one TS packet.
Definition: remux.c:596
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:24
bool NextFile(void)
Definition: recorder.c:116
cFileName * fileName
Definition: recorder.h:24
cIndexFile * index
Definition: recorder.h:26
virtual void Receive(const uchar *Data, int Length) override
This function is called from the cDevice we are attached to, and delivers one TS packet from the set ...
Definition: recorder.c:135
time_t lastErrorLog
Definition: recorder.h:32
virtual void Activate(bool On) override
If you override Activate() you need to call Detach() (which is a member of the cReceiver class) from ...
Definition: recorder.c:127
void HandleErrors(bool Force=false)
Definition: recorder.c:86
off_t fileSize
Definition: recorder.h:30
cRecordingInfo * recordingInfo
Definition: recorder.h:25
cFrameDetector * frameDetector
Definition: recorder.h:22
time_t lastDiskSpaceCheck
Definition: recorder.h:31
cUnbufferedFile * recordFile
Definition: recorder.h:27
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: recorder.c:166
bool firstIframeSeen
Definition: recorder.h:29
cRingBufferLinear * ringBuffer
Definition: recorder.h:21
char * recordingName
Definition: recorder.h:28
int oldErrors
Definition: recorder.h:33
bool RunningLowOnDiskSpace(void)
Definition: recorder.c:103
int errors
Definition: recorder.h:34
virtual ~cRecorder() override
Definition: recorder.c:74
cPatPmtGenerator patPmtGenerator
Definition: recorder.h:23
int lastErrors
Definition: recorder.h:35
void SetFramesPerSecond(double FramesPerSecond)
Definition: recording.c:465
uint16_t FrameHeight(void) const
Definition: recording.h:99
int Errors(void) const
Definition: recording.h:110
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:600
uint16_t FrameWidth(void) const
Definition: recording.h:98
void SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio)
Definition: recording.c:480
void SetErrors(int Errors)
Definition: recording.c:495
eAspectRatio AspectRatio(void) const
Definition: recording.h:102
bool Read(FILE *f, bool Force=false)
Definition: recording.c:500
double FramesPerSecond(void) const
Definition: recording.h:95
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:2512
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:354
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:305
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:355
Definition: tools.h:404
void Set(int Ms=0)
Sets the timer.
Definition: tools.c:808
bool TimedOut(void) const
Definition: tools.c:813
ssize_t Write(const void *Data, size_t Size)
Definition: tools.c:1936
cSetup Setup
Definition: config.c:372
#define MAXBROKENTIMEOUT
Definition: recorder.c:17
#define DISKCHECKINTERVAL
Definition: recorder.c:20
#define ERROR_LOG_DELTA
Definition: recorder.c:84
#define MINFREEDISKSPACE
Definition: recorder.c:19
#define RECORDERBUFSIZE
Definition: recorder.c:13
#define DEFAULTFRAMESPERSECOND
Definition: recording.h:378
#define LOCK_RECORDINGS_WRITE
Definition: recording.h:330
#define RUC_STARTRECORDING
Definition: recording.h:455
#define TS_SIZE
Definition: remux.h:34
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:503
cShutdownHandler ShutdownHandler
Definition: shutdown.c:27
int FreeDiskSpaceMB(const char *Directory, int *UsedMB)
Definition: tools.c:472
bool SpinUpDisk(const char *FileName)
Definition: tools.c:693
#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