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