vdr  2.4.0
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 4.4 2015/09/12 14:56:15 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);
29 
30  // Make sure the disk is up and running:
31 
32  SpinUpDisk(FileName);
33 
35  ringBuffer->SetTimeouts(0, 100);
37 
38  int Pid = Channel->Vpid();
39  int Type = Channel->Vtype();
40  if (!Pid && Channel->Apid(0)) {
41  Pid = Channel->Apid(0);
42  Type = 0x04;
43  }
44  if (!Pid && Channel->Dpid(0)) {
45  Pid = Channel->Dpid(0);
46  Type = 0x06;
47  }
48  frameDetector = new cFrameDetector(Pid, Type);
49  if ( Type == 0x1B // MPEG4 video
50  && (Setup.DumpNaluFill ? (strstr(FileName, "NALUKEEP") == NULL) : (strstr(FileName, "NALUDUMP") != NULL))) { // MPEG4
51  isyslog("Starting NALU fill dumper");
54  }
55  else
56  naluStreamProcessor = NULL;
57  index = NULL;
58  fileSize = 0;
59  lastDiskSpaceCheck = time(NULL);
60  fileName = new cFileName(FileName, true);
61  int PatVersion, PmtVersion;
62  if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion))
63  patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1);
64  patPmtGenerator.SetChannel(Channel);
66  if (!recordFile)
67  return;
68  // Create the index file:
69  index = new cIndexFile(FileName, true);
70  if (!index)
71  esyslog("ERROR: can't allocate index");
72  // let's continue without index, so we'll at least have the recording
73 }
74 
76 {
77  Detach();
78  if (naluStreamProcessor) {
79  long long int TotalPackets = naluStreamProcessor->GetTotalPackets();
80  long long int DroppedPackets = naluStreamProcessor->GetDroppedPackets();
81  isyslog("NALU fill dumper: %lld of %lld packets dropped, %lli%%", DroppedPackets, TotalPackets, TotalPackets ? DroppedPackets*100/TotalPackets : 0);
82  delete naluStreamProcessor;
83  }
84  delete index;
85  delete fileName;
86  delete frameDetector;
87  delete ringBuffer;
88  free(recordingName);
89 }
90 
92 {
93  if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
94  int Free = FreeDiskSpaceMB(fileName->Name());
95  lastDiskSpaceCheck = time(NULL);
96  if (Free < MINFREEDISKSPACE) {
97  dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
98  return true;
99  }
100  }
101  return false;
102 }
103 
105 {
106  if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
109  fileSize = 0;
110  }
111  }
112  return recordFile != NULL;
113 }
114 
115 void cRecorder::Activate(bool On)
116 {
117  if (On)
118  Start();
119  else
120  Cancel(3);
121 }
122 
123 void cRecorder::Receive(const uchar *Data, int Length)
124 {
125  if (Running()) {
126  static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
127  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
128  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
129  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
130  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
131  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
132  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
133  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
134  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
135  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
136  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
137  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
138  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
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}; // Length is always TS_SIZE!
146  if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
147  return; // Adaptation Field Filler found, skipping
148  int p = ringBuffer->Put(Data, Length);
149  if (p != Length && Running())
150  ringBuffer->ReportOverflow(Length - p);
151  }
152 }
153 
155 {
157  bool InfoWritten = false;
158  bool FirstIframeSeen = false;
159  while (Running()) {
160  int r;
161  uchar *b = ringBuffer->Get(r);
162  if (b) {
163  int Count = frameDetector->Analyze(b, r);
164  if (Count) {
165  if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
166  break;
167  if (frameDetector->Synced()) {
168  if (!InfoWritten) {
169  cRecordingInfo RecordingInfo(recordingName);
170  if (RecordingInfo.Read()) {
173  RecordingInfo.Write();
175  Recordings->UpdateByName(recordingName);
176  }
177  }
178  InfoWritten = true;
180  }
181  if (FirstIframeSeen || frameDetector->IndependentFrame()) {
182  FirstIframeSeen = true; // start recording with the first I-frame
183  if (!NextFile())
184  break;
185  if (index && frameDetector->NewFrame())
189  fileSize += TS_SIZE;
190  int Index = 0;
191  while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
192  recordFile->Write(pmt, TS_SIZE);
193  fileSize += TS_SIZE;
194  }
196  }
197  if (naluStreamProcessor) {
198  naluStreamProcessor->PutBuffer(b, Count);
199  bool Fail = false;
200  while (true) {
201  int OutLength = 0;
202  uchar *OutData = naluStreamProcessor->GetBuffer(OutLength);
203  if (!OutData || OutLength <= 0)
204  break;
205  if (recordFile->Write(OutData, OutLength) < 0) {
207  Fail = true;
208  break;
209  }
210  fileSize += OutLength;
211  }
212  if (Fail)
213  break;
214  }
215  else {
216  if (recordFile->Write(b, Count) < 0) {
218  break;
219  }
220  fileSize += Count;
221  }
222 
223  }
224  }
225  ringBuffer->Del(Count);
226  }
227  }
228  if (t.TimedOut()) {
229  esyslog("ERROR: video data stream broken");
232  }
233  }
234 }
unsigned char uchar
Definition: tools.h:31
void SetFramesPerSecond(double FramesPerSecond)
Definition: recording.c:441
ssize_t Write(const void *Data, size_t Size)
Definition: tools.c:1915
int DumpNaluFill
Definition: config.h:340
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
#define dsyslog(a...)
Definition: tools.h:37
void Set(int Ms=0)
Definition: tools.c:774
#define DEFAULTFRAMESPERSECOND
Definition: recording.h:351
int Dpid(int i) const
Definition: channels.h:161
cFrameDetector * frameDetector
Definition: recorder.h:22
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:2300
void SetPid(int VPid)
Definition: remux.h:618
bool RunningLowOnDiskSpace(void)
Definition: recorder.c:91
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
Definition: remux.c:1688
long long int GetDroppedPackets()
Definition: remux.h:629
#define esyslog(a...)
Definition: tools.h:35
cUnbufferedFile * NextFile(void)
Definition: recording.c:3034
#define LOG_ERROR_STR(s)
Definition: tools.h:40
time_t lastDiskSpaceCheck
Definition: recorder.h:30
void SetChannel(const cChannel *Channel)
Sets the Channel for which the PAT/PMT shall be generated.
Definition: remux.c:621
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
Definition: ringbuffer.c:306
#define MINFREEDISKSPACE
Definition: recorder.c:19
int Vtype(void) const
Definition: channels.h:156
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:2907
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
Definition: remux.h:544
uint16_t Number(void)
Definition: recording.h:501
long long int GetTotalPackets()
Definition: remux.h:628
bool Read(FILE *f)
Definition: recording.c:453
#define LOCK_RECORDINGS_WRITE
Definition: recording.h:307
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:154
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
uchar * GetPmt(int &Index)
Returns a pointer to the Index&#39;th TS packet of the PMT section.
Definition: remux.c:636
cUnbufferedFile * Open(void)
Definition: recording.c:2958
cSetup Setup
Definition: config.c:372
int FreeDiskSpaceMB(const char *Directory, int *UsedMB)
Definition: tools.c:446
cPatPmtGenerator patPmtGenerator
Definition: recorder.h:23
cShutdownHandler ShutdownHandler
Definition: shutdown.c:27
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
Definition: thread.h:101
int Apid(int i) const
Definition: channels.h:160
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
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
Definition: ringbuffer.c:371
double FramesPerSecond(void) const
Definition: recording.h:90
#define DISKCHECKINTERVAL
Definition: recorder.c:20
int MaxVideoFileSize
Definition: config.h:337
cIndexFile * index
Definition: recorder.h:26
const char * Name(void)
Definition: recording.h:500
int Vpid(void) const
Definition: channels.h:154
bool SpinUpDisk(const char *FileName)
Definition: tools.c:667
#define MEGABYTE(n)
Definition: tools.h:45
char * recordingName
Definition: recorder.h:28
void SetIoThrottle(void)
Definition: ringbuffer.c:95
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:509
virtual ~cRecorder()
Definition: recorder.c:75
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame. ...
Definition: remux.h:546
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition: remux.h:549
uchar * Get(int &Count)
Gets data from the ring buffer.
Definition: ringbuffer.c:346
cFileName * fileName
Definition: recorder.h:25
#define isyslog(a...)
Definition: tools.h:36
Definition: thread.h:79
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition: remux.h:553
off_t fileSize
Definition: recorder.h:29
cRingBufferLinear * ringBuffer
Definition: recorder.h:21
Definition: tools.h:369
#define TS_SIZE
Definition: remux.h:34
bool DoubleEqual(double a, double b)
Definition: tools.h:95
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:512
cNaluStreamProcessor * naluStreamProcessor
Definition: recorder.h:24
#define RECORDERBUFSIZE
Definition: recorder.c:13
#define MAXBROKENTIMEOUT
Definition: recorder.c:17
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting &#39;running&#39; to false, so that the Action() loop can finish in an or...
Definition: thread.c:354
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:115
void PutBuffer(uchar *Data, int Length)
Definition: remux.c:2002
#define RUC_STARTRECORDING
Definition: recording.h:421
void Detach(void)
Definition: receiver.c:125
void SetTimeouts(int PutTimeout, int GetTimeout)
Definition: ringbuffer.c:89
void RequestEmergencyExit(void)
Requests an emergency exit of the VDR main loop.
Definition: shutdown.c:93
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
Definition: recording.c:2698
int Priority(void) const
Returns the priority of the current receiving session (-MAXPRIORITY..MAXPRIORITY), or IDLEPRIORITY if no receiver is currently active.
Definition: device.c:1630
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:123
uchar * GetPat(void)
Returns a pointer to the PAT section, which consists of exactly one TS packet.
Definition: remux.c:630
bool NextFile(void)
Definition: recorder.c:104
cUnbufferedFile * recordFile
Definition: recorder.h:27
uchar * GetBuffer(int &OutLength)
Definition: remux.c:2011
void ReportOverflow(int Bytes)
Definition: ringbuffer.c:101
bool TimedOut(void) const
Definition: tools.c:779