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