vdr 2.8.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.15 2026/10/20 20:18:47 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
24cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
25:cReceiver(Channel, Priority)
26,cThread("recording")
27{
28 recordingName = strdup(FileName);
32 oldErrors = max(0, recordingInfo->Errors()) - tmpErrors; // in case this is a re-started recording
33 errors = 0;
35 working = false;
36 firstIframeSeen = false;
37
38 // Make sure the disk is up and running:
39
40 SpinUpDisk(FileName);
41
43 ringBuffer->SetTimeouts(0, 100);
45
46 int Pid = Channel->Vpid();
47 int Type = Channel->Vtype();
48 if (!Pid && Channel->Apid(0)) {
49 Pid = Channel->Apid(0);
50 Type = 0x04;
51 }
52 if (!Pid && Channel->Dpid(0)) {
53 Pid = Channel->Dpid(0);
54 Type = 0x06;
55 }
56 frameDetector = new cFrameDetector(Pid, Type);
57 index = NULL;
58 fileSize = 0;
59 lastDiskSpaceCheck = time(NULL);
60 lastErrorLog = 0;
61 fileName = new cFileName(FileName, true);
62 // Check if this is a resumed recording, in which case we definitely missed frames:
63 NextFile();
64 if (fileName->Number() > 1 || oldErrors)
68 if (!recordFile)
69 return;
70 // Create the index file:
71 index = new cIndexFile(FileName, true);
72 if (!index)
73 esyslog("ERROR: can't allocate index");
74 // let's continue without index, so we'll at least have the recording
75}
76
78{
79 Cancel(3); // in case the caller didn't call Stop()
80 Detach();
81 delete index;
82 delete fileName;
83 delete frameDetector;
84 delete ringBuffer;
85 delete recordingInfo;
86 free(recordingName);
87}
88
90{
91 Cancel(3);
92}
93
94#define ERROR_LOG_DELTA 1 // seconds between logging errors
95
97{
98 // We don't log every single error separately, to avoid spamming the log file:
99 if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) {
100 int AllErrors = oldErrors + errors + tmpErrors;
101 if (AllErrors != lastErrors) {
102 int d = AllErrors - lastErrors;
103 esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", AllErrors);
104 recordingInfo->SetErrors(AllErrors, tmpErrors);
107 Recordings->UpdateByName(recordingName);
108 lastErrors = AllErrors;
109 }
110 lastErrorLog = time(NULL);
111 }
112}
113
115{
116 if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
117 int Free = FreeDiskSpaceMB(fileName->Name());
118 lastDiskSpaceCheck = time(NULL);
119 if (Free < MINFREEDISKSPACE) {
120 dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
121 return true;
122 }
123 }
124 return false;
125}
126
128{
129 if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
132 fileSize = 0;
133 }
134 }
135 return recordFile != NULL;
136}
137
139{
140 if (On)
141 Start();
142 else
143 Cancel(3);
144}
145
146void cRecorder::Receive(const uchar *Data, int Length)
147{
148 if (working) {
149 static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
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, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
158 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
159 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
160 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
161 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
162 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
163 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
164 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
165 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
166 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
167 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
168 0xFF, 0xFF}; // Length is always TS_SIZE!
169 if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
170 return; // Adaptation Field Filler found, skipping
171 int p = ringBuffer->Put(Data, Length);
172 if (p != Length && working)
173 ringBuffer->ReportOverflow(Length - p);
174 }
175}
176
177#define MIN_IFRAMES_FOR_LAST_PTS 2
178
179void cRecorder::GetLastPts(const char *RecordingName)
180{
181 dsyslog("getting last PTS of '%s'", RecordingName);
182 if (cIndexFile *Index = new cIndexFile(RecordingName, false)) {
183 cFileName *FileName = new cFileName(RecordingName, false);
184 uint16_t FileNumber;
185 off_t FileOffset;
186 bool Independent;
187 uchar Buffer[MAXFRAMESIZE];
188 int Length;
189 int64_t LastPts = -1;
190 int IframesSeen = 0;
191 cPatPmtParser PatPmtParser;
192 bool GotPatPmtVersions = false;
193 for (int i = Index->Last(); i >= 0; i--) {
194 if (Index->Get(i, &FileNumber, &FileOffset, &Independent, &Length)) {
195 if (cUnbufferedFile *f = FileName->SetOffset(FileNumber, FileOffset)) {
196 int l = ReadFrame(f, Buffer, Length, sizeof(Buffer));
197 if (l > 0) {
198 int64_t Pts = TsGetPts(Buffer, l);
199 if (LastPts < 0 || PtsDiff(LastPts, Pts) > 0) {
200 LastPts = Pts;
201 IframesSeen = 0;
202 }
203 if (Independent) {
204 if (!GotPatPmtVersions && PatPmtParser.ParsePatPmt(Buffer, l)) {
205 int PatVersion;
206 int PmtVersion;
207 if (PatPmtParser.GetVersions(PatVersion, PmtVersion)) {
208 patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1);
209 GotPatPmtVersions = true;
210 }
211 }
212 if (++IframesSeen >= MIN_IFRAMES_FOR_LAST_PTS)
213 break;
214 }
215 }
216 else
217 break;
218 }
219 else
220 break;
221 }
222 else
223 break;
224 }
225 frameDetector->SetLastPts(LastPts);
226 delete FileName;
227 delete Index;
228 }
229}
230
232{
233//#define TEST_VDSB 40000 // 40000 to test VDSB without restart, 70000 for VDSB with restart
234#ifdef TEST_VDSB
235 cTimeMs VdsbTimer;
236#endif
238 bool InfoWritten = false;
239 bool pendIndependentFrame = false;
240 uint16_t pendNumber = 0;
241 off_t pendFileSize = 0;
242 bool pendMissing = false;
243 int NumIframesSeen = 0;
244 cTimeMs StopTimeout;
245 bool Stopping = false;
246 working = true;
247 while (true) {
248#ifdef TEST_VDSB
249 int Vdsb = VdsbTimer.Elapsed();
250 if (Vdsb > 30000 && Vdsb < TEST_VDSB) {
251 working = false;
253 }
254 working = true;
255#endif
256 int r;
257 uchar *b = ringBuffer->Get(r);
258 if (b) {
259 int Count = frameDetector->Analyze(b, r);
260 if (Count) {
261 if (!Running() && frameDetector->IndependentFrame()) { // finish the recording before the next independent frame
262 working = false;
263 break;
264 }
265 if (frameDetector->Synced()) {
266 if (!InfoWritten) {
275 Recordings->UpdateByName(recordingName);
276 }
277 InfoWritten = true;
279 }
281 firstIframeSeen = true; // start recording with the first I-frame
282 if (!NextFile())
283 break;
284 bool PreviousErrors = false;
285 bool MissingFrames = false;
286 if (frameDetector->NewFrame(PreviousErrors, MissingFrames)) {
287 if (index) {
288 if (pendNumber > 0)
289 index->Write(pendIndependentFrame, pendNumber, pendFileSize, PreviousErrors, pendMissing);
290 pendIndependentFrame = frameDetector->IndependentFrame();
291 pendNumber = fileName->Number();
292 pendFileSize = fileSize;
293 pendMissing = MissingFrames;
294 }
296 }
298 NumIframesSeen++;
299 tmpErrors = 0;
301 fileSize += TS_SIZE;
302 int Index = 0;
303 while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
304 recordFile->Write(pmt, TS_SIZE);
305 fileSize += TS_SIZE;
306 }
307 t.Reset();
308 }
309 if (recordFile->Write(b, Count) < 0) {
311 break;
312 }
313 if (NumIframesSeen >= 2) // avoids extra log entry when resuming a recording
314 HandleErrors();
315 fileSize += Count;
316 }
317 }
318 ringBuffer->Del(Count);
319 }
320 }
321 if (t.TimedOut()) {
322 esyslog("ERROR: video data stream broken");
323 tmpErrors += int(round(frameDetector->FramesPerSecond() * t.Elapsed() / 1000));
324 if (pendNumber > 0) {
325 bool PreviousErrors = false;
326 errors = frameDetector->Errors(&PreviousErrors);
327 index->Write(pendIndependentFrame, pendNumber, pendFileSize, PreviousErrors, pendMissing);
328 pendNumber = 0;
329 }
330 HandleErrors(true);
332 t.Reset();
333 }
334 if (!Running()) {
335 if (!Stopping) { // wait for the next independent frame, but less than Cancel(3) does - without video it never comes
336 Stopping = true;
337 StopTimeout.Set(2000);
338 }
339 if (StopTimeout.TimedOut() || ShutdownHandler.EmergencyExitRequested())
340 break;
341 }
342 }
343 // Estimate the number of missing frames in case the data stream was broken, but the timer
344 // didn't reach the timeout, yet:
345 if (working) {
346 int dt = t.Elapsed();
347 if (dt > 0)
348 tmpErrors += int(round(frameDetector->FramesPerSecond() * dt / 1000));
349 }
350 if (pendNumber > 0) {
351 bool PreviousErrors = false;
352 errors = frameDetector->Errors(&PreviousErrors);
353 index->Write(pendIndependentFrame, pendNumber, pendFileSize, PreviousErrors, pendMissing);
354 }
355 HandleErrors(true);
356}
int Vpid(void) const
Definition channels.h:155
int Dpid(int i) const
Definition channels.h:162
int Vtype(void) const
Definition channels.h:157
int Apid(int i) const
Definition channels.h:161
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition thread.c:73
cUnbufferedFile * NextFile(void)
Definition recording.c:3463
uint16_t Number(void)
Definition recording.h:577
cUnbufferedFile * Open(void)
Definition recording.c:3387
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
Definition recording.c:3312
const char * Name(void)
Definition recording.h:576
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
Definition recording.c:3421
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
Definition remux.h:607
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition remux.h:617
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition remux.h:621
uint16_t FrameWidth(void)
Returns the frame width, or 0 if this information is not available.
Definition remux.h:624
int Errors(bool *PreviousErrors=NULL, bool *MissingFrames=NULL)
Returns the total number of errors so far.
Definition remux.c:2261
eScanType ScanType(void)
Returns the scan type, or stUnknown if this information is not available.
Definition remux.h:628
bool NewFrame(int *PreviousErrors=NULL, int *MissingFrames=NULL)
Definition remux.c:2272
int Analyze(const uchar *Data, int Length, bool ErrorCheck=true)
Analyzes the TS packets pointed to by Data.
Definition remux.c:2293
uint16_t FrameHeight(void)
Returns the frame height, or 0 if this information is not available.
Definition remux.h:626
cFrameDetector(int Pid=0, int Type=0)
Sets up a frame detector for the given Pid and stream Type.
Definition remux.c:2199
void SetLastPts(int64_t LastPts)
If this is a resumed recording, call this function with the last PTS of the previous recording.
Definition remux.c:2255
eAspectRatio AspectRatio(void)
Returns the aspect ratio, or arUnknown if this information is not available.
Definition remux.h:630
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset, bool Errors=false, bool Missing=false)
Definition recording.c:3107
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false)
Definition recording.c:2915
uchar * GetPmt(int &Index)
Returns a pointer to the Index'th TS packet of the PMT section.
Definition remux.c:599
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:593
bool GetVersions(int &PatVersion, int &PmtVersion) const
Returns true if a valid PAT/PMT has been parsed and stores the current version numbers in the given v...
Definition remux.c:959
bool ParsePatPmt(const uchar *Data, int Length)
Parses the given Data (which may consist of several TS packets, typically an entire frame) and extrac...
Definition remux.c:940
int Priority(void)
Definition receiver.h:58
void Detach(void)
Definition receiver.c:125
cReceiver(const cChannel *Channel=NULL, int Priority=MINPRIORITY)
Creates a new receiver for the given Channel with the given Priority.
Definition receiver.c:14
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:127
cFileName * fileName
Definition recorder.h:25
cIndexFile * index
Definition recorder.h:27
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:146
time_t lastErrorLog
Definition recorder.h:34
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:138
std::atomic< bool > working
Definition recorder.h:30
void HandleErrors(bool Force=false)
Definition recorder.c:96
void GetLastPts(const char *RecordingName)
Definition recorder.c:179
void Stop(void)
Stops the recorder.
Definition recorder.c:89
off_t fileSize
Definition recorder.h:32
cRecordingInfo * recordingInfo
Definition recorder.h:26
cFrameDetector * frameDetector
Definition recorder.h:23
time_t lastDiskSpaceCheck
Definition recorder.h:33
cUnbufferedFile * recordFile
Definition recorder.h:28
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:231
bool firstIframeSeen
Definition recorder.h:31
cRingBufferLinear * ringBuffer
Definition recorder.h:22
int tmpErrors
Definition recorder.h:36
char * recordingName
Definition recorder.h:29
int oldErrors
Definition recorder.h:35
bool RunningLowOnDiskSpace(void)
Definition recorder.c:114
int errors
Definition recorder.h:37
virtual ~cRecorder() override
Definition recorder.c:77
cPatPmtGenerator patPmtGenerator
Definition recorder.h:24
int lastErrors
Definition recorder.h:38
void SetFramesPerSecond(double FramesPerSecond)
Definition recording.c:499
int TmpErrors(void) const
Definition recording.h:124
uint16_t FrameHeight(void) const
Definition recording.h:108
int Errors(void) const
Definition recording.h:123
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
Definition recording.c:390
bool Write(FILE *f, const char *Prefix="") const
Definition recording.c:659
uint16_t FrameWidth(void) const
Definition recording.h:107
void SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio)
Definition recording.c:534
eAspectRatio AspectRatio(void) const
Definition recording.h:111
bool Read(FILE *f, bool Force=false)
Definition recording.c:555
void SetErrors(int Errors, int TmpErrors=0)
Definition recording.c:549
double FramesPerSecond(void) const
Definition recording.h:103
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition recording.c:2674
cRingBufferLinear(int Size, int Margin=0, bool Statistics=false, const char *Description=NULL)
Creates a linear ring buffer.
Definition ringbuffer.c:170
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:355
bool EmergencyExitRequested(void)
Returns true if an emergency exit was requested.
Definition shutdown.h:61
void RequestEmergencyExit(void)
Requests an emergency exit of the VDR main loop.
Definition shutdown.c:104
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:330
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
Definition thread.h:110
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
Definition thread.c:264
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:381
uint64_t Elapsed(void) const
Returns the number of milliseconds that have elapsed since the last call to Set().
Definition tools.c:855
void Set(int Ms=0)
Sets the timer.
Definition tools.c:842
bool TimedOut(void) const
Returns true if the number of milliseconds given in the last call to Set() have passed.
Definition tools.c:850
void Reset(void)
Resets the timer to the same timeout as given in the last call to Set().
Definition tools.c:865
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
Definition tools.h:513
ssize_t Write(const void *Data, size_t Size)
Definition tools.c:1987
cSetup Setup
Definition config.c:373
#define MAXBROKENTIMEOUT
Definition recorder.c:17
#define DISKCHECKINTERVAL
Definition recorder.c:20
#define MIN_IFRAMES_FOR_LAST_PTS
Definition recorder.c:177
#define ERROR_LOG_DELTA
Definition recorder.c:94
#define MINFREEDISKSPACE
Definition recorder.c:19
#define RECORDERBUFSIZE
Definition recorder.c:13
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
Definition recording.c:3597
#define DEFAULTFRAMESPERSECOND
Definition recording.h:409
#define LOCK_RECORDINGS_WRITE
Definition recording.h:354
#define RUC_STARTRECORDING
Definition recording.h:486
#define MAXFRAMESIZE
Definition recording.h:505
int64_t PtsDiff(int64_t Pts1, int64_t Pts2)
Returns the difference between two PTS values.
Definition remux.c:236
int64_t TsGetPts(const uchar *p, int l, int Pid)
Definition remux.c:160
#define TS_SIZE
Definition remux.h:34
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition remux.h:507
cShutdownHandler ShutdownHandler
Definition shutdown.c:27
int FreeDiskSpaceMB(const char *Directory, int *UsedMB)
Definition tools.c:507
bool SpinUpDisk(const char *FileName)
Definition tools.c:728
#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