optional cached (i.e. fopen,...) support for raw files
[vuplus_dvbapp] / lib / dvb / tstools.cpp
1 #include <lib/dvb/tstools.h>
2 #include <lib/base/eerror.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5
6 #include <stdio.h>
7
8 eDVBTSTools::eDVBTSTools()
9 {
10         m_pid = -1;
11         m_maxrange = 256*1024;
12         
13         m_begin_valid = 0;
14         m_end_valid = 0;
15         
16         m_use_streaminfo = 0;
17         m_samples_taken = 0;
18 }
19
20 eDVBTSTools::~eDVBTSTools()
21 {
22         closeFile();
23 }
24
25 int eDVBTSTools::openFile(const char *filename)
26 {
27         closeFile();
28         
29         m_streaminfo.load((std::string(filename) + ".ap").c_str());
30         
31         if (!m_streaminfo.empty())
32                 m_use_streaminfo = 1;
33         else
34         {
35                 eDebug("no recorded stream information available");
36                 m_use_streaminfo = 0;
37         }
38         
39         m_samples_taken = 0;
40
41         if (m_file.open(filename, 1) < 0)
42                 return -1;
43         return 0;
44 }
45
46 void eDVBTSTools::closeFile()
47 {
48         m_file.close();
49 }
50
51 void eDVBTSTools::setSyncPID(int pid)
52 {
53         m_pid = pid;
54 }
55
56 void eDVBTSTools::setSearchRange(int maxrange)
57 {
58         m_maxrange = maxrange;
59 }
60
61         /* getPTS extracts a pts value from any PID at a given offset. */
62 int eDVBTSTools::getPTS(off_t &offset, pts_t &pts, int fixed)
63 {
64         if (m_use_streaminfo)
65                 return m_streaminfo.getPTS(offset, pts);
66         
67         if (!m_file.valid())
68                 return -1;
69
70         offset -= offset % 188;
71         
72         if (m_file.lseek(offset, SEEK_SET) < 0)
73                 return -1;
74
75         int left = m_maxrange;
76         
77         while (left >= 188)
78         {
79                 unsigned char block[188];
80                 if (m_file.read(block, 188) != 188)
81                 {
82                         eDebug("read error");
83                         break;
84                 }
85                 left -= 188;
86                 offset += 188;
87                 
88                 if (block[0] != 0x47)
89                 {
90                         int i = 0;
91                         while (i < 188)
92                         {
93                                 if (block[i] == 0x47)
94                                         break;
95                                 ++i;
96                         }
97                         offset = m_file.lseek(i - 188, SEEK_CUR);
98                         continue;
99                 }
100                 
101                 int pid = ((block[1] << 8) | block[2]) & 0x1FFF;
102                 int pusi = !!(block[1] & 0x40);
103                 
104 //              printf("PID %04x, PUSI %d\n", pid, pusi);
105                 
106                 if (m_pid >= 0)
107                         if (pid != m_pid)
108                                 continue;
109                 if (!pusi)
110                         continue;
111                 
112                         /* ok, now we have a PES header */
113                 unsigned char *pes;
114                 
115                         /* check for adaption field */
116                 if (block[3] & 0x20)
117                         pes = block + block[4] + 4 + 1;
118                 else
119                         pes = block + 4;
120                 
121                         /* somehow not a startcode. (this is invalid, since pusi was set.) ignore it. */
122                 if (pes[0] || pes[1] || (pes[2] != 1))
123                         continue;
124                 
125                 if (pes[7] & 0x80) /* PTS */
126                 {
127                         pts  = ((unsigned long long)(pes[ 9]&0xE))  << 29;
128                         pts |= ((unsigned long long)(pes[10]&0xFF)) << 22;
129                         pts |= ((unsigned long long)(pes[11]&0xFE)) << 14;
130                         pts |= ((unsigned long long)(pes[12]&0xFF)) << 7;
131                         pts |= ((unsigned long long)(pes[13]&0xFE)) >> 1;
132                         offset -= 188;
133                         
134 //                      eDebug("found pts %08llx at %08llx", pts, offset);
135                         
136                                 /* convert to zero-based */
137                         if (fixed)
138                                 fixupPTS(offset, pts);
139                         return 0;
140                 }
141         }
142         
143         return -1;
144 }
145
146 int eDVBTSTools::fixupPTS(const off_t &offset, pts_t &now)
147 {
148         if (m_use_streaminfo)
149         {
150                 return m_streaminfo.fixupPTS(offset, now);
151         } else
152         {
153                         /* for the simple case, we assume one epoch, with up to one wrap around in the middle. */
154                 calcBegin();
155                 if (!m_begin_valid)
156                 {       
157                         eDebug("begin not valid, can't fixup");
158                         return -1;
159                 }
160                 
161                 pts_t pos = m_pts_begin;
162                 if ((now < pos) && ((pos - now) < 90000 * 10))
163                 {       
164                         pos = 0;
165                         return 0;
166                 }
167                 
168                 if (now < pos) /* wrap around */
169                         now = now + 0x200000000LL - pos;
170                 else
171                         now -= pos;
172                 return 0;
173         }
174 }
175
176 int eDVBTSTools::getOffset(off_t &offset, pts_t &pts)
177 {
178         if (m_use_streaminfo)
179         {
180                 offset = m_streaminfo.getAccessPoint(pts);
181                 return 0;
182         } else
183         {
184 //              eDebug("get offset");
185                 if (!m_samples_taken)
186                         takeSamples();
187                 
188                 if (!m_samples.empty())
189                 {
190 //              eDebug("ok, samples ok");
191                                 /* search entry before and after */
192                         std::map<pts_t, off_t>::const_iterator l = m_samples.lower_bound(pts);
193                         std::map<pts_t, off_t>::const_iterator u = l;
194
195                         if (l != m_samples.begin())
196                                 --l;
197                 
198                         if ((u != m_samples.end()) && (l != m_samples.end()))
199                         {
200                                 pts_t pts_diff = u->first - l->first;
201                                 off_t offset_diff = u->second - l->second;
202 //              eDebug("using: %llx:%llx -> %llx:%llx", l->first, u->first, l->second, u->second);
203         
204                                 if (pts_diff)
205                                 {
206                                         int bitrate = offset_diff * 90000 * 8 / pts_diff;
207                                         if (bitrate > 0)
208                                         {
209                                                 offset = l->second;
210                                                 offset += ((pts - l->first) * (pts_t)bitrate) / 8ULL / 90000ULL;
211                                                 offset -= offset % 188;
212                                                 return 0;
213                                         }
214                                 }
215                         }
216                 }
217                 
218                 eDebug("falling back");
219                 int bitrate = calcBitrate();
220                 offset = pts * (pts_t)bitrate / 8ULL / 90000ULL;
221                 offset -= offset % 188;
222
223                 return 0;
224         }
225 }
226
227 int eDVBTSTools::getNextAccessPoint(pts_t &ts, const pts_t &start, int direction)
228 {
229         if (m_use_streaminfo)
230                 return m_streaminfo.getNextAccessPoint(ts, start, direction);
231         else
232         {
233                 eDebug("can't get next access point without streaminfo");
234                 return -1;
235         }
236 }
237
238 void eDVBTSTools::calcBegin()
239 {
240         if (!m_file.valid())
241                 return;
242
243         if (!m_begin_valid)
244         {
245                 m_offset_begin = 0;
246                 if (!getPTS(m_offset_begin, m_pts_begin))
247                         m_begin_valid = 1;
248         }
249 }
250
251 void eDVBTSTools::calcEnd()
252 {
253         if (!m_file.valid())
254                 return;
255         
256         off_t end = m_file.lseek(0, SEEK_END);
257         
258         if (abs(end - m_offset_end) > 1*1024*1024)
259         {
260                 m_offset_end = end;
261                 m_end_valid = 0;
262                 eDebug("file size changed, recalc length");
263         }
264         
265         int maxiter = 10;
266         
267         while (!m_end_valid)
268         {
269                 if (!--maxiter)
270                         return;
271                 
272                 m_offset_end -= m_maxrange;
273                 if (m_offset_end < 0)
274                         m_offset_end = 0;
275                 if (!getPTS(m_offset_end, m_pts_end))
276                         m_end_valid = 1;
277                 if (!m_offset_end)
278                         return;
279         }
280 }
281
282 int eDVBTSTools::calcLen(pts_t &len)
283 {
284         calcBegin(); calcEnd();
285         if (!(m_begin_valid && m_end_valid))
286                 return -1;
287         len = m_pts_end - m_pts_begin;
288                 /* wrap around? */
289         if (len < 0)
290                 len += 0x200000000LL;
291         return 0;
292 }
293
294 int eDVBTSTools::calcBitrate()
295 {
296         calcBegin(); calcEnd();
297         if (!(m_begin_valid && m_end_valid))
298                 return -1;
299
300         pts_t len_in_pts = m_pts_end - m_pts_begin;
301
302                 /* wrap around? */
303         if (len_in_pts < 0)
304                 len_in_pts += 0x200000000LL;
305         off_t len_in_bytes = m_offset_end - m_offset_begin;
306         
307         if (!len_in_pts)
308                 return -1;
309         
310         unsigned long long bitrate = len_in_bytes * 90000 * 8 / len_in_pts;
311         if ((bitrate < 10000) || (bitrate > 100000000))
312                 return -1;
313         
314         return bitrate;
315 }
316
317         /* pts, off */
318 void eDVBTSTools::takeSamples()
319 {
320         m_samples_taken = 1;
321         m_samples.clear();
322         pts_t dummy;
323         if (calcLen(dummy) == -1)
324                 return;
325         
326         int nr_samples = 30;
327         off_t bytes_per_sample = (m_offset_end - m_offset_begin) / (long long)nr_samples;
328         if (bytes_per_sample < 40*1024*1024)
329                 bytes_per_sample = 40*1024*1024;
330         
331         bytes_per_sample -= bytes_per_sample % 188;
332         
333         for (off_t offset = m_offset_begin; offset < m_offset_end; offset += bytes_per_sample)
334         {
335                 off_t o = offset;
336                 pts_t p;
337                 if (!eDVBTSTools::getPTS(o, p, 1))
338                 {
339 //                      eDebug("sample: %llx, %llx", o, p);
340                         m_samples[p] = o;
341                 }
342         }
343         m_samples[m_pts_begin] = m_offset_begin;
344         m_samples[m_pts_end] = m_offset_end;
345 //      eDebug("begin, end: %llx %llx", m_offset_begin, m_offset_end); 
346 }
347
348 int eDVBTSTools::findPMT(int &pmt_pid, int &service_id)
349 {
350                 /* FIXME: this will be factored out soon! */
351         if (!m_file.valid())
352         {
353                 eDebug(" file not valid");
354                 return -1;
355         }
356
357         if (m_file.lseek(0, SEEK_SET) < 0)
358         {
359                 eDebug("seek failed");
360                 return -1;
361         }
362
363         int left = 5*1024*1024;
364         
365         while (left >= 188)
366         {
367                 unsigned char block[188];
368                 if (m_file.read(block, 188) != 188)
369                 {
370                         eDebug("read error");
371                         break;
372                 }
373                 left -= 188;
374                 
375                 if (block[0] != 0x47)
376                 {
377                         int i = 0;
378                         while (i < 188)
379                         {
380                                 if (block[i] == 0x47)
381                                         break;
382                                 ++i;
383                         }
384                         m_file.lseek(i - 188, SEEK_CUR);
385                         continue;
386                 }
387                 
388                 int pid = ((block[1] << 8) | block[2]) & 0x1FFF;
389                 
390                 int pusi = !!(block[1] & 0x40);
391                 
392                 if (!pusi)
393                         continue;
394                 
395                         /* ok, now we have a PES header or section header*/
396                 unsigned char *sec;
397                 
398                         /* check for adaption field */
399                 if (block[3] & 0x20)
400                         sec = block + block[4] + 4 + 1;
401                 else
402                         sec = block + 4;
403                 
404                 if (sec[0])     /* table pointer, assumed to be 0 */
405                         continue;
406
407                 if (sec[1] == 0x02) /* program map section */
408                 {
409                         pmt_pid = pid;
410                         service_id = (sec[4] << 8) | sec[5];
411                         return 0;
412                 }
413         }
414         
415         return -1;
416 }