Merge branch 'bug_202_networkconfig_cleanup'
[enigma2.git] / lib / dvb / tstools.cpp
1 #define _ISOC99_SOURCE /* for llabs */
2 #include <lib/dvb/tstools.h>
3 #include <lib/base/eerror.h>
4 #include <unistd.h>
5 #include <fcntl.h>
6
7 #include <stdio.h>
8
9 eDVBTSTools::eDVBTSTools()
10 {
11         m_pid = -1;
12         m_maxrange = 256*1024;
13         
14         m_begin_valid = 0;
15         m_end_valid = 0;
16         
17         m_use_streaminfo = 0;
18         m_samples_taken = 0;
19         
20         m_last_filelength = 0;
21         
22         m_futile = 0;
23 }
24
25 eDVBTSTools::~eDVBTSTools()
26 {
27         closeFile();
28 }
29
30 int eDVBTSTools::openFile(const char *filename, int nostreaminfo)
31 {
32         closeFile();
33         
34         if (!nostreaminfo)
35         {
36                 eDebug("loading streaminfo for %s", filename);
37                 m_streaminfo.load(filename);
38         }
39         
40         if (!m_streaminfo.empty())
41                 m_use_streaminfo = 1;
42         else
43         {
44 //              eDebug("no recorded stream information available");
45                 m_use_streaminfo = 0;
46         }
47         
48         m_samples_taken = 0;
49
50         if (m_file.open(filename, 1) < 0)
51                 return -1;
52         return 0;
53 }
54
55 void eDVBTSTools::closeFile()
56 {
57         m_file.close();
58 }
59
60 void eDVBTSTools::setSyncPID(int pid)
61 {
62         m_pid = pid;
63 }
64
65 void eDVBTSTools::setSearchRange(int maxrange)
66 {
67         m_maxrange = maxrange;
68 }
69
70         /* getPTS extracts a pts value from any PID at a given offset. */
71 int eDVBTSTools::getPTS(off_t &offset, pts_t &pts, int fixed)
72 {
73         if (m_use_streaminfo)
74                 if (!m_streaminfo.getPTS(offset, pts))
75                         return 0;
76         
77         if (!m_file.valid())
78                 return -1;
79
80         offset -= offset % 188;
81         
82         if (m_file.lseek(offset, SEEK_SET) < 0)
83         {
84                 eDebug("lseek failed");
85                 return -1;
86         }
87         
88         int left = m_maxrange;
89         
90         while (left >= 188)
91         {
92                 unsigned char packet[188];
93                 if (m_file.read(packet, 188) != 188)
94                 {
95                         eDebug("read error");
96                         break;
97                 }
98                 left -= 188;
99                 offset += 188;
100                 
101                 if (packet[0] != 0x47)
102                 {
103                         eDebug("resync");
104                         int i = 0;
105                         while (i < 188)
106                         {
107                                 if (packet[i] == 0x47)
108                                         break;
109                                 ++i;
110                         }
111                         offset = m_file.lseek(i - 188, SEEK_CUR);
112                         continue;
113                 }
114                 
115                 int pid = ((packet[1] << 8) | packet[2]) & 0x1FFF;
116                 int pusi = !!(packet[1] & 0x40);
117                 
118 //              printf("PID %04x, PUSI %d\n", pid, pusi);
119
120                 unsigned char *payload;
121                 
122                         /* check for adaption field */
123                 if (packet[3] & 0x20)
124                 {
125                         if (packet[4] >= 183)
126                                 continue;
127                         if (packet[4])
128                         {
129                                 if (packet[5] & 0x10) /* PCR present */
130                                 {
131                                         pts  = ((unsigned long long)(packet[ 6]&0xFF)) << 25;
132                                         pts |= ((unsigned long long)(packet[ 7]&0xFF)) << 17;
133                                         pts |= ((unsigned long long)(packet[ 8]&0xFE)) << 9;
134                                         pts |= ((unsigned long long)(packet[ 9]&0xFF)) << 1;
135                                         pts |= ((unsigned long long)(packet[10]&0x80)) >> 7;
136                                         offset -= 188;
137                                         eDebug("PCR %16llx found at %lld pid %02x (%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x)", pts, offset, pid, packet[0], packet[1], packet[2], packet[3], packet[4], packet[5], packet[6], packet[7], packet[8], packet[9], packet[10]);
138                                         if (fixed && fixupPTS(offset, pts))
139                                                 return -1;
140                                         return 0;
141                                 }
142                         }
143                         payload = packet + packet[4] + 4 + 1;
144                 } else
145                         payload = packet + 4;
146
147                 
148 /*              if (m_pid >= 0)
149                         if (pid != m_pid)
150                                 continue; */
151                 if (!pusi)
152                         continue;
153                 
154                 
155                         /* somehow not a startcode. (this is invalid, since pusi was set.) ignore it. */
156                 if (payload[0] || payload[1] || (payload[2] != 1))
157                         continue;
158                 
159                         /* drop non-audio, non-video packets because other streams
160                            can be non-compliant.*/
161                 if (((payload[3] & 0xE0) != 0xC0) &&  // audio
162                     ((payload[3] & 0xF0) != 0xE0))    // video
163                         continue;
164                 
165                 if (payload[7] & 0x80) /* PTS */
166                 {
167                         pts  = ((unsigned long long)(payload[ 9]&0xE))  << 29;
168                         pts |= ((unsigned long long)(payload[10]&0xFF)) << 22;
169                         pts |= ((unsigned long long)(payload[11]&0xFE)) << 14;
170                         pts |= ((unsigned long long)(payload[12]&0xFF)) << 7;
171                         pts |= ((unsigned long long)(payload[13]&0xFE)) >> 1;
172                         offset -= 188;
173
174                         eDebug("PTS %16llx found at %lld pid %02x stream: %02x", pts, offset, pid, payload[3]);
175
176                                 /* convert to zero-based */
177                         if (fixed && fixupPTS(offset, pts))
178                                 return -1;
179                         return 0;
180                 }
181         }
182         
183         return -1;
184 }
185
186 int eDVBTSTools::fixupPTS(const off_t &offset, pts_t &now)
187 {
188         if (m_use_streaminfo)
189         {
190                 if (!m_streaminfo.fixupPTS(offset, now))
191                         return 0;
192         } else
193         {
194                         /* for the simple case, we assume one epoch, with up to one wrap around in the middle. */
195                 calcBegin();
196                 if (!m_begin_valid)
197                 {       
198                         eDebug("begin not valid, can't fixup");
199                         return -1;
200                 }
201                 
202                 pts_t pos = m_pts_begin;
203                 if ((now < pos) && ((pos - now) < 90000 * 10))
204                 {       
205                         pos = 0;
206                         return 0;
207                 }
208                 
209                 if (now < pos) /* wrap around */
210                         now = now + 0x200000000LL - pos;
211                 else
212                         now -= pos;
213                 return 0;
214         }
215         eDebug("eDVBTSTools::fixupPTS failed!");
216         return -1;
217 }
218
219 int eDVBTSTools::getOffset(off_t &offset, pts_t &pts, int marg)
220 {
221         eDebug("getOffset for pts 0x%llx", pts);
222         if (m_use_streaminfo)
223         {
224                 if (pts >= m_pts_end && marg > 0 && m_end_valid)
225                         offset = m_offset_end;
226                 else
227                         offset = m_streaminfo.getAccessPoint(pts, marg);
228                 return 0;
229         } else
230         {
231                 calcBegin(); calcEnd();
232                 
233                 if (!m_begin_valid)
234                         return -1;
235                 if (!m_end_valid)
236                         return -1;
237
238                 if (!m_samples_taken)
239                         takeSamples();
240                 
241                 if (!m_samples.empty())
242                 {
243                         int maxtries = 5;
244                         pts_t p = -1;
245                         
246                         while (maxtries--)
247                         {
248                                         /* search entry before and after */
249                                 std::map<pts_t, off_t>::const_iterator l = m_samples.lower_bound(pts);
250                                 std::map<pts_t, off_t>::const_iterator u = l;
251
252                                 if (l != m_samples.begin())
253                                         --l;
254                                 
255                                         /* we could have seeked beyond the end */
256                                 if (u == m_samples.end())
257                                 {
258                                                 /* use last segment for interpolation. */
259                                         if (l != m_samples.begin())
260                                         {
261                                                 --u;
262                                                 --l;
263                                         }
264                                 }
265                                         
266                                         /* if we don't have enough points */
267                                 if (u == m_samples.end())
268                                         break;
269                                 
270                                 pts_t pts_diff = u->first - l->first;
271                                 off_t offset_diff = u->second - l->second;
272                                 
273                                 if (offset_diff < 0)
274                                 {
275                                         eDebug("something went wrong when taking samples.");
276                                         m_samples.clear();
277                                         takeSamples();
278                                         continue;
279                                 }
280
281                                 eDebug("using: %llx:%llx -> %llx:%llx", l->first, u->first, l->second, u->second);
282
283                                 int bitrate;
284                                 
285                                 if (pts_diff)
286                                         bitrate = offset_diff * 90000 * 8 / pts_diff;
287                                 else
288                                         bitrate = 0;
289
290                                 offset = l->second;
291                                 offset += ((pts - l->first) * (pts_t)bitrate) / 8ULL / 90000ULL;
292                                 offset -= offset % 188;
293                                 
294                                 p = pts;
295                                 
296                                 if (!takeSample(offset, p))
297                                 {
298                                         int diff = (p - pts) / 90;
299                         
300                                         eDebug("calculated diff %d ms", diff);
301                                         if (abs(diff) > 300)
302                                         {
303                                                 eDebug("diff to big, refining");
304                                                 continue;
305                                         }
306                                 } else
307                                         eDebug("no sample taken, refinement not possible.");
308
309                                 break;
310                         }
311                         
312                                 /* if even the first sample couldn't be taken, fall back. */
313                                 /* otherwise, return most refined result. */
314                         if (p != -1)
315                         {
316                                 pts = p;
317                                 eDebug("aborting. Taking %llx as offset for %lld", offset, pts);
318                                 return 0;
319                         }
320                 }
321                 
322                 int bitrate = calcBitrate();
323                 offset = pts * (pts_t)bitrate / 8ULL / 90000ULL;
324                 eDebug("fallback, bitrate=%d, results in %016llx", bitrate, offset);
325                 offset -= offset % 188;
326                 
327                 return 0;
328         }
329 }
330
331 int eDVBTSTools::getNextAccessPoint(pts_t &ts, const pts_t &start, int direction)
332 {
333         if (m_use_streaminfo)
334                 return m_streaminfo.getNextAccessPoint(ts, start, direction);
335         else
336         {
337                 eDebug("can't get next access point without streaminfo");
338                 return -1;
339         }
340 }
341
342 void eDVBTSTools::calcBegin()
343 {
344         if (!m_file.valid())
345                 return;
346
347         if (!(m_begin_valid || m_futile))
348         {
349                 m_offset_begin = 0;
350                 if (!getPTS(m_offset_begin, m_pts_begin))
351                         m_begin_valid = 1;
352                 else
353                         m_futile = 1;
354         }
355 }
356
357 void eDVBTSTools::calcEnd()
358 {
359         if (!m_file.valid())
360                 return;
361         
362         off_t end = m_file.lseek(0, SEEK_END);
363         
364         if (llabs(end - m_last_filelength) > 1*1024*1024)
365         {
366                 m_last_filelength = end;
367                 m_end_valid = 0;
368                 
369                 m_futile = 0;
370 //              eDebug("file size changed, recalc length");
371         }
372         
373         int maxiter = 10;
374         
375         m_offset_end = m_last_filelength;
376         
377         while (!(m_end_valid || m_futile))
378         {
379                 if (!--maxiter)
380                 {
381                         m_futile = 1;
382                         return;
383                 }
384
385                 m_offset_end -= m_maxrange;
386                 if (m_offset_end < 0)
387                         m_offset_end = 0;
388
389                         /* restore offset if getpts fails */
390                 off_t off = m_offset_end;
391
392                 if (!getPTS(m_offset_end, m_pts_end))
393                         m_end_valid = 1;
394                 else
395                         m_offset_end = off;
396
397                 if (!m_offset_end)
398                 {
399                         m_futile = 1;
400                         break;
401                 }
402         }
403 }
404
405 int eDVBTSTools::calcLen(pts_t &len)
406 {
407         calcBegin(); calcEnd();
408         if (!(m_begin_valid && m_end_valid))
409                 return -1;
410         len = m_pts_end - m_pts_begin;
411                 /* wrap around? */
412         if (len < 0)
413                 len += 0x200000000LL;
414         return 0;
415 }
416
417 int eDVBTSTools::calcBitrate()
418 {
419         calcBegin(); calcEnd();
420         if (!(m_begin_valid && m_end_valid))
421                 return -1;
422
423         pts_t len_in_pts = m_pts_end - m_pts_begin;
424
425                 /* wrap around? */
426         if (len_in_pts < 0)
427                 len_in_pts += 0x200000000LL;
428         off_t len_in_bytes = m_offset_end - m_offset_begin;
429         
430         if (!len_in_pts)
431                 return -1;
432         
433         unsigned long long bitrate = len_in_bytes * 90000 * 8 / len_in_pts;
434         if ((bitrate < 10000) || (bitrate > 100000000))
435                 return -1;
436         
437         return bitrate;
438 }
439
440         /* pts, off */
441 void eDVBTSTools::takeSamples()
442 {
443         m_samples_taken = 1;
444         m_samples.clear();
445         pts_t dummy;
446         int retries=2;
447
448         if (calcLen(dummy) == -1)
449                 return;
450         
451         int nr_samples = 30;
452         off_t bytes_per_sample = (m_offset_end - m_offset_begin) / (long long)nr_samples;
453         if (bytes_per_sample < 40*1024*1024)
454                 bytes_per_sample = 40*1024*1024;
455
456         bytes_per_sample -= bytes_per_sample % 188;
457
458         eDebug("samples step %lld, pts begin %llx, pts end %llx, offs begin %lld, offs end %lld:",
459                 bytes_per_sample, m_pts_begin, m_pts_end, m_offset_begin, m_offset_end);
460
461         for (off_t offset = m_offset_begin; offset < m_offset_end;)
462         {
463                 pts_t p;
464                 if (takeSample(offset, p) && retries--)
465                         continue;
466                 retries = 2;
467                 offset += bytes_per_sample;
468         }
469         m_samples[0] = m_offset_begin;
470         m_samples[m_pts_end - m_pts_begin] = m_offset_end;
471 }
472
473         /* returns 0 when a sample was taken. */
474 int eDVBTSTools::takeSample(off_t off, pts_t &p)
475 {
476         off_t offset_org = off;
477
478         if (!eDVBTSTools::getPTS(off, p, 1))
479         {
480                         /* as we are happily mixing PTS and PCR values (no comment, please), we might
481                            end up with some "negative" segments. 
482                            
483                            so check if this new sample is between the previous and the next field*/
484
485                 std::map<pts_t, off_t>::const_iterator l = m_samples.lower_bound(p);
486                 std::map<pts_t, off_t>::const_iterator u = l;
487
488                 if (l != m_samples.begin())
489                 {
490                         --l;
491                         if (u != m_samples.end())
492                         {
493                                 if ((l->second > off) || (u->second < off))
494                                 {
495                                         eDebug("ignoring sample %lld %lld %lld (%llx %llx %llx)",
496                                                 l->second, off, u->second, l->first, p, u->first);
497                                         return 1;
498                                 }
499                         }
500                 }
501
502                 eDebug("adding sample %lld: pts 0x%llx -> pos %lld (diff %lld bytes)", offset_org, p, off, off-offset_org);
503                 m_samples[p] = off;
504                 return 0;
505         }
506         return -1;
507 }
508
509 int eDVBTSTools::findPMT(int &pmt_pid, int &service_id)
510 {
511                 /* FIXME: this will be factored out soon! */
512         if (!m_file.valid())
513         {
514                 eDebug(" file not valid");
515                 return -1;
516         }
517
518         if (m_file.lseek(0, SEEK_SET) < 0)
519         {
520                 eDebug("seek failed");
521                 return -1;
522         }
523
524         int left = 5*1024*1024;
525         
526         while (left >= 188)
527         {
528                 unsigned char packet[188];
529                 if (m_file.read(packet, 188) != 188)
530                 {
531                         eDebug("read error");
532                         break;
533                 }
534                 left -= 188;
535                 
536                 if (packet[0] != 0x47)
537                 {
538                         int i = 0;
539                         while (i < 188)
540                         {
541                                 if (packet[i] == 0x47)
542                                         break;
543                                 ++i;
544                         }
545                         m_file.lseek(i - 188, SEEK_CUR);
546                         continue;
547                 }
548                 
549                 int pid = ((packet[1] << 8) | packet[2]) & 0x1FFF;
550                 
551                 int pusi = !!(packet[1] & 0x40);
552                 
553                 if (!pusi)
554                         continue;
555                 
556                         /* ok, now we have a PES header or section header*/
557                 unsigned char *sec;
558                 
559                         /* check for adaption field */
560                 if (packet[3] & 0x20)
561                 {
562                         if (packet[4] >= 183)
563                                 continue;
564                         sec = packet + packet[4] + 4 + 1;
565                 } else
566                         sec = packet + 4;
567                 
568                 if (sec[0])     /* table pointer, assumed to be 0 */
569                         continue;
570
571                 if (sec[1] == 0x02) /* program map section */
572                 {
573                         pmt_pid = pid;
574                         service_id = (sec[4] << 8) | sec[5];
575                         return 0;
576                 }
577         }
578         
579         return -1;
580 }
581
582 int eDVBTSTools::findFrame(off_t &_offset, size_t &len, int &direction, int frame_types)
583 {
584         off_t offset = _offset;
585         int nr_frames = 0;
586 //      eDebug("trying to find iFrame at %llx", offset);
587
588         if (!m_use_streaminfo)
589         {
590 //              eDebug("can't get next iframe without streaminfo");
591                 return -1;
592         }
593
594                                 /* let's find the iframe before the given offset */
595         unsigned long long data;
596         
597         if (direction < 0)
598                 offset--;
599
600         while (1)
601         {
602                 if (m_streaminfo.getStructureEntry(offset, data, (direction == 0) ? 1 : 0))
603                 {
604                         eDebug("getting structure info for origin offset failed.");
605                         return -1;
606                 }
607                 if (offset == 0x7fffffffffffffffLL) /* eof */
608                 {
609                         eDebug("reached eof");
610                         return -1;
611                 }
612                         /* data is usually the start code in the lower 8 bit, and the next byte <<8. we extract the picture type from there */
613                         /* we know that we aren't recording startcode 0x09 for mpeg2, so this is safe */
614                         /* TODO: check frame_types */
615                 int is_start = (data & 0xE0FF) == 0x0009; /* H.264 NAL unit access delimiter with I-frame*/
616                 is_start |= (data & 0x3800FF) == 0x080000; /* MPEG2 picture start code with I-frame */
617                 
618                 int is_frame = ((data & 0xFF) == 0x0009) || ((data & 0xFF) == 0x00); /* H.264 UAD or MPEG2 start code */
619                 
620                 if (is_frame)
621                 {
622                         if (direction < 0)
623                                 --nr_frames;
624                         else
625                                 ++nr_frames;
626                 }
627 //              eDebug("%08llx@%llx -> %d, %d", data, offset, is_start, nr_frames);
628                 if (is_start)
629                         break;
630
631                 if (direction == -1)
632                         --offset; /* move to previous entry */
633                 else if (direction == +1)
634                         direction = 0;
635         }
636                         /* let's find the next frame after the given offset */
637         off_t start = offset;
638
639         do {
640                 if (m_streaminfo.getStructureEntry(offset, data, 1))
641                 {
642                         eDebug("get next failed");
643                         return -1;
644                 }
645                 if (offset == 0x7fffffffffffffffLL) /* eof */
646                 {
647                         eDebug("reached eof (while looking for end of iframe)");
648                         return -1;
649                 }
650 //              eDebug("%08llx@%llx (next)", data, offset);
651         } while (((data & 0xFF) != 9) && ((data & 0xFF) != 0x00)); /* next frame */
652
653                         /* align to TS pkt start */
654 //      start = start - (start % 188);
655 //      offset = offset - (offset % 188);
656
657         len = offset - start;
658         _offset = start;
659         direction = nr_frames;
660 //      eDebug("result: offset=%llx, len: %ld", offset, (int)len);
661         return 0;
662 }
663
664 int eDVBTSTools::findNextPicture(off_t &offset, size_t &len, int &distance, int frame_types)
665 {
666         int nr_frames, direction;
667 //      eDebug("trying to move %d frames at %llx", distance, offset);
668         
669         frame_types = frametypeI; /* TODO: intelligent "allow IP frames when not crossing an I-Frame */
670
671         off_t new_offset = offset;
672         size_t new_len = len;
673         int first = 1;
674
675         if (distance > 0) {
676                 direction = 0;
677                 nr_frames = 0;
678         } else {
679                 direction = -1;
680                 nr_frames = -1;
681                 distance = -distance+1;
682         }       
683         while (distance > 0)
684         {
685                 int dir = direction;
686                 if (findFrame(new_offset, new_len, dir, frame_types))
687                 {
688 //                      eDebug("findFrame failed!\n");
689                         return -1;
690                 }
691                 
692                 distance -= abs(dir);
693                 
694 //              eDebug("we moved %d, %d to go frames (now at %llx)", dir, distance, new_offset);
695
696                 if (distance >= 0 || direction == 0)
697                 {
698                         first = 0;
699                         offset = new_offset;
700                         len = new_len;
701                         nr_frames += abs(dir);
702                 } 
703                 else if (first) {
704                         first = 0;
705                         offset = new_offset;
706                         len = new_len;
707                         nr_frames += abs(dir) + distance; // never jump forward during rewind
708                 }
709         }
710
711         distance = (direction < 0) ? -nr_frames : nr_frames;
712 //      eDebug("in total, we moved %d frames", nr_frames);
713
714         return 0;
715 }