File Coverage

src/ogg.c
Criterion Covered Total %
statement 244 295 82.7
branch 105 170 61.7
condition n/a
subroutine n/a
pod n/a
total 349 465 75.0


line stmt bran cond sub pod time code
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
20 19           get_ogg_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
21             {
22 19           return _ogg_parse(infile, file, info, tags, 0);
23             }
24              
25             int
26 22           _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 22           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 22           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 22           int page = 0;
46 22           int packets = 0;
47 22           int streams = 0;
48              
49             unsigned char vorbishdr[23];
50             unsigned char channels;
51 22           unsigned int blocksize_0 = 0;
52             unsigned int avg_buf_size;
53 22           unsigned int samplerate = 0;
54 22           unsigned int bitrate_nominal = 0;
55 22           uint64_t granule_pos = 0;
56              
57 22           unsigned char vorbis_type = 0;
58              
59             int i;
60 22           int err = 0;
61              
62 22           buffer_init(&ogg_buf, OGG_BLOCK_SIZE);
63 22           buffer_init(&vorbis_buf, 0);
64              
65 22           file_size = _file_size(infile);
66 22           my_hv_store( info, "file_size", newSVuv(file_size) );
67              
68 22 50         if ( !_check_buf(infile, &ogg_buf, 10, OGG_BLOCK_SIZE) ) {
69 0           err = -1;
70 0           goto out;
71             }
72              
73             // Skip ID3 tags if any
74 22           bptr = (unsigned char *)buffer_ptr(&ogg_buf);
75 22 50         if (
76 0 0         (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') &&
    0          
    0          
77 0 0         bptr[3] < 0xff && bptr[4] < 0xff &&
    0          
78 0 0         bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
    0          
    0          
79             ) {
80             /* found an ID3 header... */
81 0           id3_size = 10 + (bptr[6]<<21) + (bptr[7]<<14) + (bptr[8]<<7) + bptr[9];
82              
83 0 0         if (bptr[5] & 0x10) {
84             // footer present
85 0           id3_size += 10;
86             }
87              
88 0           buffer_clear(&ogg_buf);
89              
90 0           audio_offset += id3_size;
91              
92             DEBUG_TRACE("Skipping ID3v2 tag of size %d\n", id3_size);
93              
94 0           PerlIO_seek(infile, id3_size, SEEK_SET);
95             }
96              
97             while (1) {
98             // Grab 28-byte Ogg header
99 165 50         if ( !_check_buf(infile, &ogg_buf, 28, OGG_BLOCK_SIZE) ) {
100 0           err = -1;
101 0           goto out;
102             }
103              
104 165           buffer_get(&ogg_buf, ogghdr, 28);
105              
106 165           audio_offset += 28;
107              
108             // check that the first four bytes are 'OggS'
109 165 50         if ( ogghdr[0] != 'O' || ogghdr[1] != 'g' || ogghdr[2] != 'g' || ogghdr[3] != 'S' ) {
    50          
    50          
    50          
110 0           PerlIO_printf(PerlIO_stderr(), "Not an Ogg file (bad OggS header): %s\n", file);
111 0           goto out;
112             }
113              
114             // Header type flag
115 165           header_type = ogghdr[5];
116              
117             // Absolute granule position, used to find the first audio page
118 165           bptr = ogghdr + 6;
119 165           granule_pos = (uint64_t)CONVERT_INT32LE(bptr);
120 165           bptr += 4;
121 165           granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32;
122              
123             // Stream serial number
124 165           serialno = CONVERT_INT32LE((ogghdr+14));
125              
126             // Count start-of-stream pages
127 165 100         if ( header_type & 0x02 ) {
128 22           streams++;
129             }
130              
131             // Keep track of packet count
132 165 100         if ( !(header_type & 0x01) ) {
133 65           packets++;
134             }
135              
136             // stop processing if we reach the 3rd packet and have no data
137 165 100         if (packets > 2 * streams && !buffer_len(&vorbis_buf) ) {
    50          
138 0           break;
139             }
140              
141             // Page seq number
142 165           pagenum = CONVERT_INT32LE((ogghdr+18));
143              
144 165 50         if (page >= 0 && page == pagenum) {
    100          
145 164           page++;
146             }
147             else {
148 1           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 165 100         if (granule_pos > 0 && granule_pos != -1) {
    100          
158             // If seeking, don't waste time on comments
159 22 100         if (seeking) {
160 3           break;
161             }
162              
163             // Parse comments, but only if we have any extra data in the buffer
164 19 100         if ( buffer_len(&vorbis_buf) > 0 ) {
165 18           _parse_vorbis_comments(infile, &vorbis_buf, tags, 1);
166             DEBUG_TRACE(" parsed vorbis comments\n");
167             }
168              
169 19           buffer_clear(&vorbis_buf);
170              
171 19           break;
172             }
173              
174             // Number of page segments
175 143           num_segments = ogghdr[26];
176              
177             // Calculate total page size
178 143           pagelen = ogghdr[27];
179 143 100         if (num_segments > 1) {
180             int i;
181              
182 117 50         if ( !_check_buf(infile, &ogg_buf, num_segments, OGG_BLOCK_SIZE) ) {
183 0           err = -1;
184 0           goto out;
185             }
186              
187 2035 100         for( i = 0; i < num_segments - 1; i++ ) {
188             u_char x;
189 1918           x = buffer_get_char(&ogg_buf);
190 1918           pagelen += x;
191             }
192              
193 117           audio_offset += num_segments - 1;
194             }
195              
196 143 50         if ( !_check_buf(infile, &ogg_buf, pagelen, OGG_BLOCK_SIZE) ) {
197 0           err = -1;
198 0           goto out;
199             }
200              
201             // Still don't have enough data, must have reached the end of the file
202 143 50         if ( buffer_len(&ogg_buf) < pagelen ) {
203 0           PerlIO_printf(PerlIO_stderr(), "Premature end of file: %s\n", file);
204              
205 0           err = -1;
206 0           goto out;
207             }
208              
209 143           audio_offset += pagelen;
210              
211             // Copy page into vorbis buffer
212 143           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 143 100         if ( !vorbis_type ) {
217 43           vorbis_type = buffer_get_char(&vorbis_buf);
218             // Verify 'vorbis' string
219 43 50         if ( strncmp( buffer_ptr(&vorbis_buf), "vorbis", 6 ) ) {
220 0           PerlIO_printf(PerlIO_stderr(), "Not a Vorbis file (bad vorbis header): %s\n", file);
221 0           goto out;
222             }
223 43           buffer_consume( &vorbis_buf, 6 );
224              
225             DEBUG_TRACE(" Found vorbis packet type %d\n", vorbis_type);
226             }
227              
228 143 100         if (vorbis_type == 1) {
229             // Parse info
230             // Grab 23-byte Vorbis header
231 22 50         if ( buffer_len(&vorbis_buf) < 23 ) {
232 0           PerlIO_printf(PerlIO_stderr(), "Not a Vorbis file (bad vorbis header): %s\n", file);
233 0           goto out;
234             }
235              
236 22           buffer_get(&vorbis_buf, vorbishdr, 23);
237              
238 22           my_hv_store( info, "version", newSViv( CONVERT_INT32LE(vorbishdr) ) );
239              
240 22           channels = vorbishdr[4];
241 22           my_hv_store( info, "channels", newSViv(channels) );
242 22           my_hv_store( info, "stereo", newSViv( channels == 2 ? 1 : 0 ) );
243              
244 22           samplerate = CONVERT_INT32LE((vorbishdr+5));
245 22           my_hv_store( info, "samplerate", newSViv(samplerate) );
246 22           my_hv_store( info, "bitrate_upper", newSViv( CONVERT_INT32LE((vorbishdr+9)) ) );
247              
248 22           bitrate_nominal = CONVERT_INT32LE((vorbishdr+13));
249 22           my_hv_store( info, "bitrate_nominal", newSViv(bitrate_nominal) );
250 22           my_hv_store( info, "bitrate_lower", newSViv( CONVERT_INT32LE((vorbishdr+17)) ) );
251              
252 22           blocksize_0 = 2 << ((vorbishdr[21] & 0xF0) >> 4);
253 22           my_hv_store( info, "blocksize_0", newSViv( blocksize_0 ) );
254 22           my_hv_store( info, "blocksize_1", newSViv( 2 << (vorbishdr[21] & 0x0F) ) );
255              
256             DEBUG_TRACE(" parsed vorbis info header\n");
257              
258 22           buffer_clear(&vorbis_buf);
259 22           vorbis_type = 0;
260             }
261              
262             // Skip rest of this page
263 143           buffer_consume( &ogg_buf, pagelen );
264 143           }
265              
266 22           buffer_clear(&ogg_buf);
267              
268             // audio_offset is 28 less because we read the Ogg header
269 22           audio_offset -= 28;
270              
271             // from the first packet past the comments
272 22           my_hv_store( info, "audio_offset", newSViv(audio_offset) );
273              
274 22           audio_size = file_size - audio_offset;
275 22           my_hv_store( info, "audio_size", newSVuv(audio_size) );
276              
277 22           my_hv_store( info, "serial_number", newSVuv(serialno) );
278              
279             // calculate average bitrate and duration
280 22           avg_buf_size = blocksize_0 * 2;
281 22 100         if ( file_size > avg_buf_size ) {
282             DEBUG_TRACE("Seeking to %d to calculate bitrate/duration\n", (int)(file_size - avg_buf_size));
283 17           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 5           PerlIO_seek(infile, audio_offset, SEEK_SET);
288             }
289              
290 22 50         if ( PerlIO_read(infile, buffer_append_space(&ogg_buf, avg_buf_size), avg_buf_size) == 0 ) {
291 0 0         if ( PerlIO_error(infile) ) {
292 0           PerlIO_printf(PerlIO_stderr(), "Error reading: %s\n", strerror(errno));
293             }
294             else {
295 0           PerlIO_printf(PerlIO_stderr(), "File too small. Probably corrupted.\n");
296             }
297              
298 0           err = -1;
299 0           goto out;
300             }
301              
302             // Find sync
303 22           bptr = (unsigned char *)buffer_ptr(&ogg_buf);
304 22           buf_size = buffer_len(&ogg_buf);
305 41910 50         while (
306             buf_size >= 14
307 41910 100         && (bptr[0] != 'O' || bptr[1] != 'g' || bptr[2] != 'g' || bptr[3] != 'S')
    100          
    100          
    50          
308             ) {
309 41890           bptr++;
310 41890           buf_size--;
311              
312 41890 100         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 2           my_hv_store( info, "song_length_ms", newSVpvf( "%d", (int)((audio_size * 8) / bitrate_nominal) * 1000) );
317 2           my_hv_store( info, "bitrate_average", newSViv(bitrate_nominal) );
318              
319 2           goto out;
320             }
321             }
322 20           bptr += 6;
323              
324             // Get absolute granule value
325 20           granule_pos = (uint64_t)CONVERT_INT32LE(bptr);
326 20           bptr += 4;
327 20           granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32;
328 20           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 20           final_serialno = CONVERT_INT32LE((bptr));
333              
334 33 100         if ( granule_pos && samplerate && serialno == final_serialno ) {
    50          
    100          
335             // XXX: needs to adjust for initial granule value if file does not start at 0 samples
336 13           int length = (int)((granule_pos * 1.0 / samplerate) * 1000);
337 13           my_hv_store( info, "song_length_ms", newSVuv(length) );
338 13           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 7           my_hv_store( info, "song_length_ms", newSVpvf( "%d", (int)((audio_size * 8) / bitrate_nominal) * 1000) );
345 7           my_hv_store( info, "bitrate_average", newSVuv(bitrate_nominal) );
346              
347             DEBUG_TRACE("Using nominal bitrate for average\n");
348             }
349              
350             out:
351 22           buffer_free(&ogg_buf);
352 22           buffer_free(&vorbis_buf);
353              
354 22 50         if (err) return err;
355              
356 22           return 0;
357             }
358              
359             void
360 32           _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 32           len = buffer_get_int_le(vorbis_buf);
370 32           vendor = newSVpvn( buffer_ptr(vorbis_buf), len );
371 32           sv_utf8_decode(vendor);
372 32           my_hv_store( tags, "VENDOR", vendor );
373 32           buffer_consume(vorbis_buf, len);
374              
375             // Number of comments
376 32           num_comments = buffer_get_int_le(vorbis_buf);
377              
378 300 100         while (num_comments--) {
379 269           len = buffer_get_int_le(vorbis_buf);
380              
381             // Sanity check length
382 269 100         if ( len > buffer_len(vorbis_buf) ) {
383             DEBUG_TRACE("invalid Vorbis comment length: %u\n", len);
384 1           return;
385             }
386              
387 268           bptr = buffer_ptr(vorbis_buf);
388              
389 268 100         if (
390             #ifdef _MSC_VER
391             !strnicmp(bptr, "METADATA_BLOCK_PICTURE=", 23)
392             #else
393 268           !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 4           buffer_consume(vorbis_buf, 23);
403              
404             // Copy picture into new buffer and base64 decode it
405 4           buffer_init(&pic_buf, len - 23);
406 4           buffer_append( &pic_buf, buffer_ptr(vorbis_buf), len - 23 );
407 4           buffer_consume(vorbis_buf, len - 23);
408              
409 4           _decode_base64( buffer_ptr(&pic_buf) );
410              
411 4           picture = _decode_flac_picture(infile, &pic_buf, &pic_length);
412 4 50         if ( !picture ) {
413 0           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 4 100         if ( my_hv_exists(tags, "ALLPICTURES") ) {
419 2           SV **entry = my_hv_fetch(tags, "ALLPICTURES");
420 2 50         if (entry != NULL) {
421 2           pictures = (AV *)SvRV(*entry);
422 2           av_push( pictures, newRV_noinc( (SV *)picture ) );
423             }
424             }
425             else {
426 2           pictures = newAV();
427              
428 2           av_push( pictures, newRV_noinc( (SV *)picture ) );
429              
430 2           my_hv_store( tags, "ALLPICTURES", newRV_noinc( (SV *)pictures ) );
431             }
432             }
433              
434 4           buffer_free(&pic_buf);
435             }
436 264 100         else if (
437             #ifdef _MSC_VER
438             !strnicmp(bptr, "COVERART=", 9)
439             #else
440 264           !strncasecmp(bptr, "COVERART=", 9)
441             #endif
442             ) {
443             // decode COVERART into ALLPICTURES
444             AV *pictures;
445 4           HV *picture = newHV();
446              
447             // Fill in recommended default values for most of the picture hash
448 4           my_hv_store( picture, "color_index", newSVuv(0) );
449 4           my_hv_store( picture, "depth", newSVuv(0) );
450 4           my_hv_store( picture, "description", newSVpvn("", 0) );
451 4           my_hv_store( picture, "height", newSVuv(0) );
452 4           my_hv_store( picture, "width", newSVuv(0) );
453 4           my_hv_store( picture, "mime_type", newSVpvn("image/", 6) ); // As recommended, real mime should be in COVERARTMIME
454 4           my_hv_store( picture, "picture_type", newSVuv(0) ); // Other
455              
456 4 100         if ( _env_true("AUDIO_SCAN_NO_ARTWORK") ) {
457 1           my_hv_store( picture, "image_data", newSVuv(len - 9) );
458 1           buffer_consume(vorbis_buf, len);
459             }
460             else {
461             int pic_length;
462              
463 3           buffer_consume(vorbis_buf, 9);
464 3           pic_length = _decode_base64( buffer_ptr(vorbis_buf) );
465             DEBUG_TRACE(" found picture of length %d\n", pic_length);
466              
467 3           my_hv_store( picture, "image_data", newSVpvn( buffer_ptr(vorbis_buf), pic_length ) );
468 3           buffer_consume(vorbis_buf, len - 9);
469             }
470              
471 4 50         if ( my_hv_exists(tags, "ALLPICTURES") ) {
472 0           SV **entry = my_hv_fetch(tags, "ALLPICTURES");
473 0 0         if (entry != NULL) {
474 0           pictures = (AV *)SvRV(*entry);
475 0           av_push( pictures, newRV_noinc( (SV *)picture ) );
476             }
477             }
478             else {
479 4           pictures = newAV();
480              
481 4           av_push( pictures, newRV_noinc( (SV *)picture ) );
482              
483 4           my_hv_store( tags, "ALLPICTURES", newRV_noinc( (SV *)pictures ) );
484             }
485             }
486             else {
487 260           New(0, tmp, (int)len + 1, char);
488 260           buffer_get(vorbis_buf, tmp, len);
489 260           tmp[len] = '\0';
490              
491 260           _split_vorbis_comment( tmp, tags );
492              
493 260           Safefree(tmp);
494             }
495             }
496              
497 31 100         if (has_framing) {
498             // Skip framing byte (Ogg only)
499 18           buffer_consume(vorbis_buf, 1);
500             }
501             }
502              
503             static int
504 3           ogg_find_frame(PerlIO *infile, char *file, int offset)
505             {
506 3           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 3           HV *info = newHV();
513 3           HV *tags = newHV();
514 3 50         if ( _ogg_parse(infile, file, info, tags, 1) != 0 ) {
515 0           goto out;
516             }
517              
518 3 50         song_length_ms = SvIV( *(my_hv_fetch( info, "song_length_ms" )) );
519 3 50         if (offset >= song_length_ms) {
520 0           goto out;
521             }
522              
523 3 50         samplerate = SvIV( *(my_hv_fetch( info, "samplerate" )) );
524              
525             // Determine target sample we're looking for
526 3           target_sample = ((offset - 1) / 10) * (samplerate / 100);
527             DEBUG_TRACE("Looking for target sample %llu\n", target_sample);
528              
529 3           frame_offset = _ogg_binary_search_sample(infile, file, info, target_sample);
530              
531             out:
532             // Don't leak
533 3           SvREFCNT_dec(info);
534 3           SvREFCNT_dec(tags);
535              
536 3           return frame_offset;
537             }
538              
539             int
540 3           _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 3           int frame_offset = -1;
546 3           int prev_frame_offset = -1;
547 3           uint64_t granule_pos = 0;
548 3           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 3 50         off_t audio_offset = SvIV( *(my_hv_fetch( info, "audio_offset" )) );
556 3 50         off_t file_size = SvIV( *(my_hv_fetch( info, "file_size" )) );
557 3 50         uint32_t serialno = SvIV( *(my_hv_fetch( info, "serial_number" )) );
558              
559             // Binary search the entire file
560 3           low = audio_offset;
561 3           high = file_size;
562              
563             // We need enough for at least 2 packets
564 3           buffer_init(&buf, OGG_BLOCK_SIZE * 2);
565              
566 28 50         while (low <= high) {
567             off_t packet_offset;
568              
569 28           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 28 50         if (mid > file_size - 28) {
574             DEBUG_TRACE(" Reached end of file, aborting\n");
575 0           frame_offset = -1;
576 0           goto out;
577             }
578              
579 28 50         if ( (PerlIO_seek(infile, mid, SEEK_SET)) == -1 ) {
580 0           frame_offset = -1;
581 0           goto out;
582             }
583              
584 28 50         if ( !_check_buf(infile, &buf, 28, OGG_BLOCK_SIZE * 2) ) {
585 0           frame_offset = -1;
586 0           goto out;
587             }
588              
589 28           bptr = buffer_ptr(&buf);
590 28           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 56 50         while (buf_size >= 4) {
595             // Save info from previous packet
596 56           prev_frame_offset = frame_offset;
597 56           prev_granule_pos = granule_pos;
598              
599 205128 50         while (
600             buf_size >= 4
601 205128 100         &&
602 717 100         (bptr[0] != 'O' || bptr[1] != 'g' || bptr[2] != 'g' || bptr[3] != 'S')
    50          
    50          
603             ) {
604 205072           bptr++;
605 205072           buf_size--;
606             }
607              
608 56 50         if (buf_size < 4) {
609             // No more packets found in buffer
610 0           break;
611             }
612              
613             // Remember how far into the buffer this packet is
614 56           packet_offset = buffer_len(&buf) - buf_size;
615              
616 56           frame_offset = mid + packet_offset;
617              
618             // Make sure we have at least the Ogg header
619 56 50         if ( !_check_buf(infile, &buf, 28, 28) ) {
620 0           frame_offset = -1;
621 0           goto out;
622             }
623              
624             // Read granule_pos for this packet
625 56           bptr = buffer_ptr(&buf);
626 56           bptr += packet_offset + 6;
627 56           granule_pos = (uint64_t)CONVERT_INT32LE(bptr);
628 56           bptr += 4;
629 56           granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32;
630 56           bptr += 4;
631 56           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 56           cur_serialno = CONVERT_INT32LE(bptr);
636              
637 56 50         if (serialno != cur_serialno) {
638             DEBUG_TRACE(" serial number changed to %x, aborting seek\n", cur_serialno);
639 0           frame_offset = -1;
640 0           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 56 50         if (granule_pos && prev_granule_pos) {
    100          
649 28           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 28 100         if ((prev_granule_pos + 1) <= target_sample && granule_pos >= target_sample) {
    50          
657             // found frame
658             DEBUG_TRACE(" found frame at %d\n", frame_offset);
659 2           goto out;
660             }
661              
662 26 50         if (target_sample < (prev_granule_pos + 1)) {
663             // Special case when very first frame has the sample
664 26 100         if (prev_frame_offset == audio_offset) {
665             DEBUG_TRACE(" first frame has target sample\n");
666 1           frame_offset = prev_frame_offset;
667 1           break;
668             }
669              
670 25           high = mid - 1;
671             DEBUG_TRACE(" high = %d\n", (int)high);
672             }
673             else {
674 0           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 25           buffer_clear(&buf);
682              
683 25           frame_offset = -1;
684 25           granule_pos = 0;
685             }
686              
687             out:
688 3           buffer_free(&buf);
689              
690 3           return frame_offset;
691             }
692