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