diff options
author | Max Kellermann <max@musicpd.org> | 2020-01-27 21:33:37 +0100 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2020-01-31 19:35:35 +0100 |
commit | 0b2444450f1dd3e249529d14418abf89579ad09a (patch) | |
tree | 2ac6c1483e7b659addadf212beb765715f4250b6 /src | |
parent | faf149d08eb4af322a8bbaa8ce83324d691a7737 (diff) |
decoder/ogg: improve seeking accuracy using binary search
On some VBR files, the single-step interpolation was very inaccurate
and inacceptable.
Closes https://github.com/MusicPlayerDaemon/MPD/issues/720
Diffstat (limited to 'src')
-rw-r--r-- | src/decoder/plugins/OggDecoder.cxx | 68 |
1 files changed, 61 insertions, 7 deletions
diff --git a/src/decoder/plugins/OggDecoder.cxx b/src/decoder/plugins/OggDecoder.cxx index 871946866..10f935743 100644 --- a/src/decoder/plugins/OggDecoder.cxx +++ b/src/decoder/plugins/OggDecoder.cxx @@ -80,12 +80,66 @@ OggDecoder::SeekGranulePos(ogg_int64_t where_granulepos) { assert(IsSeekable()); - /* interpolate the file offset where we expect to find the - given granule position */ - /* TODO: implement binary search */ - offset_type offset(where_granulepos * input_stream.GetSize() - / end_granulepos); + /* binary search: interpolate the file offset where we expect + to find the given granule position, and repeat until we're + close enough */ - SeekByte(offset); -} + static const ogg_int64_t MARGIN_BEFORE = 44100 / 3; + static const ogg_int64_t MARGIN_AFTER = 44100 / 10; + + offset_type min_offset = 0, max_offset = input_stream.GetSize(); + ogg_int64_t min_granule = 0, max_granule = end_granulepos; + + while (true) { + const offset_type delta_offset = max_offset - min_offset; + const ogg_int64_t delta_granule = max_granule - min_granule; + const ogg_int64_t relative_granule = where_granulepos - min_granule; + + const offset_type offset = min_offset + relative_granule * delta_offset + / delta_granule; + + SeekByte(offset); + + const auto new_granule = ReadGranulepos(); + if (new_granule < 0) + /* no granulepos here, which shouldn't happen + - we can't improve, so stop */ + return; + + if (new_granule > where_granulepos + MARGIN_AFTER) { + if (new_granule > max_granule) + /* something went wrong */ + return; + if (max_granule == new_granule) + /* break out of the infinite loop, we + can't get any closer */ + break; + + /* reduce the max bounds and interpolate again */ + max_granule = new_granule; + max_offset = GetStartOffset(); + } else if (new_granule + MARGIN_BEFORE < where_granulepos) { + if (new_granule < min_granule) + /* something went wrong */ + return; + + if (min_granule == new_granule) + /* break out of the infinite loop, we + can't get any closer */ + break; + + /* increase the min bounds and interpolate + again */ + min_granule = new_granule; + min_offset = GetStartOffset(); + } else { + break; + } + } + + /* go back to the last page start so OggVisitor can start + visiting from here (we have consumed a few pages + already) */ + SeekByte(GetStartOffset()); +} |