1 /*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15 */
16
17 #include "ogg.h"
18
19 int
get_ogg_metadata(PerlIO * infile,char * file,HV * info,HV * tags)20 get_ogg_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
21 {
22 return _ogg_parse(infile, file, info, tags, 0);
23 }
24
25 int
_ogg_parse(PerlIO * infile,char * file,HV * info,HV * tags,uint8_t seeking)26 _ogg_parse(PerlIO *infile, char *file, HV *info, HV *tags, uint8_t seeking)
27 {
28 Buffer ogg_buf, vorbis_buf;
29 unsigned char *bptr;
30 unsigned int buf_size;
31
32 unsigned int id3_size = 0; // size of leading ID3 data
33
34 off_t file_size; // total file size
35 off_t audio_size; // total size of audio without tags
36 off_t audio_offset = 0; // offset to audio
37
38 unsigned char ogghdr[28];
39 char header_type;
40 int serialno;
41 int final_serialno;
42 int pagenum;
43 uint8_t num_segments;
44 int pagelen;
45 int page = 0;
46 int packets = 0;
47 int streams = 0;
48
49 unsigned char vorbishdr[23];
50 unsigned char channels;
51 unsigned int blocksize_0 = 0;
52 unsigned int avg_buf_size;
53 unsigned int samplerate = 0;
54 unsigned int bitrate_nominal = 0;
55 uint64_t granule_pos = 0;
56
57 unsigned char vorbis_type = 0;
58
59 int i;
60 int err = 0;
61
62 buffer_init(&ogg_buf, OGG_BLOCK_SIZE);
63 buffer_init(&vorbis_buf, 0);
64
65 file_size = _file_size(infile);
66 my_hv_store( info, "file_size", newSVuv(file_size) );
67
68 if ( !_check_buf(infile, &ogg_buf, 10, OGG_BLOCK_SIZE) ) {
69 err = -1;
70 goto out;
71 }
72
73 // Skip ID3 tags if any
74 bptr = (unsigned char *)buffer_ptr(&ogg_buf);
75 if (
76 (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') &&
77 bptr[3] < 0xff && bptr[4] < 0xff &&
78 bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
79 ) {
80 /* found an ID3 header... */
81 id3_size = 10 + (bptr[6]<<21) + (bptr[7]<<14) + (bptr[8]<<7) + bptr[9];
82
83 if (bptr[5] & 0x10) {
84 // footer present
85 id3_size += 10;
86 }
87
88 buffer_clear(&ogg_buf);
89
90 audio_offset += id3_size;
91
92 DEBUG_TRACE("Skipping ID3v2 tag of size %d\n", id3_size);
93
94 PerlIO_seek(infile, id3_size, SEEK_SET);
95 }
96
97 while (1) {
98 // Grab 28-byte Ogg header
99 if ( !_check_buf(infile, &ogg_buf, 28, OGG_BLOCK_SIZE) ) {
100 err = -1;
101 goto out;
102 }
103
104 buffer_get(&ogg_buf, ogghdr, 28);
105
106 audio_offset += 28;
107
108 // check that the first four bytes are 'OggS'
109 if ( ogghdr[0] != 'O' || ogghdr[1] != 'g' || ogghdr[2] != 'g' || ogghdr[3] != 'S' ) {
110 PerlIO_printf(PerlIO_stderr(), "Not an Ogg file (bad OggS header): %s\n", file);
111 goto out;
112 }
113
114 // Header type flag
115 header_type = ogghdr[5];
116
117 // Absolute granule position, used to find the first audio page
118 bptr = ogghdr + 6;
119 granule_pos = (uint64_t)CONVERT_INT32LE(bptr);
120 bptr += 4;
121 granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32;
122
123 // Stream serial number
124 serialno = CONVERT_INT32LE((ogghdr+14));
125
126 // Count start-of-stream pages
127 if ( header_type & 0x02 ) {
128 streams++;
129 }
130
131 // Keep track of packet count
132 if ( !(header_type & 0x01) ) {
133 packets++;
134 }
135
136 // stop processing if we reach the 3rd packet and have no data
137 if (packets > 2 * streams && !buffer_len(&vorbis_buf) ) {
138 break;
139 }
140
141 // Page seq number
142 pagenum = CONVERT_INT32LE((ogghdr+18));
143
144 if (page >= 0 && page == pagenum) {
145 page++;
146 }
147 else {
148 page = -1;
149 DEBUG_TRACE("Missing page(s) in Ogg file: %s\n", file);
150 }
151
152 DEBUG_TRACE("OggS page %d / packet %d at %d\n", pagenum, packets, (int)(audio_offset - 28));
153 DEBUG_TRACE(" granule_pos: %llu\n", granule_pos);
154
155 // If the granule_pos > 0, we have reached the end of headers and
156 // this is the first audio page
157 if (granule_pos > 0 && granule_pos != -1) {
158 // If seeking, don't waste time on comments
159 if (seeking) {
160 break;
161 }
162
163 // Parse comments, but only if we have any extra data in the buffer
164 if ( buffer_len(&vorbis_buf) > 0 ) {
165 _parse_vorbis_comments(infile, &vorbis_buf, tags, 1);
166 DEBUG_TRACE(" parsed vorbis comments\n");
167 }
168
169 buffer_clear(&vorbis_buf);
170
171 break;
172 }
173
174 // Number of page segments
175 num_segments = ogghdr[26];
176
177 // Calculate total page size
178 pagelen = ogghdr[27];
179 if (num_segments > 1) {
180 int i;
181
182 if ( !_check_buf(infile, &ogg_buf, num_segments, OGG_BLOCK_SIZE) ) {
183 err = -1;
184 goto out;
185 }
186
187 for( i = 0; i < num_segments - 1; i++ ) {
188 u_char x;
189 x = buffer_get_char(&ogg_buf);
190 pagelen += x;
191 }
192
193 audio_offset += num_segments - 1;
194 }
195
196 if ( !_check_buf(infile, &ogg_buf, pagelen, OGG_BLOCK_SIZE) ) {
197 err = -1;
198 goto out;
199 }
200
201 // Still don't have enough data, must have reached the end of the file
202 if ( buffer_len(&ogg_buf) < pagelen ) {
203 PerlIO_printf(PerlIO_stderr(), "Premature end of file: %s\n", file);
204
205 err = -1;
206 goto out;
207 }
208
209 audio_offset += pagelen;
210
211 // Copy page into vorbis buffer
212 buffer_append( &vorbis_buf, buffer_ptr(&ogg_buf), pagelen );
213 DEBUG_TRACE(" Read %d into vorbis buffer\n", pagelen);
214
215 // Process vorbis packet
216 if ( !vorbis_type ) {
217 vorbis_type = buffer_get_char(&vorbis_buf);
218 // Verify 'vorbis' string
219 if ( strncmp( buffer_ptr(&vorbis_buf), "vorbis", 6 ) ) {
220 PerlIO_printf(PerlIO_stderr(), "Not a Vorbis file (bad vorbis header): %s\n", file);
221 goto out;
222 }
223 buffer_consume( &vorbis_buf, 6 );
224
225 DEBUG_TRACE(" Found vorbis packet type %d\n", vorbis_type);
226 }
227
228 if (vorbis_type == 1) {
229 // Parse info
230 // Grab 23-byte Vorbis header
231 if ( buffer_len(&vorbis_buf) < 23 ) {
232 PerlIO_printf(PerlIO_stderr(), "Not a Vorbis file (bad vorbis header): %s\n", file);
233 goto out;
234 }
235
236 buffer_get(&vorbis_buf, vorbishdr, 23);
237
238 my_hv_store( info, "version", newSViv( CONVERT_INT32LE(vorbishdr) ) );
239
240 channels = vorbishdr[4];
241 my_hv_store( info, "channels", newSViv(channels) );
242 my_hv_store( info, "stereo", newSViv( channels == 2 ? 1 : 0 ) );
243
244 samplerate = CONVERT_INT32LE((vorbishdr+5));
245 my_hv_store( info, "samplerate", newSViv(samplerate) );
246 my_hv_store( info, "bitrate_upper", newSViv( CONVERT_INT32LE((vorbishdr+9)) ) );
247
248 bitrate_nominal = CONVERT_INT32LE((vorbishdr+13));
249 my_hv_store( info, "bitrate_nominal", newSViv(bitrate_nominal) );
250 my_hv_store( info, "bitrate_lower", newSViv( CONVERT_INT32LE((vorbishdr+17)) ) );
251
252 blocksize_0 = 2 << ((vorbishdr[21] & 0xF0) >> 4);
253 my_hv_store( info, "blocksize_0", newSViv( blocksize_0 ) );
254 my_hv_store( info, "blocksize_1", newSViv( 2 << (vorbishdr[21] & 0x0F) ) );
255
256 DEBUG_TRACE(" parsed vorbis info header\n");
257
258 buffer_clear(&vorbis_buf);
259 vorbis_type = 0;
260 }
261
262 // Skip rest of this page
263 buffer_consume( &ogg_buf, pagelen );
264 }
265
266 buffer_clear(&ogg_buf);
267
268 // audio_offset is 28 less because we read the Ogg header
269 audio_offset -= 28;
270
271 // from the first packet past the comments
272 my_hv_store( info, "audio_offset", newSViv(audio_offset) );
273
274 audio_size = file_size - audio_offset;
275 my_hv_store( info, "audio_size", newSVuv(audio_size) );
276
277 my_hv_store( info, "serial_number", newSVuv(serialno) );
278
279 // calculate average bitrate and duration
280 avg_buf_size = blocksize_0 * 2;
281 if ( file_size > avg_buf_size ) {
282 DEBUG_TRACE("Seeking to %d to calculate bitrate/duration\n", (int)(file_size - avg_buf_size));
283 PerlIO_seek(infile, file_size - avg_buf_size, SEEK_SET);
284 }
285 else {
286 DEBUG_TRACE("Seeking to %d to calculate bitrate/duration\n", (int)audio_offset);
287 PerlIO_seek(infile, audio_offset, SEEK_SET);
288 }
289
290 if ( PerlIO_read(infile, buffer_append_space(&ogg_buf, avg_buf_size), avg_buf_size) == 0 ) {
291 if ( PerlIO_error(infile) ) {
292 PerlIO_printf(PerlIO_stderr(), "Error reading: %s\n", strerror(errno));
293 }
294 else {
295 PerlIO_printf(PerlIO_stderr(), "File too small. Probably corrupted.\n");
296 }
297
298 err = -1;
299 goto out;
300 }
301
302 // Find sync
303 bptr = (unsigned char *)buffer_ptr(&ogg_buf);
304 buf_size = buffer_len(&ogg_buf);
305 while (
306 buf_size >= 14
307 && (bptr[0] != 'O' || bptr[1] != 'g' || bptr[2] != 'g' || bptr[3] != 'S')
308 ) {
309 bptr++;
310 buf_size--;
311
312 if ( buf_size < 14 ) {
313 // Give up, use less accurate bitrate for length
314 DEBUG_TRACE("buf_size %d, using less accurate bitrate for length\n", buf_size);
315
316 my_hv_store( info, "song_length_ms", newSVpvf( "%d", (int)((audio_size * 8) / bitrate_nominal) * 1000) );
317 my_hv_store( info, "bitrate_average", newSViv(bitrate_nominal) );
318
319 goto out;
320 }
321 }
322 bptr += 6;
323
324 // Get absolute granule value
325 granule_pos = (uint64_t)CONVERT_INT32LE(bptr);
326 bptr += 4;
327 granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32;
328 bptr += 4;
329
330 // Get serial number of this page, if the serial doesn't match the beginning of the file
331 // we have changed logical bitstreams and can't use the granule_pos for bitrate
332 final_serialno = CONVERT_INT32LE((bptr));
333
334 if ( granule_pos && samplerate && serialno == final_serialno ) {
335 // XXX: needs to adjust for initial granule value if file does not start at 0 samples
336 int length = (int)((granule_pos * 1.0 / samplerate) * 1000);
337 my_hv_store( info, "song_length_ms", newSVuv(length) );
338 my_hv_store( info, "bitrate_average", newSVuv( _bitrate(audio_size, length) ) );
339
340 DEBUG_TRACE("Using granule_pos %llu / samplerate %d to calculate bitrate/duration\n", granule_pos, samplerate);
341 }
342 else {
343 // Use nominal bitrate
344 my_hv_store( info, "song_length_ms", newSVpvf( "%d", (int)((audio_size * 8) / bitrate_nominal) * 1000) );
345 my_hv_store( info, "bitrate_average", newSVuv(bitrate_nominal) );
346
347 DEBUG_TRACE("Using nominal bitrate for average\n");
348 }
349
350 out:
351 buffer_free(&ogg_buf);
352 buffer_free(&vorbis_buf);
353
354 if (err) return err;
355
356 return 0;
357 }
358
359 void
_parse_vorbis_comments(PerlIO * infile,Buffer * vorbis_buf,HV * tags,int has_framing)360 _parse_vorbis_comments(PerlIO *infile, Buffer *vorbis_buf, HV *tags, int has_framing)
361 {
362 unsigned int len;
363 unsigned int num_comments;
364 char *tmp;
365 char *bptr;
366 SV *vendor;
367
368 // Vendor string
369 len = buffer_get_int_le(vorbis_buf);
370 vendor = newSVpvn( buffer_ptr(vorbis_buf), len );
371 sv_utf8_decode(vendor);
372 my_hv_store( tags, "VENDOR", vendor );
373 buffer_consume(vorbis_buf, len);
374
375 // Number of comments
376 num_comments = buffer_get_int_le(vorbis_buf);
377
378 while (num_comments--) {
379 len = buffer_get_int_le(vorbis_buf);
380
381 // Sanity check length
382 if ( len > buffer_len(vorbis_buf) ) {
383 DEBUG_TRACE("invalid Vorbis comment length: %u\n", len);
384 return;
385 }
386
387 bptr = buffer_ptr(vorbis_buf);
388
389 if (
390 #ifdef _MSC_VER
391 !strnicmp(bptr, "METADATA_BLOCK_PICTURE=", 23)
392 #else
393 !strncasecmp(bptr, "METADATA_BLOCK_PICTURE=", 23)
394 #endif
395 ) {
396 // parse METADATA_BLOCK_PICTURE according to http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
397 AV *pictures;
398 HV *picture;
399 Buffer pic_buf;
400 uint32_t pic_length;
401
402 buffer_consume(vorbis_buf, 23);
403
404 // Copy picture into new buffer and base64 decode it
405 buffer_init(&pic_buf, len - 23);
406 buffer_append( &pic_buf, buffer_ptr(vorbis_buf), len - 23 );
407 buffer_consume(vorbis_buf, len - 23);
408
409 _decode_base64( buffer_ptr(&pic_buf) );
410
411 picture = _decode_flac_picture(infile, &pic_buf, &pic_length);
412 if ( !picture ) {
413 PerlIO_printf(PerlIO_stderr(), "Invalid Vorbis METADATA_BLOCK_PICTURE comment\n");
414 }
415 else {
416 DEBUG_TRACE(" found picture of length %d\n", pic_length);
417
418 if ( my_hv_exists(tags, "ALLPICTURES") ) {
419 SV **entry = my_hv_fetch(tags, "ALLPICTURES");
420 if (entry != NULL) {
421 pictures = (AV *)SvRV(*entry);
422 av_push( pictures, newRV_noinc( (SV *)picture ) );
423 }
424 }
425 else {
426 pictures = newAV();
427
428 av_push( pictures, newRV_noinc( (SV *)picture ) );
429
430 my_hv_store( tags, "ALLPICTURES", newRV_noinc( (SV *)pictures ) );
431 }
432 }
433
434 buffer_free(&pic_buf);
435 }
436 else if (
437 #ifdef _MSC_VER
438 !strnicmp(bptr, "COVERART=", 9)
439 #else
440 !strncasecmp(bptr, "COVERART=", 9)
441 #endif
442 ) {
443 // decode COVERART into ALLPICTURES
444 AV *pictures;
445 HV *picture = newHV();
446
447 // Fill in recommended default values for most of the picture hash
448 my_hv_store( picture, "color_index", newSVuv(0) );
449 my_hv_store( picture, "depth", newSVuv(0) );
450 my_hv_store( picture, "description", newSVpvn("", 0) );
451 my_hv_store( picture, "height", newSVuv(0) );
452 my_hv_store( picture, "width", newSVuv(0) );
453 my_hv_store( picture, "mime_type", newSVpvn("image/", 6) ); // As recommended, real mime should be in COVERARTMIME
454 my_hv_store( picture, "picture_type", newSVuv(0) ); // Other
455
456 if ( _env_true("AUDIO_SCAN_NO_ARTWORK") ) {
457 my_hv_store( picture, "image_data", newSVuv(len - 9) );
458 buffer_consume(vorbis_buf, len);
459 }
460 else {
461 int pic_length;
462
463 buffer_consume(vorbis_buf, 9);
464 pic_length = _decode_base64( buffer_ptr(vorbis_buf) );
465 DEBUG_TRACE(" found picture of length %d\n", pic_length);
466
467 my_hv_store( picture, "image_data", newSVpvn( buffer_ptr(vorbis_buf), pic_length ) );
468 buffer_consume(vorbis_buf, len - 9);
469 }
470
471 if ( my_hv_exists(tags, "ALLPICTURES") ) {
472 SV **entry = my_hv_fetch(tags, "ALLPICTURES");
473 if (entry != NULL) {
474 pictures = (AV *)SvRV(*entry);
475 av_push( pictures, newRV_noinc( (SV *)picture ) );
476 }
477 }
478 else {
479 pictures = newAV();
480
481 av_push( pictures, newRV_noinc( (SV *)picture ) );
482
483 my_hv_store( tags, "ALLPICTURES", newRV_noinc( (SV *)pictures ) );
484 }
485 }
486 else {
487 New(0, tmp, (int)len + 1, char);
488 buffer_get(vorbis_buf, tmp, len);
489 tmp[len] = '\0';
490
491 _split_vorbis_comment( tmp, tags );
492
493 Safefree(tmp);
494 }
495 }
496
497 if (has_framing) {
498 // Skip framing byte (Ogg only)
499 buffer_consume(vorbis_buf, 1);
500 }
501 }
502
503 static int
ogg_find_frame(PerlIO * infile,char * file,int offset)504 ogg_find_frame(PerlIO *infile, char *file, int offset)
505 {
506 int frame_offset = -1;
507 uint32_t samplerate;
508 uint32_t song_length_ms;
509 uint64_t target_sample;
510
511 // We need to read all metadata first to get some data we need to calculate
512 HV *info = newHV();
513 HV *tags = newHV();
514 if ( _ogg_parse(infile, file, info, tags, 1) != 0 ) {
515 goto out;
516 }
517
518 song_length_ms = SvIV( *(my_hv_fetch( info, "song_length_ms" )) );
519 if (offset >= song_length_ms) {
520 goto out;
521 }
522
523 samplerate = SvIV( *(my_hv_fetch( info, "samplerate" )) );
524
525 // Determine target sample we're looking for
526 target_sample = ((offset - 1) / 10) * (samplerate / 100);
527 DEBUG_TRACE("Looking for target sample %llu\n", target_sample);
528
529 frame_offset = _ogg_binary_search_sample(infile, file, info, target_sample);
530
531 out:
532 // Don't leak
533 SvREFCNT_dec(info);
534 SvREFCNT_dec(tags);
535
536 return frame_offset;
537 }
538
539 int
_ogg_binary_search_sample(PerlIO * infile,char * file,HV * info,uint64_t target_sample)540 _ogg_binary_search_sample(PerlIO *infile, char *file, HV *info, uint64_t target_sample)
541 {
542 Buffer buf;
543 unsigned char *bptr;
544 unsigned int buf_size;
545 int frame_offset = -1;
546 int prev_frame_offset = -1;
547 uint64_t granule_pos = 0;
548 uint64_t prev_granule_pos = 0;
549 uint32_t cur_serialno;
550 off_t low;
551 off_t high;
552 off_t mid;
553 int i;
554
555 off_t audio_offset = SvIV( *(my_hv_fetch( info, "audio_offset" )) );
556 off_t file_size = SvIV( *(my_hv_fetch( info, "file_size" )) );
557 uint32_t serialno = SvIV( *(my_hv_fetch( info, "serial_number" )) );
558
559 // Binary search the entire file
560 low = audio_offset;
561 high = file_size;
562
563 // We need enough for at least 2 packets
564 buffer_init(&buf, OGG_BLOCK_SIZE * 2);
565
566 while (low <= high) {
567 off_t packet_offset;
568
569 mid = low + ((high - low) / 2);
570
571 DEBUG_TRACE(" Searching for sample %llu between %d and %d (mid %d)\n", target_sample, (int)low, (int)high, (int)mid);
572
573 if (mid > file_size - 28) {
574 DEBUG_TRACE(" Reached end of file, aborting\n");
575 frame_offset = -1;
576 goto out;
577 }
578
579 if ( (PerlIO_seek(infile, mid, SEEK_SET)) == -1 ) {
580 frame_offset = -1;
581 goto out;
582 }
583
584 if ( !_check_buf(infile, &buf, 28, OGG_BLOCK_SIZE * 2) ) {
585 frame_offset = -1;
586 goto out;
587 }
588
589 bptr = buffer_ptr(&buf);
590 buf_size = buffer_len(&buf);
591
592 // Find all packets within this buffer, we need at least 2 packets
593 // to figure out what samples we have
594 while (buf_size >= 4) {
595 // Save info from previous packet
596 prev_frame_offset = frame_offset;
597 prev_granule_pos = granule_pos;
598
599 while (
600 buf_size >= 4
601 &&
602 (bptr[0] != 'O' || bptr[1] != 'g' || bptr[2] != 'g' || bptr[3] != 'S')
603 ) {
604 bptr++;
605 buf_size--;
606 }
607
608 if (buf_size < 4) {
609 // No more packets found in buffer
610 break;
611 }
612
613 // Remember how far into the buffer this packet is
614 packet_offset = buffer_len(&buf) - buf_size;
615
616 frame_offset = mid + packet_offset;
617
618 // Make sure we have at least the Ogg header
619 if ( !_check_buf(infile, &buf, 28, 28) ) {
620 frame_offset = -1;
621 goto out;
622 }
623
624 // Read granule_pos for this packet
625 bptr = buffer_ptr(&buf);
626 bptr += packet_offset + 6;
627 granule_pos = (uint64_t)CONVERT_INT32LE(bptr);
628 bptr += 4;
629 granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32;
630 bptr += 4;
631 buf_size -= 14;
632
633 // Also read serial number, if this ever changes within a file it is a chained
634 // file and we can't seek
635 cur_serialno = CONVERT_INT32LE(bptr);
636
637 if (serialno != cur_serialno) {
638 DEBUG_TRACE(" serial number changed to %x, aborting seek\n", cur_serialno);
639 frame_offset = -1;
640 goto out;
641 }
642
643 DEBUG_TRACE(" frame offset: %d, prev_frame_offset: %d, granule_pos: %llu, prev_granule_pos %llu\n",
644 frame_offset, prev_frame_offset, granule_pos, prev_granule_pos
645 );
646
647 // Break out after reading 2 packets
648 if (granule_pos && prev_granule_pos) {
649 break;
650 }
651 }
652
653 // Now, we know the first (prev_granule_pos + 1) and last (granule_pos) samples
654 // in the packet starting at frame_offset
655
656 if ((prev_granule_pos + 1) <= target_sample && granule_pos >= target_sample) {
657 // found frame
658 DEBUG_TRACE(" found frame at %d\n", frame_offset);
659 goto out;
660 }
661
662 if (target_sample < (prev_granule_pos + 1)) {
663 // Special case when very first frame has the sample
664 if (prev_frame_offset == audio_offset) {
665 DEBUG_TRACE(" first frame has target sample\n");
666 frame_offset = prev_frame_offset;
667 break;
668 }
669
670 high = mid - 1;
671 DEBUG_TRACE(" high = %d\n", (int)high);
672 }
673 else {
674 low = mid + 1;
675 DEBUG_TRACE(" low = %d\n", (int)low);
676 }
677
678 // XXX this can be pretty inefficient in some cases
679
680 // Reset and binary search again
681 buffer_clear(&buf);
682
683 frame_offset = -1;
684 granule_pos = 0;
685 }
686
687 out:
688 buffer_free(&buf);
689
690 return frame_offset;
691 }
692
693