By Anders Holst:
[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 {
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  found at %llx: %16llx", offset, pts);
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("found pts %08llx at %08llx 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 }
216
217 int eDVBTSTools::getOffset(off_t &offset, pts_t &pts, int marg)
218 {
219         if (m_use_streaminfo)
220         {
221                 if (pts >= m_pts_end && marg > 0 && m_end_valid)
222                         offset = m_offset_end;
223                 else
224                         offset = m_streaminfo.getAccessPoint(pts, marg);
225                 return 0;
226         } else
227         {
228                 calcBegin(); calcEnd();
229                 
230                 if (!m_begin_valid)
231                         return -1;
232                 if (!m_end_valid)
233                         return -1;
234
235                 if (!m_samples_taken)
236                         takeSamples();
237                 
238                 if (!m_samples.empty())
239                 {
240                         int maxtries = 5;
241                         pts_t p = -1;
242                         
243                         while (maxtries--)
244                         {
245                                         /* search entry before and after */
246                                 std::map<pts_t, off_t>::const_iterator l = m_samples.lower_bound(pts);
247                                 std::map<pts_t, off_t>::const_iterator u = l;
248
249                                 if (l != m_samples.begin())
250                                         --l;
251                                 
252                                         /* we could have seeked beyond the end */
253                                 if (u == m_samples.end())
254                                 {
255                                                 /* use last segment for interpolation. */
256                                         if (l != m_samples.begin())
257                                         {
258                                                 --u;
259                                                 --l;
260                                         }
261                                 }
262                                         
263                                         /* if we don't have enough points */
264                                 if (u == m_samples.end())
265                                         break;
266                                 
267                                 pts_t pts_diff = u->first - l->first;
268                                 off_t offset_diff = u->second - l->second;
269                                 
270                                 if (offset_diff < 0)
271                                 {
272                                         eDebug("something went wrong when taking samples.");
273                                         m_samples.clear();
274                                         takeSamples();
275                                         continue;
276                                 }
277
278                                 eDebug("using: %llx:%llx -> %llx:%llx", l->first, u->first, l->second, u->second);
279
280                                 int bitrate;
281                                 
282                                 if (pts_diff)
283                                         bitrate = offset_diff * 90000 * 8 / pts_diff;
284                                 else
285                                         bitrate = 0;
286
287                                 offset = l->second;
288                                 offset += ((pts - l->first) * (pts_t)bitrate) / 8ULL / 90000ULL;
289                                 offset -= offset % 188;
290                                 
291                                 p = pts;
292                                 
293                                 if (!takeSample(offset, p))
294                                 {
295                                         int diff = (p - pts) / 90;
296                         
297                                         eDebug("calculated diff %d ms", diff);
298                                         if (abs(diff) > 300)
299                                         {
300                                                 eDebug("diff to big, refining");
301                                                 continue;
302                                         }
303                                 } else
304                                         eDebug("no sample taken, refinement not possible.");
305
306                                 break;
307                         }
308                         
309                                 /* if even the first sample couldn't be taken, fall back. */
310                                 /* otherwise, return most refined result. */
311                         if (p != -1)
312                         {
313                                 pts = p;
314                                 eDebug("aborting. Taking %llx as offset for %lld", offset, pts);
315                                 return 0;
316                         }
317                 }
318                 
319                 int bitrate = calcBitrate();
320                 offset = pts * (pts_t)bitrate / 8ULL / 90000ULL;
321                 eDebug("fallback, bitrate=%d, results in %016llx", bitrate, offset);
322                 offset -= offset % 188;
323                 
324                 return 0;
325         }
326 }
327
328 int eDVBTSTools::getNextAccessPoint(pts_t &ts, const pts_t &start, int direction)
329 {
330         if (m_use_streaminfo)
331                 return m_streaminfo.getNextAccessPoint(ts, start, direction);
332         else
333         {
334                 eDebug("can't get next access point without streaminfo");
335                 return -1;
336         }
337 }
338
339 void eDVBTSTools::calcBegin()
340 {
341         if (!m_file.valid())
342                 return;
343
344         if (!(m_begin_valid || m_futile))
345         {
346                 m_offset_begin = 0;
347                 if (!getPTS(m_offset_begin, m_pts_begin))
348                         m_begin_valid = 1;
349                 else
350                         m_futile = 1;
351         }
352 }
353
354 void eDVBTSTools::calcEnd()
355 {
356         if (!m_file.valid())
357                 return;
358         
359         off_t end = m_file.lseek(0, SEEK_END);
360         
361         if (llabs(end - m_last_filelength) > 1*1024*1024)
362         {
363                 m_last_filelength = end;
364                 m_end_valid = 0;
365                 
366                 m_futile = 0;
367 //              eDebug("file size changed, recalc length");
368         }
369         
370         int maxiter = 10;
371         
372         m_offset_end = m_last_filelength;
373         
374         while (!(m_end_valid || m_futile))
375         {
376                 if (!--maxiter)
377                 {
378                         m_futile = 1;
379                         return;
380                 }
381
382                 m_offset_end -= m_maxrange;
383                 if (m_offset_end < 0)
384                         m_offset_end = 0;
385
386                         /* restore offset if getpts fails */
387                 off_t off = m_offset_end;
388
389                 if (!getPTS(m_offset_end, m_pts_end))
390                         m_end_valid = 1;
391                 else
392                         m_offset_end = off;
393
394                 if (!m_offset_end)
395                 {
396                         m_futile = 1;
397                         break;
398                 }
399         }
400 }
401
402 int eDVBTSTools::calcLen(pts_t &len)
403 {
404         calcBegin(); calcEnd();
405         if (!(m_begin_valid && m_end_valid))
406                 return -1;
407         len = m_pts_end - m_pts_begin;
408                 /* wrap around? */
409         if (len < 0)
410                 len += 0x200000000LL;
411         return 0;
412 }
413
414 int eDVBTSTools::calcBitrate()
415 {
416         calcBegin(); calcEnd();
417         if (!(m_begin_valid && m_end_valid))
418                 return -1;
419
420         pts_t len_in_pts = m_pts_end - m_pts_begin;
421
422                 /* wrap around? */
423         if (len_in_pts < 0)
424                 len_in_pts += 0x200000000LL;
425         off_t len_in_bytes = m_offset_end - m_offset_begin;
426         
427         if (!len_in_pts)
428                 return -1;
429         
430         unsigned long long bitrate = len_in_bytes * 90000 * 8 / len_in_pts;
431         if ((bitrate < 10000) || (bitrate > 100000000))
432                 return -1;
433         
434         return bitrate;
435 }
436
437         /* pts, off */
438 void eDVBTSTools::takeSamples()
439 {
440         m_samples_taken = 1;
441         m_samples.clear();
442         pts_t dummy;
443         if (calcLen(dummy) == -1)
444                 return;
445         
446         int nr_samples = 30;
447         off_t bytes_per_sample = (m_offset_end - m_offset_begin) / (long long)nr_samples;
448         if (bytes_per_sample < 40*1024*1024)
449                 bytes_per_sample = 40*1024*1024;
450
451         bytes_per_sample -= bytes_per_sample % 188;
452         
453         for (off_t offset = m_offset_begin; offset < m_offset_end; offset += bytes_per_sample)
454         {
455                 pts_t p;
456                 takeSample(offset, p);
457         }
458         m_samples[0] = m_offset_begin;
459         m_samples[m_pts_end - m_pts_begin] = m_offset_end;
460         
461 //      eDebug("begin, end: %llx %llx", m_offset_begin, m_offset_end); 
462 }
463
464         /* returns 0 when a sample was taken. */
465 int eDVBTSTools::takeSample(off_t off, pts_t &p)
466 {
467         if (!eDVBTSTools::getPTS(off, p, 1))
468         {
469                         /* as we are happily mixing PTS and PCR values (no comment, please), we might
470                            end up with some "negative" segments. 
471                            
472                            so check if this new sample is between the previous and the next field*/
473
474                 std::map<pts_t, off_t>::const_iterator l = m_samples.lower_bound(p);
475                 std::map<pts_t, off_t>::const_iterator u = l;
476
477                 if (l != m_samples.begin())
478                 {
479                         --l;
480                         if (u != m_samples.end())
481                         {
482                                 if ((l->second > off) || (u->second < off))
483                                 {
484                                         eDebug("ignoring sample %llx %llx %llx (%lld %lld %lld)",
485                                                 l->second, off, u->second, l->first, p, u->first);
486                                         return 1;
487                                 }
488                         }
489                 }
490
491                 
492                 m_samples[p] = off;
493                 return 0;
494         }
495         return 1;
496 }
497
498 int eDVBTSTools::findPMT(int &pmt_pid, int &service_id)
499 {
500                 /* FIXME: this will be factored out soon! */
501         if (!m_file.valid())
502         {
503                 eDebug(" file not valid");
504                 return -1;
505         }
506
507         if (m_file.lseek(0, SEEK_SET) < 0)
508         {
509                 eDebug("seek failed");
510                 return -1;
511         }
512
513         int left = 5*1024*1024;
514         
515         while (left >= 188)
516         {
517                 unsigned char packet[188];
518                 if (m_file.read(packet, 188) != 188)
519                 {
520                         eDebug("read error");
521                         break;
522                 }
523                 left -= 188;
524                 
525                 if (packet[0] != 0x47)
526                 {
527                         int i = 0;
528                         while (i < 188)
529                         {
530                                 if (packet[i] == 0x47)
531                                         break;
532                                 ++i;
533                         }
534                         m_file.lseek(i - 188, SEEK_CUR);
535                         continue;
536                 }
537                 
538                 int pid = ((packet[1] << 8) | packet[2]) & 0x1FFF;
539                 
540                 int pusi = !!(packet[1] & 0x40);
541                 
542                 if (!pusi)
543                         continue;
544                 
545                         /* ok, now we have a PES header or section header*/
546                 unsigned char *sec;
547                 
548                         /* check for adaption field */
549                 if (packet[3] & 0x20)
550                 {
551                         if (packet[4] >= 183)
552                                 continue;
553                         sec = packet + packet[4] + 4 + 1;
554                 } else
555                         sec = packet + 4;
556                 
557                 if (sec[0])     /* table pointer, assumed to be 0 */
558                         continue;
559
560                 if (sec[1] == 0x02) /* program map section */
561                 {
562                         pmt_pid = pid;
563                         service_id = (sec[4] << 8) | sec[5];
564                         return 0;
565                 }
566         }
567         
568         return -1;
569 }
570
571 int eDVBTSTools::findFrame(off_t &_offset, size_t &len, int &direction, int frame_types)
572 {
573         off_t offset = _offset;
574         int nr_frames = 0;
575 //      eDebug("trying to find iFrame at %llx", offset);
576
577         if (!m_use_streaminfo)
578         {
579 //              eDebug("can't get next iframe without streaminfo");
580                 return -1;
581         }
582
583                                 /* let's find the iframe before the given offset */
584         unsigned long long data;
585         
586         if (direction < 0)
587                 offset--;
588
589         while (1)
590         {
591                 if (m_streaminfo.getStructureEntry(offset, data, (direction == 0) ? 1 : 0))
592                 {
593                         eDebug("getting structure info for origin offset failed.");
594                         return -1;
595                 }
596                 if (offset == 0x7fffffffffffffffLL) /* eof */
597                 {
598                         eDebug("reached eof");
599                         return -1;
600                 }
601                         /* data is usually the start code in the lower 8 bit, and the next byte <<8. we extract the picture type from there */
602                         /* we know that we aren't recording startcode 0x09 for mpeg2, so this is safe */
603                         /* TODO: check frame_types */
604                 int is_start = (data & 0xE0FF) == 0x0009; /* H.264 NAL unit access delimiter with I-frame*/
605                 is_start |= (data & 0x3800FF) == 0x080000; /* MPEG2 picture start code with I-frame */
606                 
607                 int is_frame = ((data & 0xFF) == 0x0009) || ((data & 0xFF) == 0x00); /* H.264 UAD or MPEG2 start code */
608                 
609                 if (is_frame)
610                 {
611                         if (direction < 0)
612                                 --nr_frames;
613                         else
614                                 ++nr_frames;
615                 }
616 //              eDebug("%08llx@%llx -> %d, %d", data, offset, is_start, nr_frames);
617                 if (is_start)
618                         break;
619
620                 if (direction == -1)
621                         --offset; /* move to previous entry */
622                 else if (direction == +1)
623                         direction = 0;
624         }
625                         /* let's find the next frame after the given offset */
626         off_t start = offset;
627
628         do {
629                 if (m_streaminfo.getStructureEntry(offset, data, 1))
630                 {
631                         eDebug("get next failed");
632                         return -1;
633                 }
634                 if (offset == 0x7fffffffffffffffLL) /* eof */
635                 {
636                         eDebug("reached eof (while looking for end of iframe)");
637                         return -1;
638                 }
639 //              eDebug("%08llx@%llx (next)", data, offset);
640         } while (((data & 0xFF) != 9) && ((data & 0xFF) != 0x00)); /* next frame */
641
642                         /* align to TS pkt start */
643 //      start = start - (start % 188);
644 //      offset = offset - (offset % 188);
645
646         len = offset - start;
647         _offset = start;
648         direction = nr_frames;
649 //      eDebug("result: offset=%llx, len: %ld", offset, (int)len);
650         return 0;
651 }
652
653 int eDVBTSTools::findNextPicture(off_t &offset, size_t &len, int &distance, int frame_types)
654 {
655         int nr_frames, direction;
656 //      eDebug("trying to move %d frames at %llx", distance, offset);
657         
658         frame_types = frametypeI; /* TODO: intelligent "allow IP frames when not crossing an I-Frame */
659
660         off_t new_offset = offset;
661         size_t new_len = len;
662         int first = 1;
663
664         if (distance > 0) {
665                 direction = 0;
666                 nr_frames = 0;
667         } else {
668                 direction = -1;
669                 nr_frames = -1;
670                 distance = -distance+1;
671         }       
672         while (distance > 0)
673         {
674                 int dir = direction;
675                 if (findFrame(new_offset, new_len, dir, frame_types))
676                 {
677 //                      eDebug("findFrame failed!\n");
678                         return -1;
679                 }
680                 
681                 distance -= abs(dir);
682                 
683 //              eDebug("we moved %d, %d to go frames (now at %llx)", dir, distance, new_offset);
684
685                 if (distance >= 0 || direction == 0)
686                 {
687                         first = 0;
688                         offset = new_offset;
689                         len = new_len;
690                         nr_frames += abs(dir);
691                 } 
692                 else if (first) {
693                         first = 0;
694                         offset = new_offset;
695                         len = new_len;
696                         nr_frames += abs(dir) + distance; // never jump forward during rewind
697                 }
698         }
699
700         distance = (direction < 0) ? -nr_frames : nr_frames;
701 //      eDebug("in total, we moved %d frames", nr_frames);
702
703         return 0;
704 }