vdr  2.2.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 3.3 2014/02/21 09:19:52 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(uchar *Data, int Length)
124 {
125  if (Running()) {
126  int p = ringBuffer->Put(Data, Length);
127  if (p != Length && Running())
128  ringBuffer->ReportOverflow(Length - p);
129  }
130 }
131 
133 {
135  bool InfoWritten = false;
136  bool FirstIframeSeen = false;
137  while (Running()) {
138  int r;
139  uchar *b = ringBuffer->Get(r);
140  if (b) {
141  int Count = frameDetector->Analyze(b, r);
142  if (Count) {
143  if (!Running() && frameDetector->IndependentFrame()) // finish the recording before the next independent frame
144  break;
145  if (frameDetector->Synced()) {
146  if (!InfoWritten) {
147  cRecordingInfo RecordingInfo(recordingName);
148  if (RecordingInfo.Read()) {
151  RecordingInfo.Write();
153  }
154  }
155  InfoWritten = true;
157  }
158  if (FirstIframeSeen || frameDetector->IndependentFrame()) {
159  FirstIframeSeen = true; // start recording with the first I-frame
160  if (!NextFile())
161  break;
162  if (index && frameDetector->NewFrame())
166  fileSize += TS_SIZE;
167  int Index = 0;
168  while (uchar *pmt = patPmtGenerator.GetPmt(Index)) {
169  recordFile->Write(pmt, TS_SIZE);
170  fileSize += TS_SIZE;
171  }
173  }
174  if (naluStreamProcessor) {
175  naluStreamProcessor->PutBuffer(b, Count);
176  bool Fail = false;
177  while (true) {
178  int OutLength = 0;
179  uchar *OutData = naluStreamProcessor->GetBuffer(OutLength);
180  if (!OutData || OutLength <= 0)
181  break;
182  if (recordFile->Write(OutData, OutLength) < 0) {
184  Fail = true;
185  break;
186  }
187  fileSize += OutLength;
188  }
189  if (Fail)
190  break;
191  }
192  else {
193  if (recordFile->Write(b, Count) < 0) {
195  break;
196  }
197  fileSize += Count;
198  }
199 
200  }
201  }
202  ringBuffer->Del(Count);
203  }
204  }
205  if (t.TimedOut()) {
206  esyslog("ERROR: video data stream broken");
209  }
210  }
211 }
unsigned char uchar
Definition: tools.h:30
void SetFramesPerSecond(double FramesPerSecond)
Definition: recording.c:439
int Vpid(void) const
Definition: channels.h:170
ssize_t Write(const void *Data, size_t Size)
Definition: tools.c:1832
int DumpNaluFill
Definition: config.h:336
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:630
#define dsyslog(a...)
Definition: tools.h:36
void Set(int Ms=0)
Definition: tools.c:738
#define DEFAULTFRAMESPERSECOND
Definition: recording.h:333
cFrameDetector * frameDetector
Definition: recorder.h:22
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:2231
void SetPid(int VPid)
Definition: remux.h:597
int Dpid(int i) const
Definition: channels.h:177
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:1561
long long int GetDroppedPackets()
Definition: remux.h:608
#define esyslog(a...)
Definition: tools.h:34
cUnbufferedFile * NextFile(void)
Definition: recording.c:2964
#define LOG_ERROR_STR(s)
Definition: tools.h:39
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:636
double FramesPerSecond(void) const
Definition: recording.h:90
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
Definition: ringbuffer.c:305
#define MINFREEDISKSPACE
Definition: recorder.c:19
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:2837
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
Definition: remux.h:523
uint16_t Number(void)
Definition: recording.h:475
long long int GetTotalPackets()
Definition: remux.h:607
bool Read(FILE *f)
Definition: recording.c:451
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:510
bool TimedOut(void) const
Definition: tools.c:743
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:132
void UpdateByName(const char *FileName)
Definition: recording.c:1558
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:273
uchar * GetPmt(int &Index)
Returns a pointer to the Index&#39;th TS packet of the PMT section.
Definition: remux.c:651
cUnbufferedFile * Open(void)
Definition: recording.c:2888
cSetup Setup
Definition: config.c:373
int FreeDiskSpaceMB(const char *Directory, int *UsedMB)
Definition: tools.c:410
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:99
virtual void Receive(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
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:370
#define DISKCHECKINTERVAL
Definition: recorder.c:20
int MaxVideoFileSize
Definition: config.h:333
cIndexFile * index
Definition: recorder.h:26
const char * Name(void)
Definition: recording.h:474
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:1564
bool SpinUpDisk(const char *FileName)
Definition: tools.c:631
#define MEGABYTE(n)
Definition: tools.h:44
char * recordingName
Definition: recorder.h:28
void SetIoThrottle(void)
Definition: ringbuffer.c:95
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:488
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:525
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition: remux.h:528
int Apid(int i) const
Definition: channels.h:176
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
Definition: recording.c:1365
uchar * Get(int &Count)
Gets data from the ring buffer.
Definition: ringbuffer.c:345
cFileName * fileName
Definition: recorder.h:25
#define isyslog(a...)
Definition: tools.h:35
Definition: thread.h:77
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition: remux.h:532
off_t fileSize
Definition: recorder.h:29
cRingBufferLinear * ringBuffer
Definition: recorder.h:21
Definition: tools.h:333
#define TS_SIZE
Definition: remux.h:34
bool DoubleEqual(double a, double b)
Definition: tools.h:87
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:323
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:1878
#define RUC_STARTRECORDING
Definition: recording.h:396
void Detach(void)
Definition: receiver.c:114
int Vtype(void) const
Definition: channels.h:172
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:96
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
Definition: recording.c:2628
uchar * GetPat(void)
Returns a pointer to the PAT section, which consists of exactly one TS packet.
Definition: remux.c:645
bool NextFile(void)
Definition: recorder.c:104
cUnbufferedFile * recordFile
Definition: recorder.h:27
uchar * GetBuffer(int &OutLength)
Definition: remux.c:1887
void ReportOverflow(int Bytes)
Definition: ringbuffer.c:101