fix: correct Source trait semantics and span tracking bugs#831
fix: correct Source trait semantics and span tracking bugs#831roderickvd wants to merge 10 commits intomasterfrom
Conversation
Clarify Source::current_span_len() returns total span length (not remaining), while size_hint() returns remaining samples. Fix multiple bugs in span boundary detection, seeking, and iterator implementations. Opportunistic fixes: - fix division by zero, off-by-one error, and zero case handling - prevent counter overflows - optimize vector allocations Mid-span seeking: best-effort tracking after try_seek() to detect span boundaries when seeking lands mid-span. Fixes #691
|
I did not fix the decoders because I had already done so with the infamous #786. |
16c90ba to
7538971
Compare
Parameter updates (channels/sample_rate) occur only at span boundaries or during post-seek detection. Reset counters and enter a detection mode on try_seek (Duration::ZERO is treated as start-of-span).
Use detect_span_boundary and reset_seek_span_tracking to detect span boundaries and parameter changes.
|
Verifying span boundary behavior in the other sources, latest commits I also added:
w.r.t. the first, I wonder how many people use that, given it only ever worked correctly on mono sources. |
|
I want to take a final look at a couple of other sources, that may need to deal with mid-stream endings as well. In the meantime, feel free to review what we've got already, as I might apply the same patterns that we've already got. |
Document that Sources must emit complete frames and pad with silence when ending mid-frame, and ensure that decoders do so.
|
This thing is getting big but I think I've got it down now. Took me a good week's worth of evenings! I've left the intermediate commits intact so you can see my journey. I've been going back-and-forth to defend against the fact that when source iterators end mid-frame, multi-channel audio and queuing blows up. First I added defensive measures to "filter" sources that depend on proper frame alignment. Then, I removed it from the filters in favor of placing it with the "producer" sources: the decoders, mostly, and documenting the frame-alignment requirement. I think it's better this way. |
|
Indeed! And the sad part is that I expect that 90% of use cases will never use it. Two things I can think of:
Point 2 is easily done and I had already put in... well you know. Thanks for your review. |
|
Those perf. changes are indeed not insignificant, bummer. I would like to merge this sooner rather then later though (as it's not really isolated). How about we cut a release this week and then merge this after? That gives us until the next release to provide more performance either through 1 or 2. I feel confident we can land the experimental source architecture within half a year, so before the next release. I'm using it right now in a side project and I'm really happy with how that code looks. example: |
OK for me. |
|
|
||
| let sample_rate = input.sample_rate(); | ||
| let applier = formula.to_applier(sample_rate.get()); | ||
|
|
There was a problem hiding this comment.
TODO(yara): Copy the new multichannel logic to the fixed sources.
| samples_counted: 0, | ||
| cached_span_len: None, | ||
| last_sample_rate: sample_rate, | ||
| last_channels: channels, |
There was a problem hiding this comment.
these params (cached_span_len, last_sample_rate and last_channels) seem to be shared by most sources now. Is this something we should extract to a struct?
like:
struct PrevParams {
span_len,
sample_rate,
channel_count,
}There was a problem hiding this comment.
maybe even params, then the detect_span_boundary could maybe take, prev and current both of type Params.
Going even further, a private extension trait implemented for everything Source could provide a .params(&self) -> Params method
There was a problem hiding this comment.
Sounds all good. I don’t know precisely where you are with per-source merging so again let me know if you’d like me to take something on.
There was a problem hiding this comment.
My plan was to get this and the re-sampler merged first and then slowly bring that in. Don't worry there will be a ton to review 😭.
|
|
||
| source_pointer_impl!(<'a, Src> Source for &'a mut Src where Src: Source,); | ||
|
|
||
| /// Detects if we're at a span boundary using dual-mode tracking. |
There was a problem hiding this comment.
this line does not add much for me (I dont know what dual-mode tracking means until I've read the function body)
There was a problem hiding this comment.
Yes we should clarify the modes or actually states.
| source_pointer_impl!(<'a, Src> Source for &'a mut Src where Src: Source,); | ||
|
|
||
| /// Detects if we're at a span boundary using dual-mode tracking. | ||
| /// Returns a tuple indicating whether we're at a span boundary and if parameters changed. |
There was a problem hiding this comment.
how about we return a struct with fields named: at_span_boundary and parameters_changed or something along does lines.
There was a problem hiding this comment.
Yeah that’s be richer info. Hypothetically, sources may need to know one but not the other.
There was a problem hiding this comment.
Hypothetically, sources may need to know one but not the other. We trust the compiler to rip the unused part out :)
| pub(crate) mod test_utils { | ||
| use super::*; | ||
|
|
||
| /// Test helper source that can end mid-frame for testing incomplete frame handling. |
There was a problem hiding this comment.
but we do not support ending mid-frame?
how this is different from SamplesBuffer?
There was a problem hiding this comment.
On mobile, but it: does? SamplesBuffer should now enforce it is frame aligned. This source doesn’t.
There was a problem hiding this comment.
Okay, but the incomplete frame alignment behavior is not used anywhere with this PR applied right?
There was a problem hiding this comment.
mhm this behavior was always redundant (inf zero + take dur). Especially now we are size hinting all the other generators as infinite we should consider bringing this in line.
There was a problem hiding this comment.
out of scope for this PR though
There was a problem hiding this comment.
Also we shouldn’t call it zero. That’s not equilibrium for unsigned integers. Yes it’ll produce silence but at full DC, burning your drivers.
There was a problem hiding this comment.
I've called it Silence for the new FixedSource and ConstSource. As part of that overhaul this will be removed.
Since generators never change their parameters they have no span so it does not make senses to have a generator implement Source if it can instead implement the more strict FixedSource and ConstSource.
ConstSource can be turned into a FixedSource easily and for free the same is true for turning a FixedSource into a Source.
| sample_rate: SampleRate, | ||
| num_samples: usize, | ||
| ) -> Self { | ||
| assert_eq!( |
There was a problem hiding this comment.
Rather have this return a Result instead, it should then just have a single struct as error type.
There was a problem hiding this comment.
Agreed. Did this to keep the API stable. But we don’t need to.
There was a problem hiding this comment.
Actually this will be removed soon so I don't think it matters, that is if you agree that having generators implement Source is redundant if they already implement FixedSource and ConstSource.
yara-blue
left a comment
There was a problem hiding this comment.
yay done reviewing :)
Should be a simple rebase to get main up to date again. I think there are some refactor opportunities and I left some questions here and there.
Let me know when it's ready for a second pass
|
Sorry I as processing from top to bottom until I saw your last comment. So you do want me to make the changes here, not as-you-go? Just confirming. |
Yes confirmed, we will merge this to main after you make the changes here on this PR. Let me know when everything is ready then I'll give it a second pass and merge. Nothing else will be merged to main until that is done to make sure we get no further conflicts. So take your time do not feel rushed! |
Clarify Source::current_span_len() returns total span length (not remaining), while size_hint() returns remaining samples of the entire iterator. Fix multiple bugs in span boundary detection, seeking, and iterator implementations.
Opportunistic fixes:
Mid-span seeking: best-effort tracking after try_seek() to detect span boundaries when seeking lands mid-span.
Fixes #691