root/liboggz/trunk/src/liboggz/oggz_seek.c

Revision 3793, 21.5 kB (checked in by conrad, 2 weeks ago)

revert Ralph's dereference-before-null-check fixes from r3787,
accidentally overwritten in r3791

Line 
1 /*
2    Copyright (C) 2003 Commonwealth Scientific and Industrial Research
3    Organisation (CSIRO) Australia
4
5    Redistribution and use in source and binary forms, with or without
6    modification, are permitted provided that the following conditions
7    are met:
8
9    - Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11
12    - Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15
16    - Neither the name of CSIRO Australia nor the names of its
17    contributors may be used to endorse or promote products derived from
18    this software without specific prior written permission.
19
20    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23    PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ORGANISATION OR
24    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 /*
34  * oggz_seek.c
35  *
36  * Conrad Parker <conrad@annodex.net>
37  */
38
39 #include "config.h"
40
41 #if OGGZ_CONFIG_READ
42
43 #include <assert.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48
49 #ifdef HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52
53 #include <fcntl.h>
54 #include <errno.h>
55 #include <string.h>
56 #include <time.h>
57
58 #include <ogg/ogg.h>
59
60 #include "oggz_compat.h"
61 #include "oggz_private.h"
62
63 /*#define DEBUG*/
64 /*#define DEBUG_VERBOSE*/
65
66 #define CHUNKSIZE 4096
67
68 /*
69  * The typical usage is:
70  *
71  *   oggz_set_data_start (oggz, oggz_tell (oggz));
72  */
73 int
74 oggz_set_data_start (OGGZ * oggz, oggz_off_t offset)
75 {
76   if (oggz == NULL) return -1;
77
78   if (offset < 0) return -1;
79
80   oggz->offset_data_begin = offset;
81
82   return 0;
83 }
84
85 static oggz_off_t
86 oggz_tell_raw (OGGZ * oggz)
87 {
88   oggz_off_t offset_at;
89
90   offset_at = oggz_io_tell (oggz);
91
92   return offset_at;
93 }
94
95 /*
96  * seeks and syncs
97  */
98
99 int
100 oggz_seek_reset_stream(void *data) {
101   ((oggz_stream_t *)data)->last_granulepos = -1L;
102   return 0;
103 }
104
105 static oggz_off_t
106 oggz_seek_raw (OGGZ * oggz, oggz_off_t offset, int whence)
107 {
108   OggzReader  * reader = &oggz->x.reader;
109   oggz_off_t    offset_at;
110
111   if (oggz_io_seek (oggz, offset, whence) == -1) {
112     return -1;
113   }
114
115   offset_at = oggz_io_tell (oggz);
116
117   oggz->offset = offset_at;
118
119   ogg_sync_reset (&reader->ogg_sync);
120
121   oggz_vector_foreach(oggz->streams, oggz_seek_reset_stream);
122  
123   return offset_at;
124 }
125
126 static int
127 oggz_stream_reset (void * data)
128 {
129   oggz_stream_t * stream = (oggz_stream_t *) data;
130
131   if (stream->ogg_stream.serialno != -1) {
132     ogg_stream_reset (&stream->ogg_stream);
133   }
134
135   return 0;
136 }
137
138 static void
139 oggz_reset_streams (OGGZ * oggz)
140 {
141   oggz_vector_foreach (oggz->streams, oggz_stream_reset);
142 }
143
144 static long
145 oggz_reset_seek (OGGZ * oggz, oggz_off_t offset, ogg_int64_t unit, int whence)
146 {
147   OggzReader * reader = &oggz->x.reader;
148
149   oggz_off_t offset_at;
150
151   offset_at = oggz_seek_raw (oggz, offset, whence);
152   if (offset_at == -1) return -1;
153
154   oggz->offset = offset_at;
155
156 #ifdef DEBUG
157   printf ("reset to %" PRI_OGGZ_OFF_T "d\n", offset_at);
158 #endif
159
160   if (unit != -1) reader->current_unit = unit;
161
162   return offset_at;
163 }
164
165 static long
166 oggz_reset (OGGZ * oggz, oggz_off_t offset, ogg_int64_t unit, int whence)
167 {
168   oggz_reset_streams (oggz);
169   return oggz_reset_seek (oggz, offset, unit, whence);
170 }
171
172 int
173 oggz_purge (OGGZ * oggz)
174 {
175   if (oggz == NULL) return OGGZ_ERR_BAD_OGGZ;
176
177   if (oggz->flags & OGGZ_WRITE) {
178     return OGGZ_ERR_INVALID;
179   }
180
181   oggz_reset_streams (oggz);
182
183   if (oggz->file && oggz_reset (oggz, oggz->offset, -1, SEEK_SET) < 0) {
184     return OGGZ_ERR_SYSTEM;
185   }
186
187   return 0;
188 }
189
190 /*
191  * oggz_get_next_page (oggz, og, do_read)
192  *
193  * retrieves the next page.
194  * returns >= 0 if found; return value is offset of page start
195  * returns -1 on error
196  * returns -2 if EOF was encountered
197  */
198 static oggz_off_t
199 oggz_get_next_page (OGGZ * oggz, ogg_page * og)
200 {
201   OggzReader * reader = &oggz->x.reader;
202   char * buffer;
203   long bytes = 0, more;
204   oggz_off_t page_offset = 0, ret;
205   int found = 0;
206
207   do {
208     more = ogg_sync_pageseek (&reader->ogg_sync, og);
209
210     if (more == 0) {
211       page_offset = 0;
212
213       buffer = ogg_sync_buffer (&reader->ogg_sync, CHUNKSIZE);
214       if ((bytes = (long) oggz_io_read (oggz, buffer, CHUNKSIZE)) == 0) {
215         if (oggz->file && feof (oggz->file)) {
216 #ifdef DEBUG_VERBOSE
217           printf ("get_next_page: feof (oggz->file), returning -2\n");
218 #endif
219           clearerr (oggz->file);
220           return -2;
221         }
222       }
223       if (bytes == OGGZ_ERR_SYSTEM) {
224           /*oggz_set_error (oggz, OGGZ_ERR_SYSTEM);*/
225           return -1;
226       }
227
228       if (bytes == 0) {
229 #ifdef DEBUG_VERBOSE
230         printf ("get_next_page: bytes == 0, returning -2\n");
231 #endif
232         return -2;
233       }
234
235       ogg_sync_wrote(&reader->ogg_sync, bytes);
236
237     } else if (more < 0) {
238 #ifdef DEBUG_VERBOSE
239       printf ("get_next_page: skipped %ld bytes\n", -more);
240 #endif
241       page_offset -= more;
242     } else {
243 #ifdef DEBUG_VERBOSE
244       printf ("get_next_page: page has %ld bytes\n", more);
245 #endif
246       found = 1;
247     }
248
249   } while (!found);
250
251   /* Calculate the byte offset of the page which was found */
252   if (bytes > 0) {
253     oggz->offset = oggz_tell_raw (oggz) - bytes + page_offset;
254   } else {
255     /* didn't need to do any reading -- accumulate the page_offset */
256     oggz->offset += page_offset;
257   }
258  
259   ret = oggz->offset + more;
260
261   return ret;
262 }
263
264 static oggz_off_t
265 oggz_get_next_start_page (OGGZ * oggz, ogg_page * og)
266 {
267   oggz_off_t page_offset;
268   int found = 0;
269
270   do {
271     page_offset = oggz_get_next_page (oggz, og);
272
273     /* Return this value if one of the following conditions is met:
274      *
275      *   page_offset < 0     : error or EOF
276      *   page_offset == 0    : start of stream
277      *   !ogg_page_continued : page contains start of a packet
278      *   ogg_page_packets > 1: page contains start of a packet
279      */
280     /*if (page_offset <= 0 || !ogg_page_continued (og) ||
281         ogg_page_packets (og) > 1)*/
282     if (page_offset <= 0 || ogg_page_granulepos(og) > -1)
283       found = 1;
284   }
285   while (!found);
286
287   return page_offset;
288 }
289
290 static oggz_off_t
291 oggz_get_prev_start_page (OGGZ * oggz, ogg_page * og,
292                          ogg_int64_t * granule, long * serialno)
293 {
294   oggz_off_t offset_at, offset_start;
295   oggz_off_t page_offset, found_offset = 0;
296   ogg_int64_t unit_at;
297   ogg_int64_t granule_at = -1;
298
299 #if 0
300   offset_at = oggz_tell_raw (oggz);
301   if (offset_at == -1) return -1;
302 #else
303   offset_at = oggz->offset;
304 #endif
305
306   offset_start = offset_at;
307
308   do {
309
310     offset_start = offset_start - CHUNKSIZE;
311     if (offset_start < 0) offset_start = 0;
312
313     offset_start = oggz_seek_raw (oggz, offset_start, SEEK_SET);
314     if (offset_start == -1) return -1;
315
316 #ifdef DEBUG
317
318     printf ("get_prev_start_page: [A] offset_at: @%" PRI_OGGZ_OFF_T "d\toffset_start: @%" PRI_OGGZ_OFF_T "d\n",
319             offset_at, offset_start);
320
321     printf ("get_prev_start_page: seeked to %" PRI_OGGZ_OFF_T "d\n", offset_start);
322 #endif
323
324     page_offset = 0;
325
326     do {
327       page_offset = oggz_get_next_start_page (oggz, og);
328       if (page_offset == -1) {
329 #ifdef DEBUG
330         printf ("get_prev_start_page: page_offset = -1\n");
331 #endif
332         return -1;
333       }
334       if (page_offset == -2) {
335 #ifdef DEBUG
336         printf ("*** get_prev_start_page: page_offset = -2\n");
337 #endif
338         break;
339       }
340
341       granule_at = ogg_page_granulepos (og);
342
343 #ifdef DEBUG_VERBOSE
344       printf ("get_prev_start_page: GOT page (%lld) @%" PRI_OGGZ_OFF_T "d\tat @%" PRI_OGGZ_OFF_T  "d\n",
345               granule_at, page_offset, offset_at);
346 #endif
347
348       /* Need to stash the granule and serialno of this page because og
349        * will be overwritten by the time we realise this was the desired
350        * prev page */
351       if (page_offset >= 0 && page_offset < offset_at) {
352         found_offset = page_offset;
353         *granule = granule_at;
354         *serialno = ogg_page_serialno (og);
355       }
356
357     } while (page_offset >= 0 && page_offset < offset_at);
358
359 #ifdef DEBUG
360     printf ("get_prev_start_page: [B] offset_at: @%" PRI_OGGZ_OFF_T "d\toffset_start: @%" PRI_OGGZ_OFF_T "d\n"
361             "found_offset: @%" PRI_OGGZ_OFF_T "d\tpage_offset: @%" PRI_OGGZ_OFF_T "d\n",
362             offset_at, offset_start, found_offset, page_offset);
363 #endif
364     /* reset the file offset */
365     /*offset_at = offset_start;*/
366
367   } while (found_offset == 0 && offset_start > 0);
368
369   unit_at = oggz_get_unit (oggz, *serialno, *granule);
370   offset_at = oggz_reset (oggz, found_offset, unit_at, SEEK_SET);
371
372 #ifdef DEBUG
373     printf ("get_prev_start_page: [C] offset_at: @%" PRI_OGGZ_OFF_T "d\t"
374             "found_offset: @%" PRI_OGGZ_OFF_T "d\tunit_at: %lld\n",
375             offset_at, found_offset, unit_at);
376 #endif
377
378   if (offset_at == -1) return -1;
379
380   if (offset_at >= 0)
381     return found_offset;
382   else
383     return -1;
384 }
385
386 static oggz_off_t
387 oggz_scan_for_page (OGGZ * oggz, ogg_page * og, ogg_int64_t unit_target,
388                    oggz_off_t offset_begin, oggz_off_t offset_end)
389 {
390   oggz_off_t offset_at, offset_next;
391   oggz_off_t offset_prev = -1;
392   ogg_int64_t granule_at;
393   ogg_int64_t unit_at;
394   long serialno;
395
396 #ifdef DEBUG
397   printf (" SCANNING from %" PRI_OGGZ_OFF_T "d...", offset_begin);
398 #endif
399
400   for ( ; ; ) {
401     offset_at = oggz_seek_raw (oggz, offset_begin, SEEK_SET);
402     if (offset_at == -1) return -1;
403
404 #ifdef DEBUG
405     printf (" scan @%" PRI_OGGZ_OFF_T "d\n", offset_at);
406 #endif
407
408     offset_next = oggz_get_next_start_page (oggz, og);
409
410     if (offset_next < 0) {
411       return offset_next;
412     }
413
414     if (offset_next == 0 && offset_begin != 0) {
415 #ifdef DEBUG
416       printf (" ... scanned past EOF\n");
417 #endif
418       return -1;
419     }
420     if (offset_next > offset_end) {
421 #ifdef DEBUG
422       printf (" ... scanned to page %ld\n", (long)ogg_page_granulepos (og));
423 #endif
424
425 #if 0
426       if (offset_prev != -1) {
427         offset_at = oggz_seek_raw (oggz, offset_prev, SEEK_SET);
428         if (offset_at == -1) return -1;
429
430         offset_next = oggz_get_next_start_page (oggz, og);
431         if (offset_next < 0) return offset_next;
432
433         serialno = ogg_page_serialno (og);
434         granule_at = ogg_page_granulepos (og);
435         unit_at = oggz_get_unit (oggz, serialno, granule_at);
436
437         return offset_at;
438       } else {
439         return -1;
440       }
441 #else
442       serialno = ogg_page_serialno (og);
443       granule_at = ogg_page_granulepos (og);
444       unit_at = oggz_get_unit (oggz, serialno, granule_at);
445      
446       return offset_at;
447 #endif
448     }
449
450     offset_at = offset_next;
451
452     serialno = ogg_page_serialno (og);
453     granule_at = ogg_page_granulepos (og);
454     unit_at = oggz_get_unit (oggz, serialno, granule_at);
455
456     if (unit_at < unit_target) {
457 #ifdef DEBUG
458       printf (" scan: (%lld) < (%lld)\n", unit_at, unit_target);
459 #endif
460       offset_prev = offset_next;
461       offset_begin = offset_next+1;
462     } else if (unit_at > unit_target) {
463 #ifdef DEBUG
464       printf (" scan: (%lld) > (%lld)\n", unit_at, unit_target);
465 #endif
466 #if 0
467       /* hole ? */
468       offset_at = oggz_seek_raw (oggz, offset_begin, SEEK_SET);
469       if (offset_at == -1) return -1;
470
471       offset_next = oggz_get_next_start_page (oggz, og);
472       if (offset_next < 0) return offset_next;
473
474       serialno = ogg_page_serialno (og);
475       granule_at = ogg_page_granulepos (og);
476       unit_at = oggz_get_unit (oggz, serialno, granule_at);
477
478       break;
479 #else
480       do {
481         offset_at = oggz_get_prev_start_page(oggz, og, &granule_at, &serialno);
482         unit_at = oggz_get_unit(oggz, serialno, granule_at);
483       } while (unit_at > unit_target);
484       return offset_at;
485 #endif
486     } else if (unit_at == unit_target) {
487 #ifdef DEBUG
488       printf (" scan: (%lld) == (%lld)\n", unit_at, unit_target);
489 #endif
490       break;
491     }
492   }
493
494   return offset_at;
495 }
496
497 #define GUESS_MULTIPLIER (1<<16)
498
499 static oggz_off_t
500 guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
501        ogg_int64_t unit_begin, ogg_int64_t unit_end,
502        oggz_off_t offset_begin, oggz_off_t offset_end)
503 {
504   ogg_int64_t guess_ratio;
505   oggz_off_t offset_guess;
506
507   if (unit_at == unit_begin) return offset_begin;
508
509   guess_ratio =
510     GUESS_MULTIPLIER * (unit_target - unit_begin) /
511     (unit_at - unit_begin);
512
513 #ifdef DEBUG
514   printf ("oggz_seek::guess: guess_ratio %lld = (%lld - %lld) / (%lld - %lld)\n",
515           guess_ratio, unit_target, unit_begin, unit_at, unit_begin);
516 #endif
517  
518   offset_guess = offset_begin +
519     (oggz_off_t)(((offset_end - offset_begin) * guess_ratio) /
520                  GUESS_MULTIPLIER);
521  
522   return offset_guess;
523 }
524
525 static oggz_off_t
526 oggz_seek_guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
527                  ogg_int64_t unit_begin, ogg_int64_t unit_end,
528                  oggz_off_t offset_at,
529                  oggz_off_t offset_begin, oggz_off_t offset_end)
530 {
531   oggz_off_t offset_guess;
532
533   if (unit_at == unit_begin) {
534     offset_guess = offset_begin + (offset_end - offset_begin)/2;
535   } else if (unit_end == -1) {
536     offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
537                           offset_begin, offset_at);
538   } else if (unit_end <= unit_begin) {
539 #ifdef DEBUG
540     printf ("oggz_seek_guess: unit_end <= unit_begin (ERROR)\n");
541 #endif
542     offset_guess = -1;
543   } else {
544     offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
545                           offset_begin, offset_end);
546   }
547
548 #ifdef DEBUG
549     printf ("oggz_seek_guess: guessed %" PRI_OGGZ_OFF_T "d\n", offset_guess);
550 #endif
551
552   return offset_guess;
553 }
554
555 static oggz_off_t
556 oggz_offset_end (OGGZ * oggz)
557 {
558   int fd;
559   struct stat statbuf;
560   oggz_off_t offset_end = -1;
561
562   if (oggz->file != NULL) {
563     if ((fd = fileno (oggz->file)) == -1) {
564       /*oggz_set_error (oggz, OGGZ_ERR_SYSTEM);*/
565       return -1;
566     }
567
568     if (fstat (fd, &statbuf) == -1) {
569       /*oggz_set_error (oggz, OGGZ_ERR_SYSTEM);*/
570       return -1;
571     }
572
573     if (oggz_stat_regular (statbuf.st_mode)) {
574       offset_end = statbuf.st_size;
575 #ifdef DEBUG
576       printf ("oggz_offset_end: stat size %" PRI_OGGZ_OFF_T "d\n", offset_end);
577 #endif
578     } else {
579       /*oggz_set_error (oggz, OGGZ_ERR_NOSEEK);*/
580
581       /* XXX: should be able to just carry on and guess, as per io */
582       /*return -1;*/
583     }
584   } else {
585     oggz_off_t offset_save;
586
587     if (oggz->io == NULL || oggz->io->seek == NULL) {
588       /* No file, and no io seek method */
589       return -1;
590     }
591
592     /* Get the offset of the end by querying the io seek method */
593     offset_save = oggz_io_tell (oggz);
594     if (oggz_io_seek (oggz, 0, SEEK_END) == -1) {
595       return -1;
596     }
597     offset_end = oggz_io_tell (oggz);
598     if (oggz_io_seek (oggz, offset_save, SEEK_SET) == -1) {
599       return -1; /* fubar */
600     }
601   }
602
603   return offset_end;
604 }
605
606 static ogg_int64_t
607 oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
608 {
609   OggzReader * reader;
610   oggz_off_t offset_orig, offset_at, offset_guess;
611   oggz_off_t offset_begin, offset_end = -1, offset_next;
612   ogg_int64_t granule_at;
613   ogg_int64_t unit_at, unit_begin = 0