some refcounting fixes and speedups
[enigma2.git] / lib / service / servicedvbrecord.cpp
1 #include <lib/service/servicedvbrecord.h>
2 #include <lib/base/eerror.h>
3 #include <lib/dvb/epgcache.h>
4 #include <fcntl.h>
5
6 DEFINE_REF(eDVBServiceRecord);
7
8 eDVBServiceRecord::eDVBServiceRecord(const eServiceReferenceDVB &ref): m_ref(ref)
9 {
10         CONNECT(m_service_handler.serviceEvent, eDVBServiceRecord::serviceEvent);
11         m_state = stateIdle;
12         m_want_record = 0;
13         m_tuned = 0;
14         m_target_fd = -1;
15         m_error = 0;
16         m_streaming = 0;
17         m_simulate = false;
18 }
19
20 void eDVBServiceRecord::serviceEvent(int event)
21 {
22         eDebug("RECORD service event %d", event);
23         switch (event)
24         {
25         case eDVBServicePMTHandler::eventTuned:
26         {
27                 eDebug("tuned..");
28                 m_tuned = 1;
29                 if (m_state == stateRecording && m_want_record)
30                         doRecord();
31                 m_event((iRecordableService*)this, evTunedIn);
32                 break;
33         }
34         case eDVBServicePMTHandler::eventTuneFailed:
35         {
36                 eDebug("record failed to tune");
37                 m_event((iRecordableService*)this, evTuneFailed);
38                 break;
39         }
40         case eDVBServicePMTHandler::eventNewProgramInfo:
41         {
42                 if (m_state == stateIdle)
43                         doPrepare();
44                 else if (m_want_record) /* doRecord can be called from Prepared and Recording state */
45                         doRecord();
46                 m_event((iRecordableService*)this, evNewProgramInfo);
47                 break;
48         }
49         case eDVBServicePMTHandler::eventMisconfiguration:
50                 m_error = errMisconfiguration;
51                 m_event((iRecordableService*)this, evTuneFailed);
52                 break;
53         case eDVBServicePMTHandler::eventNoResources:
54                 m_error = errNoResources;
55                 m_event((iRecordableService*)this, evTuneFailed);
56                 break;
57         }
58 }
59
60 RESULT eDVBServiceRecord::prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id)
61 {
62         m_filename = filename;
63         m_streaming = 0;
64         
65         if (m_state == stateIdle)
66         {
67                 int ret = doPrepare();
68                 if (!ret)
69                 {
70                         eEPGCache::getInstance()->Lock();
71                         const eit_event_struct *event = 0;
72                         eServiceReferenceDVB ref = m_ref.getParentServiceReference();
73                         if (!ref.valid())
74                                 ref = m_ref;
75                         if ( eit_event_id != -1 )
76                         {
77                                 eDebug("query epg event id %d", eit_event_id);
78                                 eEPGCache::getInstance()->lookupEventId(ref, eit_event_id, event);
79                         }
80                         if ( !event && (begTime != -1 && endTime != -1) )
81                         {
82                                 time_t queryTime = begTime + ((endTime-begTime)/2);
83                                 tm beg, end, query;
84                                 localtime_r(&begTime, &beg);
85                                 localtime_r(&endTime, &end);
86                                 localtime_r(&queryTime, &query);
87                                 eDebug("query stime %d:%d:%d, etime %d:%d:%d, qtime %d:%d:%d",
88                                         beg.tm_hour, beg.tm_min, beg.tm_sec,
89                                         end.tm_hour, end.tm_min, end.tm_sec,
90                                         query.tm_hour, query.tm_min, query.tm_sec);
91                                 eEPGCache::getInstance()->lookupEventTime(ref, queryTime, event);
92                         }
93                         if ( event )
94                         {
95                                 eDebug("found event.. store to disc");
96                                 std::string fname = filename;
97                                 fname.erase(fname.length()-2, 2);
98                                 fname+="eit";
99                                 int fd = open(fname.c_str(), O_CREAT|O_WRONLY, 0777);
100                                 if (fd>-1)
101                                 {
102                                         int evLen=HILO(event->descriptors_loop_length)+12/*EIT_LOOP_SIZE*/;
103                                         int wr = ::write( fd, (unsigned char*)event, evLen );
104                                         if ( wr != evLen )
105                                                 eDebug("eit write error (%m)");
106                                         ::close(fd);
107                                 }
108                         }
109                         eEPGCache::getInstance()->Unlock();
110                 }
111                 return ret;
112         }
113         else
114                 return -1;
115 }
116
117 RESULT eDVBServiceRecord::prepareStreaming()
118 {
119         m_filename = "";
120         m_streaming = 1;
121         if (m_state == stateIdle)
122                 return doPrepare();
123         return -1;
124 }
125
126 RESULT eDVBServiceRecord::start(bool simulate)
127 {
128         m_simulate = simulate;
129         m_want_record = 1;
130                 /* when tune wasn't yet successfully, doRecord stays in "prepared"-state which is fine. */
131         m_event((iRecordableService*)this, evStart);
132         return doRecord();
133 }
134
135 RESULT eDVBServiceRecord::stop()
136 {
137         if (!m_simulate)
138                 eDebug("stop recording!");
139         if (m_state == stateRecording)
140         {
141                 if (m_record)
142                         m_record->stop();
143                 if (m_target_fd >= 0)
144                 {
145                         ::close(m_target_fd);
146                         m_target_fd = -1;
147                 }
148                 m_state = statePrepared;
149         } else if (!m_simulate)
150                 eDebug("(was not recording)");
151         if (m_state == statePrepared)
152         {
153                 m_record = 0;
154                 m_state = stateIdle;
155         }
156         m_event((iRecordableService*)this, evRecordStopped);
157         return 0;
158 }
159
160 int eDVBServiceRecord::doPrepare()
161 {
162                 /* allocate a ts recorder if we don't already have one. */
163         if (m_state == stateIdle)
164         {
165                 m_pids_active.clear();
166                 m_state = statePrepared;
167                 return m_service_handler.tune(m_ref, 0, 0, m_simulate);
168         }
169         return 0;
170 }
171
172 int eDVBServiceRecord::doRecord()
173 {
174         int err = doPrepare();
175         if (err)
176         {
177                 m_error = errTuneFailed;
178                 m_event((iRecordableService*)this, evRecordFailed);
179                 return err;
180         }
181         
182         if (!m_tuned)
183                 return 0; /* try it again when we are tuned in */
184         
185         if (!m_record && m_tuned && !m_streaming && !m_simulate)
186         {
187                 eDebug("Recording to %s...", m_filename.c_str());
188                 ::remove(m_filename.c_str());
189                 int fd = ::open(m_filename.c_str(), O_WRONLY|O_CREAT|O_LARGEFILE, 0644);
190                 if (fd == -1)
191                 {
192                         eDebug("eDVBServiceRecord - can't open recording file!");
193                         m_error = errOpenRecordFile;
194                         m_event((iRecordableService*)this, evRecordFailed);
195                         return errOpenRecordFile;
196                 }
197
198                         /* turn off kernel caching strategies */
199                 posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
200
201                 ePtr<iDVBDemux> demux;
202                 if (m_service_handler.getDataDemux(demux))
203                 {
204                         eDebug("eDVBServiceRecord - NO DEMUX available!");
205                         m_error = errNoDemuxAvailable;
206                         m_event((iRecordableService*)this, evRecordFailed);
207                         return errNoDemuxAvailable;
208                 }
209                 demux->createTSRecorder(m_record);
210                 if (!m_record)
211                 {
212                         eDebug("eDVBServiceRecord - no ts recorder available.");
213                         m_error = errNoTsRecorderAvailable;
214                         m_event((iRecordableService*)this, evRecordFailed);
215                         return errNoTsRecorderAvailable;
216                 }
217                 m_record->setTargetFD(fd);
218                 m_record->setTargetFilename(m_filename.c_str());
219                 m_record->connectEvent(slot(*this, &eDVBServiceRecord::recordEvent), m_con_record_event);
220
221                 m_target_fd = fd;
222         }
223         
224         if (m_streaming)
225         {
226                 m_state = stateRecording;
227                 eDebug("start streaming...");
228         } else
229         {
230                 eDebug("start recording...");
231
232                 eDVBServicePMTHandler::program program;
233                 if (m_service_handler.getProgramInfo(program))
234                         eDebug("getting program info failed.");
235                 else
236                 {
237                         std::set<int> pids_to_record;
238
239                         pids_to_record.insert(0); // PAT
240
241                         if (program.pmtPid != -1)
242                                 pids_to_record.insert(program.pmtPid); // PMT
243
244                         int timing_pid = -1;
245
246                         eDebugNoNewLine("RECORD: have %d video stream(s)", program.videoStreams.size());
247                         if (!program.videoStreams.empty())
248                         {
249                                 eDebugNoNewLine(" (");
250                                 for (std::vector<eDVBServicePMTHandler::videoStream>::const_iterator
251                                         i(program.videoStreams.begin()); 
252                                         i != program.videoStreams.end(); ++i)
253                                 {
254                                         pids_to_record.insert(i->pid);
255                                         
256                                         if (timing_pid == -1)
257                                                 timing_pid = i->pid;
258                                         
259                                         if (i != program.videoStreams.begin())
260                                                         eDebugNoNewLine(", ");
261                                         eDebugNoNewLine("%04x", i->pid);
262                                 }
263                                 eDebugNoNewLine(")");
264                         }
265                         eDebugNoNewLine(", and %d audio stream(s)", program.audioStreams.size());
266                         if (!program.audioStreams.empty())
267                         {
268                                 eDebugNoNewLine(" (");
269                                 for (std::vector<eDVBServicePMTHandler::audioStream>::const_iterator
270                                         i(program.audioStreams.begin()); 
271                                         i != program.audioStreams.end(); ++i)
272                                 {
273                                         pids_to_record.insert(i->pid);
274         
275                                         if (timing_pid == -1)
276                                                 timing_pid = i->pid;
277                                 
278                                         if (i != program.audioStreams.begin())
279                                                 eDebugNoNewLine(", ");
280                                         eDebugNoNewLine("%04x", i->pid);
281                                 }
282                                 eDebugNoNewLine(")");
283                         }
284                         if (!program.subtitleStreams.empty())
285                         {
286                                 eDebugNoNewLine(" (");
287                                 for (std::vector<eDVBServicePMTHandler::subtitleStream>::const_iterator
288                                         i(program.subtitleStreams.begin());
289                                         i != program.subtitleStreams.end(); ++i)
290                                 {
291                                         pids_to_record.insert(i->pid);
292         
293                                         if (i != program.subtitleStreams.begin())
294                                                 eDebugNoNewLine(", ");
295                                         eDebugNoNewLine("%04x", i->pid);
296                                 }
297                                 eDebugNoNewLine(")");
298                         }
299                         eDebugNoNewLine(", and the pcr pid is %04x", program.pcrPid);
300                         if (program.pcrPid != 0x1fff)
301                                 pids_to_record.insert(program.pcrPid);
302                         eDebug(", and the text pid is %04x", program.textPid);
303                         if (program.textPid != -1)
304                                 pids_to_record.insert(program.textPid); // Videotext
305
306                                 /* find out which pids are NEW and which pids are obsolete.. */
307                         std::set<int> new_pids, obsolete_pids;
308
309                         std::set_difference(pids_to_record.begin(), pids_to_record.end(), 
310                                         m_pids_active.begin(), m_pids_active.end(),
311                                         std::inserter(new_pids, new_pids.begin()));
312
313                         std::set_difference(
314                                         m_pids_active.begin(), m_pids_active.end(),
315                                         pids_to_record.begin(), pids_to_record.end(), 
316                                         std::inserter(new_pids, new_pids.begin())
317                                         );
318                         
319                         for (std::set<int>::iterator i(new_pids.begin()); i != new_pids.end(); ++i)
320                         {
321                                 eDebug("ADD PID: %04x", *i);
322                                 m_record->addPID(*i);
323                         }
324
325                         for (std::set<int>::iterator i(obsolete_pids.begin()); i != obsolete_pids.end(); ++i)
326                         {
327                                 eDebug("REMOVED PID: %04x", *i);
328                                 m_record->removePID(*i);
329                         }
330
331                         if (timing_pid != -1)
332                                 m_record->setTimingPID(timing_pid);
333
334                         m_pids_active = pids_to_record;
335
336                         if (m_state != stateRecording)
337                         {
338                                 m_record->start();
339                                 m_state = stateRecording;
340                         }
341                 }
342         }
343         m_error = 0;
344         m_event((iRecordableService*)this, evRecordRunning);
345         return 0;
346 }
347
348 RESULT eDVBServiceRecord::frontendInfo(ePtr<iFrontendInformation> &ptr)
349 {
350         ptr = this;
351         return 0;
352 }
353
354 RESULT eDVBServiceRecord::connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection)
355 {
356         connection = new eConnection((iRecordableService*)this, m_event.connect(event));
357         return 0;
358 }
359
360 RESULT eDVBServiceRecord::stream(ePtr<iStreamableService> &ptr)
361 {
362         ptr = this;
363         return 0;
364 }
365
366 extern void PutToDict(ePyObject &dict, const char*key, ePyObject item);  // defined in dvb/frontend.cpp
367
368 PyObject *eDVBServiceRecord::getStreamingData()
369 {
370         eDVBServicePMTHandler::program program;
371         if (!m_tuned || m_service_handler.getProgramInfo(program))
372         {
373                 Py_RETURN_NONE;
374         }
375
376         ePyObject r = program.createPythonObject();
377         ePtr<iDVBDemux> demux;
378         if (!m_service_handler.getDataDemux(demux))
379         {
380                 uint8_t demux_id;
381                 if (!demux->getCADemuxID(demux_id))
382                         PutToDict(r, "demux", PyInt_FromLong(demux_id));
383         }
384
385         return r;
386 }
387
388 void eDVBServiceRecord::recordEvent(int event)
389 {
390         switch (event)
391         {
392         case iDVBTSRecorder::eventWriteError:
393                 eWarning("[eDVBServiceRecord] record write error");
394                 stop();
395                 m_event((iRecordableService*)this, evRecordWriteError);
396                 return;
397         default:
398                 eDebug("unhandled record event %d", event);
399         }
400 }