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  index = NULL;
50  fileSize = 0;
51  lastDiskSpaceCheck = time(NULL);
52  fileName = new cFileName(FileName, true);
53  int PatVersion, PmtVersion;
54  if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion))
55  patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1);
56  patPmtGenerator.SetChannel(Channel);
58  if (!recordFile)
59  return;
60  // Create the index file:
61  index = new cIndexFile(FileName, true);
62  if (!index)
63  esyslog("ERROR: can't allocate index");
64  // let's continue without index, so we'll at least have the recording
65 }
66 
68 {
69  Detach();
70  delete index;
71  delete fileName;
72  delete frameDetector;
73  delete ringBuffer;
74  free(recordingName);
75 }
76 
78 {
79  if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
80  int Free = FreeDiskSpaceMB(fileName->Name());
81  lastDiskSpaceCheck = time(NULL);
82  if (Free < MINFREEDISKSPACE) {
83  dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
84  return true;
85  }
86  }
87  return false;
88 }
89 
91 {
92  if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
95  fileSize = 0;
96  }
97  }
98  return recordFile != NULL;
99 }
100 
101 void cRecorder::Activate(bool On)
102 {
103  if (On)
104  Start();
105  else
106  Cancel(3);
107 }
108 
109 void cRecorder::Receive(const uchar *Data, int Length)
110 {
111  if (Running()) {
112  static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
113  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
114  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
115  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
116  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
117  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
118  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
119  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
120  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
121  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
122  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
123  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
124  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
125  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
126  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
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}; // Length is always TS_SIZE!
132  if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
133  return; // Adaptation Field Filler found, skipping
134  int p = ringBuffer->Put(Data, Length);
135  if (p != Length && Running())
136  ringBuffer->ReportOverflow(Length - p);
137  }
138 }
139 
141 {
143  bool InfoWritten = false;
144  bool FirstIframeSeen = false;
145  while (Running()) {
146  int r;
147  uchar *b = ringBuffer->Get(r);
148  if (b) {
149  int Count = frameDetector->Analyze(b, r);
150  if (Count) {
151  if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
152  break;
153  if (frameDetector->Synced()) {
154  if (!InfoWritten) {
155  cRecordingInfo RecordingInfo(recordingName);
156  if (RecordingInfo.Read()) {
159  RecordingInfo.Write();
161  Recordings->UpdateByName(recordingName);
162  }
163  }
164  InfoWritten = true;
166  }
167  if (FirstIframeSeen || frameDetector->IndependentFrame()) {
168  FirstIframeSeen = true; // start recording with the first I-frame
169  if (!NextFile())
170  break;
171  if (index && frameDetector->NewFrame())
175  fileSize += TS_SIZE;
176  int Index = 0;
177  while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
178  recordFile->Write(pmt, TS_SIZE);
179  fileSize += TS_SIZE;
180  }
182  }
183  if (recordFile->Write(b, Count) < 0) {
185  break;
186  }
187  fileSize += Count;
188  }
189  }
190  ringBuffer->Del(Count);
191  }
192  }
193  if (t.TimedOut()) {
194  esyslog("ERROR: video data stream broken");
197  }
198  }
199 }
unsigned char uchar
Definition: tools.h:31
void SetFramesPerSecond(double FramesPerSecond)
Definition: recording.c:442
ssize_t Write(const void *Data, size_t Size)
Definition: tools.c:1915
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:579
#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:159
cFrameDetector * frameDetector
Definition: recorder.h:22
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:2301
bool RunningLowOnDiskSpace(void)
Definition: recorder.c:77
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
Definition: remux.c:1652
#define esyslog(a...)
Definition: tools.h:35
cUnbufferedFile * NextFile(void)
Definition: recording.c:3035
#define LOG_ERROR_STR(s)
Definition: tools.h:40
time_t lastDiskSpaceCheck
Definition: recorder.h:29
void SetChannel(const cChannel *Channel)
Sets the Channel for which the PAT/PMT shall be generated.
Definition: remux.c:585
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:154
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:2908
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
Definition: remux.h:538
uint16_t Number(void)
Definition: recording.h:501
bool Read(FILE *f)
Definition: recording.c:454
#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:140
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'th TS packet of the PMT section.
Definition: remux.c:600
cUnbufferedFile * Open(void)
Definition: recording.c:2959
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:158
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:338
cIndexFile * index
Definition: recorder.h:25
const char * Name(void)
Definition: recording.h:500
int Vpid(void) const
Definition: channels.h:152
bool SpinUpDisk(const char *FileName)
Definition: tools.c:667
#define MEGABYTE(n)
Definition: tools.h:45
char * recordingName
Definition: recorder.h:27
void SetIoThrottle(void)
Definition: ringbuffer.c:95
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:503
virtual ~cRecorder()
Definition: recorder.c:67
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
Definition: remux.h:540
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition: remux.h:543
uchar * Get(int &Count)
Gets data from the ring buffer.
Definition: ringbuffer.c:346
cFileName * fileName
Definition: recorder.h:24
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:547
off_t fileSize
Definition: recorder.h:28
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:513
#define RECORDERBUFSIZE
Definition: recorder.c:13
#define MAXBROKENTIMEOUT
Definition: recorder.c:17
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
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:101
#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:2699
int Priority(void) const
Returns the priority of the current receiving session (-MAXPRIORITY..MAXPRIORITY),...
Definition: device.c:1632
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:109
uchar * GetPat(void)
Returns a pointer to the PAT section, which consists of exactly one TS packet.
Definition: remux.c:594
bool NextFile(void)
Definition: recorder.c:90
cUnbufferedFile * recordFile
Definition: recorder.h:26
void ReportOverflow(int Bytes)
Definition: ringbuffer.c:101
bool TimedOut(void) const
Definition: tools.c:779