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