-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A blueprint for seeking feature #29
Comments
Thanks for writing this up!
I think in hindsight, adding these
I am working on this in the
Yes, that sounds good!
It’s not yet exposed indeed, parsing isn’t even implemented yet, so I would start with that first.
Depending on the size of the seektable, it may be faster to have a vec with binary search or even linear search, but for now I would go with whatever makes the implementation simpler.
Yeah just the header should be sufficient, the flac format takes care to avoid creating the frame header bit pattern in other places, and if the CRC of the header matches and the sample number makes sense, then that should be fine. I expect we need to verify that the sample numbers are increasing as the position in the stream increases, otherwise it is probably possible to create an adversarial input that breaks the binary search.
That may be a good future optimization, but I would start with the simplest option possible. But we need some kind of forward search anyway, right? If binary search tells you an offset in the stream, but that offset is not the start of a frame header, then we need to search ahead (or backwards) until there is some frame, otherwise the binary search can’t continue.
That could be a second step, I would start with searching for the frame; I’m not sure how useful seeking in |
Thanks for your detailed review. I understood that adding two more constructors are not a good way. So, as I understand, the high-level API So, the low-level seeking function will be like this. Does it seem good? /// Seek the given reader to an appropriate position so that
/// the next frame returned by `FrameReader::read_next_or_eof`
/// will contain the sample with the given index, if exists.
fn seek_reader_by_sample_number<R: io::Read + io::Seek>(
reader: &mut R,
sample_number: u64,
audio_block_start_position: Option<u64>, // Maybe it should not be Option?
seek_table: Option<SeekTable>,
) -> Option<u64> {
// ...
} Do I have to implement new seeking feature on
Thanks for teaching me. I've totally confused them. Regarding with the seektable, are you planning to implement parsing and storing it in
Well actually, when it comes to an adversarial input, I guess it is possible to provide a fake header where sample numbers "seem" to be increasing but still results in breaking binary search. So I think we can leave it as future tasks as well.
Um, it is my fault that I didn't explain clearly, but this is a necessary step for binary seeking, not an optimization.
I've already implemented the similar algorithm for the
At least, I want seekable |
Yes, that would be a good first step either way.
I intend for
I have no plans to implement it, you can go ahead :) This would be a welcome addition in a separate pull request either way.
If there are still adversarial cases, it should be possible to find them with a fuzzer. There are already a few fuzzers, maybe we can add one where the fuzz input is first a 32-bit offset to seek to, and the remainder of the input is the flac file. The implementation of the fuzzer would be to just do the seek. If there is an infinite loop, libFuzzer will report it as a hang. But a good first step would be to implement seek indeed, we can add the fuzzer later.
Yes, I think we are on the same page here 👍
Out of curiosity, can you tell a bit more about your use case? |
Well, |
I find myself looking for seek functionality in a flac decoder - did you ever make any progress on this, @TonalidadeHidrica ? |
@joshhansen Unfortunately I didn't, and if you would try implement this, I'd be very happy! |
I'm planning to add seeking feature to this library, as I mentioned in #28 . This issue has been opened for tracking the design plan for this new feature.
The methods to add
First, we are going to add new constructors
new_seekable
andnew_seekable_ext
inimpl <R: Read + Seek> FlacReader {
. The purpose of creating a new struct is that we have to do an additional process right after reading all the metadata blocks: save the position of the reader. This enables the reader to determine the range for binary searching for every seek. This position is going to be stored in a new field ofFlacReader
,audio_block_start_position: Option<u64>
.Next, we are going to add new function
seek
toFrameReader
andFlacSamples
, both of which takes the sample number (0-indexed).FrameReader::seek
seeks the reader to the beginning of the desired frame. Here, the "desired frame" is the last frame whose frame number is less than or equal to the given sample number (in case of fixed block size stream, it is automatically converted appropriately). Therefore, the next frame (this is equivalent toclaxon::frame::Block
, right?) yielded fromFrameReader
will include the desired sample (if the given sample number was less than the range of the length of the audio stream).std::io::SeekFrom::End(0)
. However, if possible, this function usesSEEKTABLE
metadata to narrow down the range, and thus speed up the seeking process.claxon::metadata::SeekTable
does not expose any API to access the data. Shall I add some as well?BTreeMap
rather thanVec
.FRAME_HEADER
, by matching both sync code and 8-bit CRC provided at last. Calculating CRC-16 for entire frame makes it even more robust, but I think it is excessive work, and there are very low possibility that CRC-8 accidentally matches.FlacSamples::seek
seeks the reader to the desired sample. That is, the next sample yielded from the iterator will precisely has the sample index that equal to the provided argument. Internally, it callsFrameReader::seek
and then discards the first few frames in the first block obtained.Concerns and future tasks
audio_block_start_position
will be stored asOption<u64>
, as it may be vacant for non-seekable reader while filled for seekable one. But this is independent of whether the reader is actually seekable or not. That is, one can use the original constructorFrameReader::new
while providing seekable reader, and then callseek
function. So, the field may beNone
for the first timeseek
function is called, in which case we have to go back to the beginning of the stream and actually fill it with appropriate value. This may not be a big problem, but something that can be avoided by "type puzzle": For example, we can define a marker traitIsSeekable
, create two marker structsSeekable
andNonSeekable
, acceptS: IsSeekable
as additional type parameter forFlacReader
,FrameReader
andFlacSamples
, and define associated typeIsSeekable::AudioBlockStartPosition
which is set to()
forNonSeekable
andu64
forSeekable
. Whew. Personally I love this kind of type puzzle but I doubt it's suitable for public library. Alternatively, we can define seekable variants forFlacReader
,FrameReader
andFlacSamples
, which may make API less understandable and make code confusing.Thanks for reading this lengthy draft! Feel free to make questions or oppositions. I'll get started in a few days anyway.
The text was updated successfully, but these errors were encountered: