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