save timing information after record
[openblackhole/openblackhole-enigma2.git] / lib / dvb / demux.cpp
1 #include <stdio.h>
2 #include <fcntl.h>
3 #include <sys/ioctl.h>
4 #include <errno.h>
5 #include <unistd.h>
6 #include <signal.h>
7
8 #if HAVE_DVB_API_VERSION < 3
9 #include <ost/dmx.h>
10
11 #ifndef DMX_SET_NEGFILTER_MASK
12         #define DMX_SET_NEGFILTER_MASK   _IOW('o',48,uint8_t *)
13 #endif
14
15 #ifndef DMX_GET_STC
16         struct dmx_stc
17         {
18                 unsigned int num;       /* input : which STC? O..N */
19                 unsigned int base;      /* output: divisor for stc to get 90 kHz clock */
20                 unsigned long long stc; /* output: src in 'base'*90 kHz units */
21         };
22         #define DMX_GET_STC             _IOR('o', 50, struct dmx_stc)
23 #endif
24
25 #else
26 #include <linux/dvb/dmx.h>
27 #endif
28
29 #include "crc32.h"
30
31 #include <lib/base/eerror.h>
32 #include <lib/base/filepush.h>
33 #include <lib/dvb/idvb.h>
34 #include <lib/dvb/demux.h>
35 #include <lib/dvb/esection.h>
36 #include <lib/dvb/decoder.h>
37 #include <lib/dvb/pvrparse.h>
38
39 eDVBDemux::eDVBDemux(int adapter, int demux): adapter(adapter), demux(demux)
40 {
41         m_dvr_busy = 0;
42 }
43
44 eDVBDemux::~eDVBDemux()
45 {
46 }
47
48 int eDVBDemux::openDemux(void)
49 {
50         char filename[128];
51 #if HAVE_DVB_API_VERSION < 3
52         snprintf(filename, 128, "/dev/dvb/card%d/demux%d", adapter, demux);
53 #else
54         snprintf(filename, 128, "/dev/dvb/adapter%d/demux%d", adapter, demux);
55 #endif
56         return ::open(filename, O_RDWR);
57 }
58
59 DEFINE_REF(eDVBDemux)
60
61 RESULT eDVBDemux::setSourceFrontend(int fenum)
62 {
63 #if HAVE_DVB_API_VERSION >= 3
64         int fd = openDemux();
65         
66         int n = DMX_SOURCE_FRONT0 + fenum;
67         int res = ::ioctl(fd, DMX_SET_SOURCE, &n);
68         if (res)
69                 eDebug("DMX_SET_SOURCE failed! - %m");
70         ::close(fd);
71         return res;
72 #endif
73         return 0;
74 }
75
76 RESULT eDVBDemux::setSourcePVR(int pvrnum)
77 {
78 #if HAVE_DVB_API_VERSION >= 3
79         int fd = openDemux();
80         int n = DMX_SOURCE_DVR0 + pvrnum;
81         int res = ::ioctl(fd, DMX_SET_SOURCE, &n);
82         ::close(fd);
83         return res;
84 #endif
85         return 0;
86 }
87
88 RESULT eDVBDemux::createSectionReader(eMainloop *context, ePtr<iDVBSectionReader> &reader)
89 {
90         RESULT res;
91         reader = new eDVBSectionReader(this, context, res);
92         if (res)
93                 reader = 0;
94         return res;
95 }
96
97 RESULT eDVBDemux::createTSRecorder(ePtr<iDVBTSRecorder> &recorder)
98 {
99         if (m_dvr_busy)
100                 return -EBUSY;
101         recorder = new eDVBTSRecorder(this);
102         return 0;
103 }
104
105 RESULT eDVBDemux::getMPEGDecoder(ePtr<iTSMPEGDecoder> &decoder)
106 {
107         decoder = new eTSMPEGDecoder(this, 0);
108         return 0;
109 }
110
111 RESULT eDVBDemux::getSTC(pts_t &pts, int num)
112 {
113         int fd = openDemux();
114         
115         if (fd < 0)
116                 return -ENODEV;
117
118         struct dmx_stc stc;
119         stc.num = num;
120         stc.base = 1;
121         
122         if (ioctl(fd, DMX_GET_STC, &stc) < 0)
123         {
124                 ::close(fd);
125                 return -1;
126         }
127         
128         pts = stc.stc;
129         
130         ::close(fd);
131         return 0;
132 }
133
134 RESULT eDVBDemux::flush()
135 {
136         // FIXME: implement flushing the PVR queue here.
137         
138         m_event(evtFlush);
139         return 0;
140 }
141
142 RESULT eDVBDemux::connectEvent(const Slot1<void,int> &event, ePtr<eConnection> &conn)
143 {
144         conn = new eConnection(this, m_event.connect(event));
145         return 0;
146 }
147
148 void eDVBSectionReader::data(int)
149 {
150         __u8 data[4096]; // max. section size
151         int r;
152         r = ::read(fd, data, 4096);
153         if(r < 0)
154         {
155                 eWarning("ERROR reading section - %m\n");
156                 return;
157         }
158         if (checkcrc)
159         {
160                         // this check should never happen unless the driver is crappy!
161                 unsigned int c;
162                 if ((c = crc32((unsigned)-1, data, r)))
163                 {
164                         eDebug("crc32 failed! is %x\n", c);
165                         return;
166                 }
167         }
168         if (active)
169                 read(data);
170         else
171                 eDebug("data.. but not active");
172 }
173
174 eDVBSectionReader::eDVBSectionReader(eDVBDemux *demux, eMainloop *context, RESULT &res): demux(demux)
175 {
176         char filename[128];
177         fd = demux->openDemux();
178         
179         if (fd >= 0)
180         {
181                 notifier=new eSocketNotifier(context, fd, eSocketNotifier::Read, false);
182                 CONNECT(notifier->activated, eDVBSectionReader::data);
183                 res = 0;
184         } else
185         {
186                 perror(filename);
187                 res = errno;
188         }
189 }
190
191 DEFINE_REF(eDVBSectionReader)
192
193 eDVBSectionReader::~eDVBSectionReader()
194 {
195         if (notifier)
196                 delete notifier;
197         if (fd >= 0)
198                 ::close(fd);
199 }
200
201 RESULT eDVBSectionReader::start(const eDVBSectionFilterMask &mask)
202 {
203         RESULT res;
204         if (fd < 0)
205                 return -ENODEV;
206
207         notifier->start();
208 #if HAVE_DVB_API_VERSION < 3
209         dmxSctFilterParams sct;
210 #else
211         dmx_sct_filter_params sct;
212 #endif
213         sct.pid     = mask.pid;
214         sct.timeout = 0;
215 #if HAVE_DVB_API_VERSION < 3
216         sct.flags   = 0;
217 #else
218         sct.flags   = DMX_IMMEDIATE_START;
219 #endif
220         if (mask.flags & eDVBSectionFilterMask::rfCRC)
221         {
222                 sct.flags |= DMX_CHECK_CRC;
223                 checkcrc = 1;
224         } else
225                 checkcrc = 0;
226         
227         memcpy(sct.filter.filter, mask.data, DMX_FILTER_SIZE);
228         memcpy(sct.filter.mask, mask.mask, DMX_FILTER_SIZE);
229 #if HAVE_DVB_API_VERSION >= 3
230         memcpy(sct.filter.mode, mask.mode, DMX_FILTER_SIZE);
231         if (::ioctl(fd, DMX_SET_BUFFER_SIZE, 8192*8) < 0)
232                 eDebug("DMX_SET_BUFFER_SIZE failed(%m)");
233 #endif
234         
235         res = ::ioctl(fd, DMX_SET_FILTER, &sct);
236         if (!res)
237         {
238 #if HAVE_DVB_API_VERSION < 3
239                 res = ::ioctl(fd, DMX_SET_NEGFILTER_MASK, mask.mode);
240                 if (!res)
241                 {
242                         res = ::ioctl(fd, DMX_START, 0);
243                         if (!res)
244                                 active = 1;
245                 }
246 #else
247                 active = 1;
248 #endif
249         }
250         return res;
251 }
252
253 RESULT eDVBSectionReader::stop()
254 {
255         if (!active)
256                 return -1;
257
258         active=0;
259         ::ioctl(fd, DMX_STOP);
260         notifier->stop();
261
262         return 0;
263 }
264
265 RESULT eDVBSectionReader::connectRead(const Slot1<void,const __u8*> &r, ePtr<eConnection> &conn)
266 {
267         conn = new eConnection(this, read.connect(r));
268         return 0;
269 }
270
271 class eDVBRecordFileThread: public eFilePushThread
272 {
273 public:
274         eDVBRecordFileThread();
275         void setTimingPID(int pid);
276         
277         void saveTimingInformation(const std::string &filename);
278 protected:
279         void filterRecordData(const unsigned char *data, int len);
280 private:
281         eMPEGStreamParserTS m_ts_parser;
282         eMPEGStreamInformation m_stream_info;
283         off_t m_current_offset;
284         int m_pid;
285 };
286
287 eDVBRecordFileThread::eDVBRecordFileThread()
288         : m_ts_parser(m_stream_info)
289 {
290         m_current_offset = 0;
291 }
292
293 void eDVBRecordFileThread::setTimingPID(int pid)
294 {
295         m_ts_parser.setPid(pid);
296 }
297
298 void eDVBRecordFileThread::saveTimingInformation(const std::string &filename)
299 {
300         m_stream_info.save(filename.c_str());
301 }
302
303 void eDVBRecordFileThread::filterRecordData(const unsigned char *data, int len)
304 {
305         m_ts_parser.parseData(m_current_offset, data, len);
306         
307         m_current_offset += len;
308 }
309
310 DEFINE_REF(eDVBTSRecorder);
311
312 eDVBTSRecorder::eDVBTSRecorder(eDVBDemux *demux): m_demux(demux)
313 {
314         m_running = 0;
315         m_target_fd = -1;
316         m_thread = new eDVBRecordFileThread();
317         m_demux->m_dvr_busy = 1;
318 }
319
320 eDVBTSRecorder::~eDVBTSRecorder()
321 {
322         stop();
323         delete m_thread;
324         m_demux->m_dvr_busy = 0;
325 }
326
327 RESULT eDVBTSRecorder::start()
328 {
329         if (m_running)
330                 return -1;
331         
332         if (m_target_fd == -1)
333                 return -2;
334                 
335         char filename[128];
336 #if HAVE_DVB_API_VERSION < 3
337         snprintf(filename, 128, "/dev/dvb/card%d/dvr%d", m_demux->adapter, m_demux->demux);
338 #else
339         snprintf(filename, 128, "/dev/dvb/adapter%d/dvr%d", m_demux->adapter, m_demux->demux);
340 #endif
341         m_source_fd = ::open(filename, O_RDONLY);
342         
343         if (m_source_fd < 0)
344         {
345                 eDebug("FAILED to open dvr (%s) in ts recoder (%m)", filename);
346                 return -3;
347         }
348         
349         m_thread->start(m_source_fd, m_target_fd);
350         m_running = 1;
351         
352         for (std::map<int,int>::iterator i(m_pids.begin()); i != m_pids.end(); ++i)
353                 startPID(i->first);
354         
355         return 0;
356 }
357
358 RESULT eDVBTSRecorder::addPID(int pid)
359 {
360         if (m_pids.find(pid) != m_pids.end())
361                 return -1;
362         
363         m_pids.insert(std::pair<int,int>(pid, -1));
364         if (m_running)
365                 startPID(pid);
366         return 0;
367 }
368
369 RESULT eDVBTSRecorder::removePID(int pid)
370 {
371         if (m_pids.find(pid) == m_pids.end())
372                 return -1;
373                 
374         if (m_running)
375                 stopPID(pid);
376         
377         m_pids.erase(pid);
378         return 0;
379 }
380
381 RESULT eDVBTSRecorder::setTimingPID(int pid)
382 {
383         if (m_running)
384                 return -1;
385         m_thread->setTimingPID(pid);
386         return 0;
387 }
388
389 RESULT eDVBTSRecorder::setTargetFD(int fd)
390 {
391         m_target_fd = fd;
392         return 0;
393 }
394
395 RESULT eDVBTSRecorder::setTargetFilename(const char *filename)
396 {
397         m_target_filename = filename;
398 }
399
400 RESULT eDVBTSRecorder::setBoundary(off_t max)
401 {
402         return -1; // not yet implemented
403 }
404
405 RESULT eDVBTSRecorder::stop()
406 {
407         for (std::map<int,int>::iterator i(m_pids.begin()); i != m_pids.end(); ++i)
408                 stopPID(i->first);
409
410         if (!m_running)
411                 return -1;
412         m_thread->stop();
413         
414         close(m_source_fd);
415         
416         if (m_target_filename != "")
417                 m_thread->saveTimingInformation(m_target_filename + ".ap");
418         
419         return 0;
420 }
421
422 RESULT eDVBTSRecorder::connectEvent(const Slot1<void,int> &event, ePtr<eConnection> &conn)
423 {
424         conn = new eConnection(this, m_event.connect(event));
425         return 0;
426 }
427
428 RESULT eDVBTSRecorder::startPID(int pid)
429 {
430         int fd = m_demux->openDemux();
431         if (fd < 0)
432         {
433                 eDebug("FAILED to open demux in ts recoder (%m)");
434                 return -1;
435         }
436
437 #if HAVE_DVB_API_VERSION < 3
438         dmxPesFilterParams flt;
439         
440         flt.pesType = DMX_PES_OTHER;
441 #else
442         dmx_pes_filter_params flt;
443         
444         flt.pes_type = DMX_PES_OTHER;
445 #endif
446
447         flt.pid     = pid;
448         flt.input   = DMX_IN_FRONTEND;
449         flt.output  = DMX_OUT_TS_TAP;
450         
451         flt.flags   = DMX_IMMEDIATE_START;
452
453         int res = ::ioctl(fd, DMX_SET_PES_FILTER, &flt);
454         if (res < 0)
455         {
456                 eDebug("set pes filter failed!");
457                 ::close(fd);
458                 return -1;
459         }
460         m_pids[pid] = fd;
461
462         return 0;
463 }
464
465 void eDVBTSRecorder::stopPID(int pid)
466 {
467         if (m_pids[pid] != -1)
468                 ::close(m_pids[pid]);
469         m_pids[pid] = -1;
470 }