Skip to content

doc: describe when the partial block after a seek is written - #924

Open
Alb3e3 wants to merge 1 commit into
xiph:masterfrom
Alb3e3:docs-seek-partial-block-timing
Open

doc: describe when the partial block after a seek is written#924
Alb3e3 wants to merge 1 commit into
xiph:masterfrom
Alb3e3:docs-seek-partial-block-timing

Conversation

@Alb3e3

@Alb3e3 Alb3e3 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Documentation only, no behaviour change. It comes out of #906, where seeking into the last block of a stream looks like the write callback stops being called.

Chasing that down, the decoder is doing what it was built to do: write_audio_frame_to_client_() recognises the frame that contains the target sample while is_seeking is set, trims the samples before the target, calls the write callback with that partial block, and only then kicks out of seek mode. So the partial block is delivered from inside FLAC__stream_decoder_seek_absolute(), and decoding resumes with the block after it.

For a seek into any block but the last that is invisible: the next FLAC__stream_decoder_process_single() delivers the following frame, so the client keeps getting audio. For a seek into the last block there is no following frame, so process_single() returns true, calls nothing, and leaves the decoder in FLAC__STREAM_DECODER_END_OF_STREAM — which reads as lost audio even though those samples were already handed over during the seek.

Measured on a 258048-sample, 4096-blocksize file (write callbacks counted separately during the seek and during the following process_single()):

-- seeks into earlier blocks --
seek to 245760: during_seek=1 during_process=1 first_sample=245760 blocksize=4096 state=SEARCH_FOR_FRAME_SYNC
seek to 253951: during_seek=1 during_process=1 first_sample=253951 blocksize=4096 state=SEARCH_FOR_FRAME_SYNC
-- seeks into the last block --
seek to 253952: during_seek=1 during_process=0 first_sample=253952 blocksize=4096 state=END_OF_STREAM
seek to 257948: during_seek=1 during_process=0 first_sample=257948 blocksize=100  state=END_OF_STREAM
seek to 258047: during_seek=1 during_process=0 first_sample=258047 blocksize=1    state=END_OF_STREAM

No samples are lost in any of these cases — blocksize is exactly the number of samples from the target to the end of the stream.

The existing wording is what misleads:

Decoding will resume at the given sample. Note that because of this, the next write callback may contain a partial block.

"the next write callback" reads as a callback from a later process_single() call. This patch says when that callback actually happens and what to expect when the target is in the last block.

If the preference is to change the behaviour instead — hold the partial block back and deliver it from the next process_single() — I'm happy to look at that, but it would change what every existing client sees during a seek, so documenting the current contract seemed like the right first step.

AI tool disclosure

Prepared with AI assistance; I verified the code path and ran the measurements above myself, and can answer questions during review. The commit carries an Assisted-by: trailer.

FLAC__stream_decoder_seek_absolute() decodes the block containing the
target sample during the seek and hands the samples from the target
onwards to the write callback before it returns. The documentation only
said that the next write callback may contain a partial block, which
reads as if that callback comes from a later
FLAC__stream_decoder_process_single() call. Seeking into the last block
of a stream then looks like lost audio: the samples have already been
written, and the following process_single() call reports end of stream
without a write callback (issue xiph#906).

Assisted-by: Claude Code (Claude Opus 5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant