1b83a4c4949599f284cd5989a214db48ad244eae
[vuplus_dvbapp] / lib / service / servicedvbrecord.cpp
1 #include <lib/service/servicedvbrecord.h>
2 #include <lib/base/eerror.h>
3 #include <lib/dvb/epgcache.h>
4 #include <lib/dvb/metaparser.h>
5 #include <fcntl.h>
6
7         /* for cutlist */
8 #include <byteswap.h>
9 #include <netinet/in.h>
10
11 #ifndef BYTE_ORDER
12 #error no byte order defined!
13 #endif
14
15 DEFINE_REF(eDVBServiceRecord);
16
17 eDVBServiceRecord::eDVBServiceRecord(const eServiceReferenceDVB &ref): m_ref(ref)
18 {
19         CONNECT(m_service_handler.serviceEvent, eDVBServiceRecord::serviceEvent);
20         CONNECT(m_event_handler.m_eit_changed, eDVBServiceRecord::gotNewEvent);
21         m_state = stateIdle;
22         m_want_record = 0;
23         m_tuned = 0;
24         m_target_fd = -1;
25         m_error = 0;
26         m_streaming = 0;
27         m_simulate = false;
28         m_last_event_id = -1;
29 }
30
31 void eDVBServiceRecord::serviceEvent(int event)
32 {
33         eDebug("RECORD service event %d", event);
34         switch (event)
35         {
36         case eDVBServicePMTHandler::eventTuned:
37         {
38                 eDebug("tuned..");
39                 m_tuned = 1;
40
41                         /* start feeding EIT updates */
42                 ePtr<iDVBDemux> m_demux;
43                 if (!m_service_handler.getDataDemux(m_demux))
44                 {
45                         eServiceReferenceDVB &ref = (eServiceReferenceDVB&) m_ref;
46                         int sid = ref.getParentServiceID().get();
47                         if (!sid)
48                                 sid = ref.getServiceID().get();
49                         if ( ref.getParentTransportStreamID().get() &&
50                                 ref.getParentTransportStreamID() != ref.getTransportStreamID() )
51                                 m_event_handler.startOther(m_demux, sid);
52                         else
53                                 m_event_handler.start(m_demux, sid);
54                 }
55
56                 if (m_state == stateRecording && m_want_record)
57                         doRecord();
58                 m_event((iRecordableService*)this, evTunedIn);
59                 break;
60         }
61         case eDVBServicePMTHandler::eventTuneFailed:
62         {
63                 eDebug("record failed to tune");
64                 m_event((iRecordableService*)this, evTuneFailed);
65                 break;
66         }
67         case eDVBServicePMTHandler::eventNewProgramInfo:
68         {
69                 if (m_state == stateIdle)
70                         doPrepare();
71                 else if (m_want_record) /* doRecord can be called from Prepared and Recording state */
72                         doRecord();
73                 m_event((iRecordableService*)this, evNewProgramInfo);
74                 break;
75         }
76         case eDVBServicePMTHandler::eventMisconfiguration:
77                 m_error = errMisconfiguration;
78                 m_event((iRecordableService*)this, evTuneFailed);
79                 break;
80         case eDVBServicePMTHandler::eventNoResources:
81                 m_error = errNoResources;
82                 m_event((iRecordableService*)this, evTuneFailed);
83                 break;
84         }
85 }
86
87 RESULT eDVBServiceRecord::prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags)
88 {
89         m_filename = filename;
90         m_streaming = 0;
91         
92         if (m_state == stateIdle)
93         {
94                 int ret = doPrepare();
95                 if (!ret)
96                 {
97                         eEPGCache::getInstance()->Lock();
98                         const eit_event_struct *event = 0;
99                         eServiceReferenceDVB ref = m_ref.getParentServiceReference();
100                         if (!ref.valid())
101                                 ref = m_ref;
102                         if ( eit_event_id != -1 )
103                         {
104                                 eDebug("query epg event id %d", eit_event_id);
105                                 eEPGCache::getInstance()->lookupEventId(ref, eit_event_id, event);
106                         }
107                         if ( !event && (begTime != -1 && endTime != -1) )
108                         {
109                                 time_t queryTime = begTime + ((endTime-begTime)/2);
110                                 tm beg, end, query;
111                                 localtime_r(&begTime, &beg);
112                                 localtime_r(&endTime, &end);
113                                 localtime_r(&queryTime, &query);
114                                 eDebug("query stime %d:%d:%d, etime %d:%d:%d, qtime %d:%d:%d",
115                                         beg.tm_hour, beg.tm_min, beg.tm_sec,
116                                         end.tm_hour, end.tm_min, end.tm_sec,
117                                         query.tm_hour, query.tm_min, query.tm_sec);
118                                 eEPGCache::getInstance()->lookupEventTime(ref, queryTime, event);
119                         }
120                         if ( event )
121                         {
122                                 eDebug("found event.. store to disc");
123                                 std::string fname = filename;
124                                 fname.erase(fname.length()-2, 2);
125                                 fname+="eit";
126                                 int fd = open(fname.c_str(), O_CREAT|O_WRONLY, 0777);
127                                 if (fd>-1)
128                                 {
129                                         int evLen=HILO(event->descriptors_loop_length)+12/*EIT_LOOP_SIZE*/;
130                                         int wr = ::write( fd, (unsigned char*)event, evLen );
131                                         if ( wr != evLen )
132                                                 eDebug("eit write error (%m)");
133                                         ::close(fd);
134                                 }
135                         }
136                         eEPGCache::getInstance()->Unlock();
137                         
138                 }
139                 if (ret)
140                         return ret;
141                 else
142                 {
143                         eDVBMetaParser meta;
144                         meta.m_time_create = begTime;
145                         meta.m_ref = m_ref;
146                         meta.m_data_ok = 1;
147                         if (name)
148                                 meta.m_name = name;
149                         if (descr)
150                                 meta.m_description = descr;
151                         ret = meta.updateMeta(filename) ? -255 : 0;
152                 }
153                 return ret;
154         }
155         return -1;
156 }
157
158 RESULT eDVBServiceRecord::prepareStreaming()
159 {
160         m_filename = "";
161         m_streaming = 1;
162         if (m_state == stateIdle)
163                 return doPrepare();
164         return -1;
165 }
166
167 RESULT eDVBServiceRecord::start(bool simulate)
168 {
169         m_simulate = simulate;
170         m_want_record = 1;
171                 /* when tune wasn't yet successfully, doRecord stays in "prepared"-state which is fine. */
172         m_event((iRecordableService*)this, evStart);
173         return doRecord();
174 }
175
176 RESULT eDVBServiceRecord::stop()
177 {
178         if (!m_simulate)
179                 eDebug("stop recording!");
180         if (m_state == stateRecording)
181         {
182                 if (m_record)
183                         m_record->stop();
184                 if (m_target_fd >= 0)
185                 {
186                         ::close(m_target_fd);
187                         m_target_fd = -1;
188                 }
189                 
190                 saveCutlist();
191                 
192                 m_state = statePrepared;
193         } else if (!m_simulate)
194                 eDebug("(was not recording)");
195         if (m_state == statePrepared)
196         {
197                 m_record = 0;
198                 m_state = stateIdle;
199         }
200         m_event((iRecordableService*)this, evRecordStopped);
201         return 0;
202 }
203
204 int eDVBServiceRecord::doPrepare()
205 {
206                 /* allocate a ts recorder if we don't already have one. */
207         if (m_state == stateIdle)
208         {
209                 m_pids_active.clear();
210                 m_state = statePrepared;
211                 return m_service_handler.tune(m_ref, 0, 0, m_simulate);
212         }
213         return 0;
214 }
215
216 int eDVBServiceRecord::doRecord()
217 {
218         int err = doPrepare();
219         if (err)
220         {
221                 m_error = errTuneFailed;
222                 m_event((iRecordableService*)this, evRecordFailed);
223                 return err;
224         }
225         
226         if (!m_tuned)
227                 return 0; /* try it again when we are tuned in */
228         
229         if (!m_record && m_tuned && !m_streaming && !m_simulate)
230         {
231                 eDebug("Recording to %s...", m_filename.c_str());
232                 ::remove(m_filename.c_str());
233                 int fd = ::open(m_filename.c_str(), O_WRONLY|O_CREAT|O_LARGEFILE, 0644);
234                 if (fd == -1)
235                 {
236                         eDebug("eDVBServiceRecord - can't open recording file!");
237                         m_error = errOpenRecordFile;
238                         m_event((iRecordableService*)this, evRecordFailed);
239                         return errOpenRecordFile;
240                 }
241
242                         /* turn off kernel caching strategies */
243                 posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
244
245                 ePtr<iDVBDemux> demux;
246                 if (m_service_handler.getDataDemux(demux))
247                 {
248                         eDebug("eDVBServiceRecord - NO DEMUX available!");
249                         m_error = errNoDemuxAvailable;
250                         m_event((iRecordableService*)this, evRecordFailed);
251                         return errNoDemuxAvailable;
252                 }
253                 demux->createTSRecorder(m_record);
254                 if (!m_record)
255                 {
256                         eDebug("eDVBServiceRecord - no ts recorder available.");
257                         m_error = errNoTsRecorderAvailable;
258                         m_event((iRecordableService*)this, evRecordFailed);
259                         return errNoTsRecorderAvailable;
260                 }
261                 m_record->setTargetFD(fd);
262                 m_record->setTargetFilename(m_filename.c_str());
263                 m_record->connectEvent(slot(*this, &eDVBServiceRecord::recordEvent), m_con_record_event);
264
265                 m_target_fd = fd;
266         }
267         
268         if (m_streaming)
269         {
270                 m_state = stateRecording;
271                 eDebug("start streaming...");
272         } else
273         {
274                 eDebug("start recording...");
275
276                 eDVBServicePMTHandler::program program;
277                 if (m_service_handler.getProgramInfo(program))
278                         eDebug("getting program info failed.");
279                 else
280                 {
281                         std::set<int> pids_to_record;
282
283                         pids_to_record.insert(0); // PAT
284
285                         if (program.pmtPid != -1)
286                                 pids_to_record.insert(program.pmtPid); // PMT
287
288                         int timing_pid = -1, timing_pid_type = -1;
289
290                         eDebugNoNewLine("RECORD: have %d video stream(s)", program.videoStreams.size());
291                         if (!program.videoStreams.empty())
292                         {
293                                 eDebugNoNewLine(" (");
294                                 for (std::vector<eDVBServicePMTHandler::videoStream>::const_iterator
295                                         i(program.videoStreams.begin()); 
296                                         i != program.videoStreams.end(); ++i)
297                                 {
298                                         pids_to_record.insert(i->pid);
299                                         
300                                         if (timing_pid == -1)
301                                         {
302                                                 timing_pid = i->pid;
303                                                 timing_pid_type = i->type;
304                                         }
305                                         
306                                         if (i != program.videoStreams.begin())
307                                                         eDebugNoNewLine(", ");
308                                         eDebugNoNewLine("%04x", i->pid);
309                                 }
310                                 eDebugNoNewLine(")");
311                         }
312                         eDebugNoNewLine(", and %d audio stream(s)", program.audioStreams.size());
313                         if (!program.audioStreams.empty())
314                         {
315                                 eDebugNoNewLine(" (");
316                                 for (std::vector<eDVBServicePMTHandler::audioStream>::const_iterator
317                                         i(program.audioStreams.begin()); 
318                                         i != program.audioStreams.end(); ++i)
319                                 {
320                                         pids_to_record.insert(i->pid);
321         
322                                         if (timing_pid == -1)
323                                         {
324                                                 timing_pid = i->pid;
325                                                 timing_pid_type = -1;
326                                         }
327                                 
328                                         if (i != program.audioStreams.begin())
329                                                 eDebugNoNewLine(", ");
330                                         eDebugNoNewLine("%04x", i->pid);
331                                 }
332                                 eDebugNoNewLine(")");
333                         }
334                         if (!program.subtitleStreams.empty())
335                         {
336                                 eDebugNoNewLine(" (");
337                                 for (std::vector<eDVBServicePMTHandler::subtitleStream>::const_iterator
338                                         i(program.subtitleStreams.begin());
339                                         i != program.subtitleStreams.end(); ++i)
340                                 {
341                                         pids_to_record.insert(i->pid);
342         
343                                         if (i != program.subtitleStreams.begin())
344                                                 eDebugNoNewLine(", ");
345                                         eDebugNoNewLine("%04x", i->pid);
346                                 }
347                                 eDebugNoNewLine(")");
348                         }
349                         eDebugNoNewLine(", and the pcr pid is %04x", program.pcrPid);
350                         if (program.pcrPid != 0x1fff)
351                                 pids_to_record.insert(program.pcrPid);
352                         eDebug(", and the text pid is %04x", program.textPid);
353                         if (program.textPid != -1)
354                                 pids_to_record.insert(program.textPid); // Videotext
355
356                                 /* find out which pids are NEW and which pids are obsolete.. */
357                         std::set<int> new_pids, obsolete_pids;
358
359                         std::set_difference(pids_to_record.begin(), pids_to_record.end(), 
360                                         m_pids_active.begin(), m_pids_active.end(),
361                                         std::inserter(new_pids, new_pids.begin()));
362
363                         std::set_difference(
364                                         m_pids_active.begin(), m_pids_active.end(),
365                                         pids_to_record.begin(), pids_to_record.end(), 
366                                         std::inserter(new_pids, new_pids.begin())
367                                         );
368                         
369                         for (std::set<int>::iterator i(new_pids.begin()); i != new_pids.end(); ++i)
370                         {
371                                 eDebug("ADD PID: %04x", *i);
372                                 m_record->addPID(*i);
373                         }
374
375                         for (std::set<int>::iterator i(obsolete_pids.begin()); i != obsolete_pids.end(); ++i)
376                         {
377                                 eDebug("REMOVED PID: %04x", *i);
378                                 m_record->removePID(*i);
379                         }
380
381                         if (timing_pid != -1)
382                                 m_record->setTimingPID(timing_pid, timing_pid_type);
383
384                         m_pids_active = pids_to_record;
385
386                         if (m_state != stateRecording)
387                         {
388                                 m_record->start();
389                                 m_state = stateRecording;
390                         }
391                 }
392         }
393         m_error = 0;
394         m_event((iRecordableService*)this, evRecordRunning);
395         return 0;
396 }
397
398 RESULT eDVBServiceRecord::frontendInfo(ePtr<iFrontendInformation> &ptr)
399 {
400         ptr = this;
401         return 0;
402 }
403
404 RESULT eDVBServiceRecord::connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection)
405 {
406         connection = new eConnection((iRecordableService*)this, m_event.connect(event));
407         return 0;
408 }
409
410 RESULT eDVBServiceRecord::stream(ePtr<iStreamableService> &ptr)
411 {
412         ptr = this;
413         return 0;
414 }
415
416 extern void PutToDict(ePyObject &dict, const char*key, long val);  // defined in dvb/frontend.cpp
417
418 PyObject *eDVBServiceRecord::getStreamingData()
419 {
420         eDVBServicePMTHandler::program program;
421         if (!m_tuned || m_service_handler.getProgramInfo(program))
422         {
423                 Py_RETURN_NONE;
424         }
425
426         ePyObject r = program.createPythonObject();
427         ePtr<iDVBDemux> demux;
428         if (!m_service_handler.getDataDemux(demux))
429         {
430                 uint8_t demux_id;
431                 if (!demux->getCADemuxID(demux_id))
432                         PutToDict(r, "demux", demux_id);
433         }
434
435         return r;
436 }
437
438 void eDVBServiceRecord::recordEvent(int event)
439 {
440         switch (event)
441         {
442         case iDVBTSRecorder::eventWriteError:
443                 eWarning("[eDVBServiceRecord] record write error");
444                 stop();
445                 m_event((iRecordableService*)this, evRecordWriteError);
446                 return;
447         default:
448                 eDebug("unhandled record event %d", event);
449         }
450 }
451
452 void eDVBServiceRecord::gotNewEvent()
453 {
454         ePtr<eServiceEvent> event_now;
455         m_event_handler.getEvent(event_now, 0);
456
457         if (!event_now)
458                 return;
459
460         int event_id = event_now->getEventId();
461
462         pts_t p;
463         
464         if (m_record)
465         {
466                 if (m_record->getCurrentPCR(p))
467                         eDebug("getting PCR failed!");
468                 else
469                 {
470                         static int i;
471                         m_event_timestamps[/* event_id*/ ++i] = p;
472                         eDebug("pcr of eit change: %llx", p);
473                 }
474         }
475
476         if (event_id != m_last_event_id)
477                 eDebug("[eDVBServiceRecord] now running: %s (%d seconds)", event_now->getEventName().c_str(), event_now->getDuration());
478         
479         m_last_event_id = event_id;
480 }
481
482 void eDVBServiceRecord::saveCutlist()
483 {
484                         /* XXX: dupe of eDVBServicePlay::saveCuesheet, refactor plz */
485         std::string filename = m_filename + ".cuts";
486
487         eDVBTSTools tstools;
488         
489         if (tstools.openFile(m_filename.c_str()))
490         {
491                 eDebug("[eDVBServiceRecord] saving cutlist failed because tstools failed");
492                 return;
493         }
494         
495         FILE *f = fopen(filename.c_str(), "wb");
496
497         if (f)
498         {
499                 unsigned long long where;
500                 int what;
501
502                 for (std::map<int,pts_t>::iterator i(m_event_timestamps.begin()); i != m_event_timestamps.end(); ++i)
503                 {
504                         pts_t p = i->second;
505                         off_t offset = 0; // fixme, we need to note down both
506                         if (tstools.fixupPTS(offset, p))
507                         {
508                                 eDebug("[eDVBServiceRecord] fixing up PTS failed, not saving");
509                                 continue;
510                         }
511                         eDebug("fixed up %llx to %llx (offset %llx)", i->second, p, offset);
512 #if BYTE_ORDER == BIG_ENDIAN
513                         where = p;
514 #else
515                         where = bswap_64(p);
516 #endif
517                         what = htonl(2); /* mark */
518                         fwrite(&where, sizeof(where), 1, f);
519                         fwrite(&what, sizeof(what), 1, f);
520                 }
521                 fclose(f);
522         }
523         
524 }