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);
}
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;
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)
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)