update seek routine to use info from seek table
authorJosh Coalson <jcoalson@users.sourceforce.net>
Fri, 13 Apr 2001 18:42:12 +0000 (18:42 +0000)
committerJosh Coalson <jcoalson@users.sourceforce.net>
Fri, 13 Apr 2001 18:42:12 +0000 (18:42 +0000)
src/libFLAC/file_decoder.c

index a94bcc2..c9e179c 100644 (file)
@@ -378,6 +378,7 @@ void metadata_callback_(const FLAC__StreamDecoder *decoder, const FLAC__StreamMe
        else if(metadata->type == FLAC__METADATA_TYPE_SEEKTABLE) {
                file_decoder->guts->seek_table = &metadata->data.seek_table;
        }
+
        if(file_decoder->state != FLAC__FILE_DECODER_SEEKING)
                file_decoder->guts->metadata_callback(file_decoder, metadata, file_decoder->guts->client_data);
 }
@@ -393,7 +394,8 @@ void error_callback_(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErro
 
 bool seek_to_absolute_sample_(FLAC__FileDecoder *decoder, long filesize, uint64 target_sample)
 {
-       long lower_bound, upper_bound, pos, last_pos = -1;
+       long first_frame_offset, lower_bound, upper_bound, pos = -1, last_pos = -1;
+       int i, lower_seek_point = -1;
        unsigned approx_bytes_per_frame;
        uint64 last_frame_sample = 0xffffffffffffffff;
        bool needs_seek;
@@ -410,17 +412,26 @@ bool seek_to_absolute_sample_(FLAC__FileDecoder *decoder, long filesize, uint64
        else
                approx_bytes_per_frame = 1152 * decoder->guts->stream_info.channels * decoder->guts->stream_info.bits_per_sample/8 + 64;
 
-       /* The file pointer is currently at the first frame plus any read
-          ahead data, so first we get the file pointer, then subtract
-          uncomsumed bytes to get the position (lower_bound) of the first frame
-          in the file */
-       if(-1 == (lower_bound = ftell(decoder->guts->file))) {
+       /*
+        * The file pointer is currently at the first frame plus any read
+        * ahead data, so first we get the file pointer, then subtract
+        * uncomsumed bytes to get the position of the first frame in the
+        * file.
+        */
+       if(-1 == (first_frame_offset = ftell(decoder->guts->file))) {
                decoder->state = FLAC__FILE_DECODER_SEEK_ERROR;
                return false;
        }
-       lower_bound -= FLAC__stream_decoder_input_bytes_unconsumed(decoder->guts->stream);
-       if(lower_bound < 0)
-               lower_bound = 0;
+       first_frame_offset -= FLAC__stream_decoder_input_bytes_unconsumed(decoder->guts->stream);
+       assert(first_frame_offset >= 0);
+
+       /*
+        * First, we set an upper and lower bound on where in the
+        * file we will search.  For now we assume the worst case
+        * scenario, which is our best guess at the beginning of
+        * the first and last frames.
+        */
+       lower_bound = first_frame_offset;
 
        /* calc the upper_bound, beyond which we never want to seek */
        if(decoder->guts->stream_info.max_framesize > 0)
@@ -428,13 +439,63 @@ bool seek_to_absolute_sample_(FLAC__FileDecoder *decoder, long filesize, uint64
        else
                upper_bound = filesize - ((decoder->guts->stream_info.channels * decoder->guts->stream_info.bits_per_sample * FLAC__MAX_BLOCK_SIZE) / 8 + 128 + 2);
 
-       /* Now we need to use the metadata and the filelength to search to the frame with the correct sample */
+       /*
+        * Now we refine the bounds if we have a seektable with
+        * suitable points.  Note that according to the spec they
+        * must be ordered by ascending sample number.
+        */
+       if(0 != decoder->guts->seek_table) {
+               /* find the closest seek point <= target_sample, if it exists */
+               for(i = (int)decoder->guts->seek_table->num_points - 1; i >= 0; i--) {
+                       if(decoder->guts->seek_table->points[i].sample_number <= target_sample)
+                               break;
+               }
+               if(i >= 0) { /* i.e. we found a suitable seek point... */
+                       lower_bound = first_frame_offset + decoder->guts->seek_table->points[i].stream_offset;
+                       lower_seek_point = i;
+               }
+
+               /* find the closest seek point > target_sample, if it exists */
+               for(i = 0; i < (int)decoder->guts->seek_table->num_points; i++) {
+                       if(decoder->guts->seek_table->points[i].sample_number > target_sample)
+                               break;
+               }
+               if(i < (int)decoder->guts->seek_table->num_points) { /* i.e. we found a suitable seek point... */
+                       upper_bound = first_frame_offset + decoder->guts->seek_table->points[i].stream_offset;
+                       upper_seek_point = i;
+               }
+       }
+
+       /*
+        * Now guess at where within those bounds our target
+        * sample will be.
+        */
+       if(lower_seek_point >= 0) {
+               /* first see if our sample is within a few frames of the lower seekpoint */
+               if(decoder->guts->seek_table->points[lower_seek_point].sample_number <= target_sample && target_sample < decoder->guts->seek_table->points[lower_seek_point].sample_number + (decoder->guts->seek_table->points[lower_seek_point].frame_samples * 4)) {
+                       pos = lower_bound;
+               }
+               else if(upper_seek_point >= 0) {
+                       const uint64 target_offset = target_sample - decoder->guts->seek_table->points[lower_seek_point].sample_number;
+                       const uint64 range_samples = decoder->guts->seek_table->points[upper_seek_point].sample_number - decoder->guts->seek_table->points[lower_seek_point].sample_number;
+                       const long range_bytes = upper_bound - lower_bound;
 #ifdef _MSC_VER
-       /* with VC++ you have to spoon feed it the casting */
-       pos = lower_bound + (long)((double)(int64)target_sample / (double)(int64)decoder->guts->stream_info.total_samples * (double)(filesize-lower_bound-1)) - approx_bytes_per_frame;
+                       /* with VC++ you have to spoon feed it the casting */
+                       pos = lower_bound + (long)((double)(int64)target_offset / (double)(int64)range_samples * (double)(range_bytes-1)) - approx_bytes_per_frame;
 #else
-       pos = lower_bound + (long)((double)target_sample / (double)decoder->guts->stream_info.total_samples * (double)(filesize-lower_bound-1)) - approx_bytes_per_frame;
+                       pos = lower_bound + (long)((double)target_offset / (double)range_samples * (double)(range_bytes-1)) - approx_bytes_per_frame;
 #endif
+               }
+       }
+       if(pos < 0) {
+               /* We need to use the metadata and the filelength to estimate the position of the frame with the correct sample */
+#ifdef _MSC_VER
+               /* with VC++ you have to spoon feed it the casting */
+               pos = first_frame_offset + (long)((double)(int64)target_sample / (double)(int64)decoder->guts->stream_info.total_samples * (double)(filesize-first_frame_offset-1)) - approx_bytes_per_frame;
+#else
+               pos = first_frame_offset + (long)((double)target_sample / (double)decoder->guts->stream_info.total_samples * (double)(filesize-first_frame_offset-1)) - approx_bytes_per_frame;
+#endif
+       }
 
        /* clip the position to the bounds, lower bound takes precedence */
        if(pos >= upper_bound)