vdr 2.7.3
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
24cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
25:cReceiver(Channel, Priority)
26,cThread("recording")
27{
28 recordingName = strdup(FileName);
30 recordingInfo->Read();
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);
42 ringBuffer->SetIoThrottle();
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);
64 recordFile = fileName->Open();
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
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);
93 recordingInfo->SetErrors(oldErrors + errors);
94 recordingInfo->Write();
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
119 if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) {
120 recordFile = fileName->NextFile();
121 fileSize = 0;
122 }
123 }
124 return recordFile != NULL;
125}
126
128{
129 if (On)
130 Start();
131 else
132 Cancel(3);
133}
134
135void 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)
178 frameDetector->SetMissing();
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) {
189 if ((frameDetector->FramesPerSecond() > 0 && DoubleEqual(recordingInfo->FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(recordingInfo->FramesPerSecond(), frameDetector->FramesPerSecond())) ||
190 frameDetector->FrameWidth() != recordingInfo->FrameWidth() ||
191 frameDetector->FrameHeight() != recordingInfo->FrameHeight() ||
192 frameDetector->AspectRatio() != recordingInfo->AspectRatio()) {
193 recordingInfo->SetFramesPerSecond(frameDetector->FramesPerSecond());
194 recordingInfo->SetFrameParams(frameDetector->FrameWidth(), frameDetector->FrameHeight(), frameDetector->ScanType(), frameDetector->AspectRatio());
195 recordingInfo->Write();
197 Recordings->UpdateByName(recordingName);
198 }
199 InfoWritten = true;
201 }
202 if (firstIframeSeen || frameDetector->IndependentFrame()) {
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 }
223 if (frameDetector->IndependentFrame()) {
224 recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE);
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) {
234 LOG_ERROR_STR(fileName->Name());
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);
247 frameDetector->SetMissing();
248 errors += MAXBROKENTIMEOUT / 1000 * frameDetector->FramesPerSecond();
249 HandleErrors(true);
250 esyslog("ERROR: video data stream broken");
251 ShutdownHandler.RequestEmergencyExit();
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: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
int Priority(void)
Definition receiver.h:57
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:116
cFileName * fileName
Definition recorder.h:24
cIndexFile * index
Definition recorder.h:26
time_t lastErrorLog
Definition recorder.h:32
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:135
void HandleErrors(bool Force=false)
Definition recorder.c:86
off_t fileSize
Definition recorder.h:30
cRecordingInfo * recordingInfo
Definition recorder.h:25
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:166
cFrameDetector * frameDetector
Definition recorder.h:22
time_t lastDiskSpaceCheck
Definition recorder.h:31
cUnbufferedFile * recordFile
Definition recorder.h:27
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()
Definition recorder.c:74
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:127
cPatPmtGenerator patPmtGenerator
Definition recorder.h:23
int lastErrors
Definition recorder.h:35
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition recording.c:2495
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
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
Definition thread.c:238
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:805
bool TimedOut(void) const
Definition tools.c:810
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:376
#define LOCK_RECORDINGS_WRITE
Definition recording.h:328
#define RUC_STARTRECORDING
Definition recording.h:453
#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:469
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