Support turbo2.
[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 <lib/base/httpstream.h>
6 #include <lib/base/nconfig.h>
7
8 #include <fcntl.h>
9
10         /* for cutlist */
11 #include <byteswap.h>
12 #include <netinet/in.h>
13
14 #ifndef BYTE_ORDER
15 #error no byte order defined!
16 #endif
17
18 DEFINE_REF(eDVBServiceRecord);
19
20 eDVBServiceRecord::eDVBServiceRecord(const eServiceReferenceDVB &ref, bool isstreamclient):
21         m_ref(ref),m_is_stream_client(isstreamclient)
22 {
23         CONNECT(m_service_handler.serviceEvent, eDVBServiceRecord::serviceEvent);
24         CONNECT(m_event_handler.m_eit_changed, eDVBServiceRecord::gotNewEvent);
25         m_state = stateIdle;
26         m_want_record = 0;
27         m_record_ecm = false;
28         m_descramble = true;
29         m_pvr_descramble = false;
30         m_tuned = 0;
31         m_target_fd = -1;
32         m_error = 0;
33         m_streaming = 0;
34         m_simulate = false;
35         m_last_event_id = -1;
36         m_serviceType = eDVBServicePMTHandler::recording;
37 }
38
39 void eDVBServiceRecord::serviceEvent(int event)
40 {
41         eDebug("RECORD service event %d", event);
42         switch (event)
43         {
44         case eDVBServicePMTHandler::eventTuned:
45         {
46                 eDebug("tuned..");
47                 m_tuned = 1;
48
49                         /* start feeding EIT updates */
50                 ePtr<iDVBDemux> m_demux;
51                 if (!m_service_handler.getDataDemux(m_demux))
52                 {
53                         eServiceReferenceDVB &ref = (eServiceReferenceDVB&) m_ref;
54                         int sid = ref.getParentServiceID().get();
55                         if (!sid)
56                                 sid = ref.getServiceID().get();
57                         if ( ref.getParentTransportStreamID().get() &&
58                                 ref.getParentTransportStreamID() != ref.getTransportStreamID() )
59                                 m_event_handler.startOther(m_demux, sid);
60                         else
61                                 m_event_handler.start(m_demux, sid);
62                 }
63
64                 if (m_state == stateRecording && m_want_record)
65                         doRecord();
66                 m_event((iRecordableService*)this, evTunedIn);
67                 break;
68         }
69         case eDVBServicePMTHandler::eventTuneFailed:
70         {
71                 eDebug("record failed to tune");
72                 m_event((iRecordableService*)this, evTuneFailed);
73                 break;
74         }
75         case eDVBServicePMTHandler::eventNewProgramInfo:
76         {
77                 if (m_state == stateIdle)
78                         doPrepare();
79                 else if (m_want_record) /* doRecord can be called from Prepared and Recording state */
80                 {
81                         if (m_pvr_descramble)
82                                 updateDecoder();
83                         else
84                                 doRecord();
85                 }
86                 m_event((iRecordableService*)this, evNewProgramInfo);
87                 break;
88         }
89         case eDVBServicePMTHandler::eventMisconfiguration:
90                 m_error = errMisconfiguration;
91                 m_event((iRecordableService*)this, evTuneFailed);
92                 break;
93         case eDVBServicePMTHandler::eventNoResources:
94                 m_error = errNoResources;
95                 m_event((iRecordableService*)this, evTuneFailed);
96                 break;
97         case eDVBServicePMTHandler::eventEOF:
98                 m_event((iRecordableService*)this, evPvrEof);
99                 break;
100         case eDVBServicePMTHandler::eventStartPvrDescramble:
101                 if (m_want_record)
102                 {
103                         doRecord();
104                 }
105                 break;
106         }
107 }
108
109 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, bool descramble, bool recordecm)
110 {
111         bool config_recording_always_ecm = ePythonConfigQuery::getConfigBoolValue("config.recording.always_ecm", false);
112         bool config_recording_never_decrypt = ePythonConfigQuery::getConfigBoolValue("config.recording.never_decrypt", false);
113         m_filename = filename;
114         m_streaming = 0;
115         m_descramble = config_recording_never_decrypt ? false : descramble;
116         m_record_ecm = config_recording_always_ecm ? true : recordecm;
117
118         // force descramble for _pvrdesc.ts
119         if (strstr(filename, "_pvrdesc.ts"))
120         {
121                 m_pvr_descramble = true;
122                 m_descramble = true;
123         }
124
125         if (m_state == stateIdle)
126         {
127                 int ret = doPrepare();
128                 if (!ret)
129                 {
130                         eServiceReferenceDVB ref = m_ref.getParentServiceReference();
131                         ePtr<eDVBResourceManager> res_mgr;
132                         eDVBMetaParser meta;
133                         std::string service_data;
134                         if (!ref.valid())
135                                 ref = m_ref;
136                         if (!eDVBResourceManager::getInstance(res_mgr))
137                         {
138                                 ePtr<iDVBChannelList> db;
139                                 if (!res_mgr->getChannelList(db))
140                                 {
141                                         ePtr<eDVBService> service;
142                                         if (!db->getService(ref, service))
143                                         {
144                                                 char tmp[255];
145                                                 sprintf(tmp, "f:%x", service->m_flags);
146                                                 service_data += tmp;
147                                                 // cached pids
148                                                 for (int x=0; x < eDVBService::cacheMax; ++x)
149                                                 {
150                                                         int entry = service->getCacheEntry((eDVBService::cacheID)x);
151                                                         if (entry != -1)
152                                                         {
153                                                                 sprintf(tmp, ",c:%02d%04x", x, entry);
154                                                                 service_data += tmp;
155                                                         }
156                                                 }
157                                         }
158                                 }
159                         }
160                         meta.m_time_create = begTime;
161                         meta.m_ref = m_ref;
162                         meta.m_data_ok = 1;
163                         meta.m_service_data = service_data;
164                         if (name)
165                                 meta.m_name = name;
166                         if (descr)
167                                 meta.m_description = descr;
168                         if (tags)
169                                 meta.m_tags = tags;
170                         meta.m_scrambled = !m_descramble;
171                         ret = meta.updateMeta(m_filename) ? -255 : 0;
172                         if (!ret)
173                         {
174                                 std::string fname = filename;
175                                 fname.erase(fname.length()-2, 2);
176                                 fname += "eit";
177                                 eEPGCache::getInstance()->saveEventToFile(fname.c_str(), ref, eit_event_id, begTime, endTime);
178                         }
179                 }
180                 return ret;
181         }
182         return -1;
183 }
184
185 RESULT eDVBServiceRecord::prepareStreaming()
186 {
187         m_filename = "";
188         m_streaming = 1;
189         if (m_state == stateIdle)
190                 return doPrepare();
191         return -1;
192 }
193
194 RESULT eDVBServiceRecord::start(bool simulate)
195 {
196         m_simulate = simulate;
197         m_want_record = 1;
198                 /* when tune wasn't yet successfully, doRecord stays in "prepared"-state which is fine. */
199         m_event((iRecordableService*)this, evStart);
200         return doRecord();
201 }
202
203 RESULT eDVBServiceRecord::stop()
204 {
205         if (!m_simulate)
206                 eDebug("stop recording!");
207         if (m_state == stateRecording)
208         {
209                 if (m_record)
210                         m_record->stop();
211                 if (m_target_fd >= 0)
212                 {
213                         ::close(m_target_fd);
214                         m_target_fd = -1;
215                 }
216                 
217                 saveCutlist();
218                 
219                 m_state = statePrepared;
220         } else if (!m_simulate)
221                 eDebug("(was not recording)");
222         if (m_state == statePrepared)
223         {
224                 m_record = 0;
225                 m_state = stateIdle;
226         }
227         m_event((iRecordableService*)this, evRecordStopped);
228         return 0;
229 }
230
231 int eDVBServiceRecord::doPrepare()
232 {
233                 /* allocate a ts recorder if we don't already have one. */
234         if (m_state == stateIdle)
235         {
236                 int use_decode_demux = 0;
237
238                 if (m_streaming)
239                 {
240                         m_serviceType = m_record_ecm ? eDVBServicePMTHandler::scrambled_streamserver : eDVBServicePMTHandler::streamserver;
241                 }
242                 else
243                 {
244                         m_serviceType = m_record_ecm ? eDVBServicePMTHandler::scrambled_recording : eDVBServicePMTHandler::recording;
245                 }
246
247                 m_pids_active.clear();
248                 m_state = statePrepared;
249                 ePtr<iTsSource> source;
250                 if (!m_simulate && !m_ref.path.empty())
251                 {
252                         if (m_is_stream_client)
253                         {
254                                 m_descramble = true; // assume stream is scrambled
255                                 m_record_ecm = false;
256                                 m_serviceType = eDVBServicePMTHandler::streamclient;
257                                 eHttpStream *f = new eHttpStream();
258                                 f->open(m_ref.path.c_str());
259                                 source = ePtr<iTsSource>(f);
260                         }
261                         else
262                         {
263                                 use_decode_demux = 1;
264
265                                 /* re-record a recording */
266                                 eDVBMetaParser meta;
267                                 if (!meta.parseFile(m_ref.path))
268                                 {
269                                         m_descramble = meta.m_scrambled;
270                                 }
271
272                                 if(m_pvr_descramble)
273                                 {
274                                         m_serviceType = eDVBServicePMTHandler::pvrDescramble;
275                                 }
276                                 else
277                                 {
278                                         m_serviceType = eDVBServicePMTHandler::offline;
279                                 }
280
281                                 eRawFile *f = new eRawFile();
282                                 f->open(m_ref.path.c_str());
283                                 source = ePtr<iTsSource>(f);
284                         }
285                         m_event((iRecordableService*)this, evPvrTuneStart);
286                 }
287                 else
288                 {
289                         m_event((iRecordableService*)this, evTuneStart);
290                 }
291                 return m_service_handler.tuneExt(m_ref, use_decode_demux, source, m_ref.path.c_str(), 0, m_simulate, 0, m_serviceType, m_descramble);
292         }
293         return 0;
294 }
295
296 int eDVBServiceRecord::doRecord()
297 {
298         int err = doPrepare();
299         if (err)
300         {
301                 m_error = errTuneFailed;
302                 m_event((iRecordableService*)this, evRecordFailed);
303                 return err;
304         }
305         
306         if (!m_tuned)
307                 return 0; /* try it again when we are tuned in */
308         
309         if (!m_record && m_tuned && !m_streaming && !m_simulate)
310         {
311                 if (m_pvr_descramble)
312                 {
313                         if (m_service_handler.isPmtReady())
314                         {
315                                 if (!m_service_handler.isCiConnected())
316                                 {
317                                         m_event((iRecordableService*)this, evRecordFailed);
318                                         return errNoCiConnected;
319                                 }
320                         }
321                         else
322                         {
323                                 return 0;
324                         }
325                 }
326
327                 eDebug("Recording to %s...", m_filename.c_str());
328                 ::remove(m_filename.c_str());
329                 int fd = ::open(m_filename.c_str(), O_WRONLY|O_CREAT|O_LARGEFILE, 0644);
330                 if (fd == -1)
331                 {
332                         eDebug("eDVBServiceRecord - can't open recording file!");
333                         m_error = errOpenRecordFile;
334                         m_event((iRecordableService*)this, evRecordFailed);
335                         return errOpenRecordFile;
336                 }
337
338                         /* turn off kernel caching strategies */
339                 posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
340
341                 ePtr<iDVBDemux> demux;
342                 if (m_service_handler.getDataDemux(demux))
343                 {
344                         eDebug("eDVBServiceRecord - NO DEMUX available!");
345                         m_error = errNoDemuxAvailable;
346                         m_event((iRecordableService*)this, evRecordFailed);
347                         return errNoDemuxAvailable;
348                 }
349                 demux->createTSRecorder(m_record);
350                 if (!m_record)
351                 {
352                         eDebug("eDVBServiceRecord - no ts recorder available.");
353                         m_error = errNoTsRecorderAvailable;
354                         m_event((iRecordableService*)this, evRecordFailed);
355                         return errNoTsRecorderAvailable;
356                 }
357                 m_record->setTargetFD(fd);
358                 m_record->setTargetFilename(m_filename.c_str());
359                 m_record->connectEvent(slot(*this, &eDVBServiceRecord::recordEvent), m_con_record_event);
360
361                 m_target_fd = fd;
362         }
363         
364         if (m_streaming)
365         {
366                 m_state = stateRecording;
367                 eDebug("start streaming...");
368         } else
369         {
370                 eDebug("start recording...");
371
372                 eDVBServicePMTHandler::program program;
373                 if (m_service_handler.getProgramInfo(program))
374                         eDebug("getting program info failed.");
375                 else
376                 {
377                         std::set<int> pids_to_record;
378
379                         pids_to_record.insert(0); // PAT
380
381                         if (program.pmtPid != -1)
382                                 pids_to_record.insert(program.pmtPid); // PMT
383
384                         int timing_pid = -1, timing_pid_type = -1;
385
386                         eDebugNoNewLine("RECORD: have %zd video stream(s)", program.videoStreams.size());
387                         if (!program.videoStreams.empty())
388                         {
389                                 eDebugNoNewLine(" (");
390                                 for (std::vector<eDVBServicePMTHandler::videoStream>::const_iterator
391                                         i(program.videoStreams.begin()); 
392                                         i != program.videoStreams.end(); ++i)
393                                 {
394                                         pids_to_record.insert(i->pid);
395                                         
396                                         if (timing_pid == -1)
397                                         {
398                                                 timing_pid = i->pid;
399                                                 timing_pid_type = i->type;
400                                         }
401                                         
402                                         if (i != program.videoStreams.begin())
403                                                         eDebugNoNewLine(", ");
404                                         eDebugNoNewLine("%04x", i->pid);
405                                 }
406                                 eDebugNoNewLine(")");
407                         }
408                         eDebugNoNewLine(", and %zd audio stream(s)", program.audioStreams.size());
409                         if (!program.audioStreams.empty())
410                         {
411                                 eDebugNoNewLine(" (");
412                                 for (std::vector<eDVBServicePMTHandler::audioStream>::const_iterator
413                                         i(program.audioStreams.begin()); 
414                                         i != program.audioStreams.end(); ++i)
415                                 {
416                                         pids_to_record.insert(i->pid);
417         
418                                         if (timing_pid == -1)
419                                         {
420                                                 timing_pid = i->pid;
421                                                 timing_pid_type = -1;
422                                         }
423                                 
424                                         if (i != program.audioStreams.begin())
425                                                 eDebugNoNewLine(", ");
426                                         eDebugNoNewLine("%04x", i->pid);
427                                 }
428                                 eDebugNoNewLine(")");
429                         }
430                         if (!program.subtitleStreams.empty())
431                         {
432                                 eDebugNoNewLine(" (");
433                                 for (std::vector<eDVBServicePMTHandler::subtitleStream>::const_iterator
434                                         i(program.subtitleStreams.begin());
435                                         i != program.subtitleStreams.end(); ++i)
436                                 {
437                                         pids_to_record.insert(i->pid);
438         
439                                         if (i != program.subtitleStreams.begin())
440                                                 eDebugNoNewLine(", ");
441                                         eDebugNoNewLine("%04x", i->pid);
442                                 }
443                                 eDebugNoNewLine(")");
444                         }
445                         eDebugNoNewLine(", and the pcr pid is %04x", program.pcrPid);
446                         if (program.pcrPid != 0x1fff)
447                                 pids_to_record.insert(program.pcrPid);
448                         eDebug(", and the text pid is %04x", program.textPid);
449                         if (program.textPid != -1)
450                                 pids_to_record.insert(program.textPid); // Videotext
451
452                         if (m_record_ecm)
453                         {
454                                 for (std::list<eDVBServicePMTHandler::program::capid_pair>::const_iterator i(program.caids.begin());
455                                                         i != program.caids.end(); ++i)
456                                 {
457                                         if (i->capid >= 0) pids_to_record.insert(i->capid);
458                                 }
459                                 pids_to_record.insert(EventInformationSection::PID);
460                                 pids_to_record.insert(TimeAndDateSection::PID);
461                         }
462
463                         {
464                                 int isCrypted = (int)program.isCrypted();
465                                 int scrambled = !m_descramble;
466                                 if (!m_descramble)
467                                 {
468                                         scrambled = isCrypted;
469                                 }
470                                 else if (!isCrypted)
471                                 {
472                                         scrambled = 0;
473                                 }
474                                 else // m_descramble && isCrypted
475                                 {
476                                         if (!m_service_handler.isCiConnected())
477                                                 scrambled = 1;
478                                         else
479                                                 scrambled = 0;
480                                 }
481                                 eDVBMetaParser dvbParser;
482                                 dvbParser.parseFile(m_filename);
483                                 dvbParser.m_scrambled = scrambled;
484                                 dvbParser.updateMeta(m_filename);
485                         }
486
487                                 /* find out which pids are NEW and which pids are obsolete.. */
488                         std::set<int> new_pids, obsolete_pids;
489
490                         std::set_difference(pids_to_record.begin(), pids_to_record.end(), 
491                                         m_pids_active.begin(), m_pids_active.end(),
492                                         std::inserter(new_pids, new_pids.begin()));
493
494                         std::set_difference(
495                                         m_pids_active.begin(), m_pids_active.end(),
496                                         pids_to_record.begin(), pids_to_record.end(), 
497                                         std::inserter(obsolete_pids, obsolete_pids.begin())
498                                         );
499                         
500                         for (std::set<int>::iterator i(new_pids.begin()); i != new_pids.end(); ++i)
501                         {
502                                 eDebug("ADD PID: %04x", *i);
503                                 m_record->addPID(*i);
504                         }
505
506                         for (std::set<int>::iterator i(obsolete_pids.begin()); i != obsolete_pids.end(); ++i)
507                         {
508                                 eDebug("REMOVED PID: %04x", *i);
509                                 m_record->removePID(*i);
510                         }
511
512                         if (timing_pid != -1)
513                                 m_record->setTimingPID(timing_pid, timing_pid_type);
514
515                         m_pids_active = pids_to_record;
516
517                         if (m_state != stateRecording)
518                         {
519                                 m_record->start();
520                                 m_state = stateRecording;
521                         }
522                 }
523         }
524         m_error = 0;
525         m_event((iRecordableService*)this, evRecordRunning);
526         return 0;
527 }
528
529 void eDVBServiceRecord::updateDecoder()
530 {
531         int vpid = -1, vpidtype = -1, apid = -1, apidtype = -1, pcrpid = -1;
532
533         eDVBServicePMTHandler &h = m_service_handler;
534
535         eDVBServicePMTHandler::program program;
536         if (m_service_handler.getProgramInfo(program))
537                 eDebug("getting program info failed.");
538         else
539         {
540                 eDebugNoNewLine("have %zd video stream(s)", program.videoStreams.size());
541                 if (!program.videoStreams.empty())
542                 {
543                         eDebugNoNewLine(" (");
544                         for (std::vector<eDVBServicePMTHandler::videoStream>::const_iterator
545                                 i(program.videoStreams.begin());
546                                 i != program.videoStreams.end(); ++i)
547                         {
548                                 if (vpid == -1)
549                                 {
550                                         vpid = i->pid;
551                                         vpidtype = i->type;
552                                 }
553                                 if (i != program.videoStreams.begin())
554                                         eDebugNoNewLine(", ");
555                                 eDebugNoNewLine("%04x", i->pid);
556                         }
557                         eDebugNoNewLine(")");
558                 }
559
560                 eDebugNoNewLine(", and %zd audio stream(s)", program.audioStreams.size());
561                 if (!program.audioStreams.empty())
562                 {
563                         eDebugNoNewLine(" (");
564                         for (std::vector<eDVBServicePMTHandler::audioStream>::const_iterator
565                                 i(program.audioStreams.begin());
566                                 i != program.audioStreams.end(); ++i)
567                         {
568                                 if (i != program.audioStreams.begin())
569                                         eDebugNoNewLine(", ");
570                                 eDebugNoNewLine("%04x", i->pid);
571                         }
572                         eDebugNoNewLine(")");
573                 }
574
575                 apid = program.audioStreams[program.defaultAudioStream].pid;
576                 apidtype = program.audioStreams[program.defaultAudioStream].type;
577
578                 eDebugNoNewLine(", and the pcr pid is %04x", program.pcrPid);
579                 pcrpid = program.pcrPid;
580         }
581
582         if (!m_decoder)
583         {
584                 h.getDecodeDemux(m_decode_demux);
585                 if (m_decode_demux)
586                 {
587                         m_decode_demux->getMPEGDecoder(m_decoder, 0);
588                 }
589         }
590
591         if (m_decoder)
592         {
593                 m_decoder->setVideoPID(vpid, vpidtype);
594                 m_decoder->setAudioPID(apid, apidtype);
595                 m_decoder->setSyncPCR(-1);
596                 m_decoder->play();
597         }
598 }
599
600 RESULT eDVBServiceRecord::frontendInfo(ePtr<iFrontendInformation> &ptr)
601 {
602         ptr = this;
603         return 0;
604 }
605
606 RESULT eDVBServiceRecord::connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection)
607 {
608         connection = new eConnection((iRecordableService*)this, m_event.connect(event));
609         return 0;
610 }
611
612 RESULT eDVBServiceRecord::stream(ePtr<iStreamableService> &ptr)
613 {
614         ptr = this;
615         return 0;
616 }
617
618 extern void PutToDict(ePyObject &dict, const char*key, long val);  // defined in dvb/frontend.cpp
619
620 PyObject *eDVBServiceRecord::getStreamingData()
621 {
622         eDVBServicePMTHandler::program program;
623         if (!m_tuned || m_service_handler.getProgramInfo(program))
624         {
625                 Py_RETURN_NONE;
626         }
627
628         ePyObject r = program.createPythonObject();
629         ePtr<iDVBDemux> demux;
630         if (!m_service_handler.getDataDemux(demux))
631         {
632                 uint8_t demux_id;
633                 if (!demux->getCADemuxID(demux_id))
634                         PutToDict(r, "demux", demux_id);
635         }
636
637         return r;
638 }
639
640 void eDVBServiceRecord::recordEvent(int event)
641 {
642         switch (event)
643         {
644         case iDVBTSRecorder::eventWriteError:
645                 eWarning("[eDVBServiceRecord] record write error");
646                 stop();
647                 m_event((iRecordableService*)this, evRecordWriteError);
648                 return;
649         default:
650                 eDebug("unhandled record event %d", event);
651         }
652 }
653
654 void eDVBServiceRecord::gotNewEvent()
655 {
656         ePtr<eServiceEvent> event_now;
657         m_event_handler.getEvent(event_now, 0);
658
659         if (!event_now)
660                 return;
661
662         int event_id = event_now->getEventId();
663
664         pts_t p;
665         
666         if (m_record)
667         {
668                 if (m_record->getCurrentPCR(p))
669                         eDebug("getting PCR failed!");
670                 else
671                 {
672                         m_event_timestamps[event_id] = p;
673                         eDebug("pcr of eit change: %llx", p);
674                 }
675         }
676
677         if (event_id != m_last_event_id)
678                 eDebug("[eDVBServiceRecord] now running: %s (%d seconds)", event_now->getEventName().c_str(), event_now->getDuration());
679         
680         m_last_event_id = event_id;
681
682         m_event((iRecordableService*)this, evNewEventInfo);
683 }
684
685 void eDVBServiceRecord::saveCutlist()
686 {
687                         /* XXX: dupe of eDVBServicePlay::saveCuesheet, refactor plz */
688         std::string filename = m_filename + ".cuts";
689
690         eDVBTSTools tstools;
691         
692         if (tstools.openFile(m_filename.c_str()))
693         {
694                 eDebug("[eDVBServiceRecord] saving cutlist failed because tstools failed");
695                 return;
696         }
697         
698         FILE *f = fopen(filename.c_str(), "wb");
699
700         if (f)
701         {
702                 unsigned long long where;
703                 int what;
704
705                 for (std::map<int,pts_t>::iterator i(m_event_timestamps.begin()); i != m_event_timestamps.end(); ++i)
706                 {
707                         pts_t p = i->second;
708                         off_t offset = 0; // fixme, we need to note down both
709                         if (tstools.fixupPTS(offset, p))
710                         {
711                                 eDebug("[eDVBServiceRecord] fixing up PTS failed, not saving");
712                                 continue;
713                         }
714                         eDebug("fixed up %llx to %llx (offset %llx)", i->second, p, offset);
715 #if BYTE_ORDER == BIG_ENDIAN
716                         where = p;
717 #else
718                         where = bswap_64(p);
719 #endif
720                         what = htonl(2); /* mark */
721                         fwrite(&where, sizeof(where), 1, f);
722                         fwrite(&what, sizeof(what), 1, f);
723                 }
724                 fclose(f);
725         }
726         
727 }
728
729 RESULT eDVBServiceRecord::subServices(ePtr<iSubserviceList> &ptr)
730 {
731         ptr = this;
732         return 0;
733 }
734
735 int eDVBServiceRecord::getNumberOfSubservices()
736 {
737         ePtr<eServiceEvent> evt;
738         if (!m_event_handler.getEvent(evt, 0))
739                 return evt->getNumOfLinkageServices();
740         return 0;
741 }
742
743 RESULT eDVBServiceRecord::getSubservice(eServiceReference &sub, unsigned int n)
744 {
745         ePtr<eServiceEvent> evt;
746         if (!m_event_handler.getEvent(evt, 0))
747         {
748                 if (!evt->getLinkageService(sub, m_ref, n))
749                         return 0;
750         }
751         sub.type=eServiceReference::idInvalid;
752         return -1;
753 }