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
Fixed inconsistent seek behavior in SoundStream #1118
Conversation
I can confirm that the current master version suffer from these three issues
and
and
and that your code (which I haven't yet looked at) fixes them! 👍 |
Any updates on this? I'd prefer to see this merged, or at least approved, before I go back and update the loop points PR. But it doesn't sound like anyone's fully inspected my code yet. Since the fix is reproducible, hopefully it's just a matter of syntax and other nitpicks, if anything. And I'd love to see this make it into 2.4. |
I'm not so much familiar with the internal of the audio module, so it'd be better if someone else reviewed this. And I'm afraid targeting 2.4 will only postpone its release... :-/ |
I was away the whole week, I'll try to review your code ASAP (and fully read the PR description :). But I've seen something that should be removed: you added a public function to sf::Music which is not needed to fix the bugs (or I missed something?). Don't mix unrelated things in a single PR. If you want to add something to the public API, then you should first discuss it on the forum. |
Whoops, |
Can confirm that it works on my Windows 10 machine just fine. |
Just checking in since it's been a little while. I'm glad to see 2 confirmed fix cases, but has anyone had a chance to vet the code convention and style? |
I would have hoped that @LaurentGomila could take some time to look at it, since he's most familiar with the audio module. The style looks okay to me, except for the line break which seems unnecessary - we don't have a line width limit afaik. |
Sorry, my free time is really limited and when I get some, I usually have other (personal) things to do.
This should rather be done in
I would rather add a The second fix of this PR requires more attention, I'll have a look at it later. |
I think Laurent has some good points there. Are you willing to update your code @Cobaltergeist? |
3590c42
to
a020a71
Compare
Okay, I went and moved the sample position tracking, including the counter itself, the overrun clamping and the divide-by-0 checks, into As well as the |
if (sampleOffset > m_sampleCount) | ||
m_sampleOffset = m_sampleCount; | ||
else | ||
m_sampleOffset = sampleOffset; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or...
m_sampleOffset = std::min(sampleOffset, m_sampleCount);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I wrote that I was unsure whether all c++03 implementations supported uint64 as the parameter type.
But looking at it now, the standard says it's been templatized since at least c++98, so I'll go and make the switch.
Added some "cosmetic" comments. 😉 |
@LaurentGomila sorry for not replying directly to your line, but I cover something important in the 2nd half of this comment that I don't want hidden when I update the diff. I'd argue against completely omitting the first sample-read check simply for the sake of defensive programming. Especially since we allow users to add custom stream classes and sound readers that the underlying What if we change it to Actually, speaking of error cases, let me check what does happen when a file becomes inaccessible mid-stream... Oh jeez, it causes a segfault... Looks like
The latter saves stack frames and makes a retry counter easier to follow if we use one, but it abuses looping semantics a bit. Up to you guys which one I go for. |
It depends how we want the music to behave. In case we read less bytes than requested, I don't think we should stop the music, the back-end may have chosen to return less samples for some reason, but still be valid and able to return new data on the next call. In the case where no sample was returned at all, and we are not at EOF, it's less obvious. The input file may be stuck for some reason, and we may freeze ther music if we just keep on trying to read. I think stopping makes sense in this case. So, it seems like we both agree on how to change this piece of code ;)
Let's address this issue in another PR, since it's a different thing. This way we can finally end and merge this one. |
Alright, I merged up to the latest master and performed the cosmetic changes:
I have code for the segfault fix, but I'll include that in another PR once this one is merged. |
Sorry to bump this, but is there any news? Test suite results? Comments or approval from @LaurentGomila? Prospects of making the next merge list? I can't cleanly make the segfault fix PR until this is merged, since it modifies |
@Cobaltergeist Assuming you've got your code written already (or it's not that complex to do; not really up to date here right now), just create your pull request so people can try it standalone, comment on it, etc. and then just rebase it in case this PR is merged first. Just make sure to note the possible conflict in your PR description (e.g. something like this: |
I noticed that @binary1248 ran some tests, and a few failed due to not recognizing |
Does anyone want to weigh in on my findings? I'm only touching the code if we agree that I need to compensate for unreliable readers. |
Can this/does this need fixing in the readers themselves? |
The FLAC reader will indeed need fixing at some point. I'm just not sure that it falls into the scope of this PR. While I guess it can fit the definition of "Fixing Inconsistent Seek Behavior", it's a bit more outside the body of code already being modified than I'd like. At the very least's it's gonna require a bit more digging. Is that worth delaying this PR's merge? While my code could be doing a bit more to work around violated assumptions, I don't think its assumptions themselves are unreasonable. My current impression is that I should add a safeguard for the offset overrun case to my PR now (which can be done with 2 lines in |
Actually I'm gonna revise my stance on this. I've managed to track down and fix the issue with the FLAC reader. The change isn't huge, and it fits the spirit of this PR, so unless I hear objections, I'd like to include it in this commit. The problem was that the underlying FLAC decoder has 2 seeking quirks that First, every seek operation triggers a Second, the FLAC decoder does not like seeking straight to EOF. The workaround is easy. I just seek to one sample before the EOF, call Please let me know if it's worthwhile to include my new fix in this PR, or if we should just get this one merged while I make a new one. |
Sounds good. Although linked to the same issue, these two changes fix two distinct bugs, so I'd say make another PR for the FLAC reader. |
So how about I just add the following safety checks to
I'll put up the other PR soon. |
Can you explain again why And I'd also add some comments, as these extra checks may not look intuitive at first sight. |
With a compliant stream, an invalid But yeah, I'll leave the first clamp in, and add a comment. |
f045d39
to
7a1797b
Compare
That should do it. With As for this one, it should be ready to merge once the tests are re-run. |
Do we really need to clamp |
After seeking what happened with the non-compliant FLAC reader, my logic boiled down to this: Now that Currently, Basically, I see this as |
To me, it is just hiding potential flaws in readers. I can't imagine any valid situation where this check would be useful. We shouldn't be afraid to dig deeper if the true problem is there -- in reader(s). Yes, users can provide their own readers. And if we compensate for (hide) logic errors originating in readers, these users (and us as well) will never get a chance to spot them and provide a proper fix. |
Well, I guess I'll just go ahead and revert that... I just need to get this thing merged and over with. |
7a1797b
to
0909514
Compare
Agreed. Thank you again for your help, and for your patience 👍 |
|
Ignore me, everything's fine! I just messed up a git command and didn't reapply things properly. I redid it the right way, and things are behaving again. The current commit looks good to merge. |
This PR has been added to my merge list, meaning it will be merged soon, unless someone raises any concerns (again 😉). |
Merged in 4856aea on branch 2.4.x Thanks for all the work you put into this! 👍 |
Sorry for being so spotty with my updates to the Loop Points. One of the things I was tasked to do was isolate my bugfixes for
sf::SoundStream
into a separate PR so that #629 could be decided independently.Now, for anyone who doesn't know what this is about, here's a summary of what I addressed in this PR, namely two edge-cases that break the consistency of
m_samplesProcessed
and one regression whensetPlayingOffset()
andplay()
are used when the sound is stopped:I only found the first one because I was using a file that was an exact numbers of seconds long, which matches the SoundStream buffer size. Currently, if
onGetData()
reads to the exact end of the file, it will returntrue
due to the full read, even though there's no more data available afterward. However, this means that the buffer isn't properly marked as an "end". When it's time to read the next buffer after that, it will read 0 bytes and returnfalse
. This will trigger a seek to 0 (in the current version without loop points), and flag the buffer as an "end". But since the read was 0, the buffer then gets refilled with 1 second of post-seek data. The two buffers get played normally, but now the 2nd one is flagged as the end, even though it contains beginning data. And when it gets unloaded, the sample counter gets reset one second too late. This creates a "lag" that persists over the playback, forever reporting an incorrect play position.My fix involved adding
m_tellPos
, insf::Music
to keep track of the sample position in the underlying file. Now, if the position afterwards lands on the EOF,onGetData()
will return false, even if the a read fills the whole buffer. I couldn't fix it insf::SoundStream
itself, because modifying the signature ofonSeek()
would break the API. Butsf::Music
is close enough to the underlying file to track its position, and has enough leverage with its return value fromonGetData()
so it can force a zero-seek at the right time.A slightly different case involved a similar error when the play position starts at the end-of-file. It causes the same "delayed zero-read" effect, though the offset corrects itself after a loop iteration. Since no sound is loaded yet, there's no rightful owner of the "end" status, so my solution is the
immediateLoop
parameter infillAndPushBuffer()
it's only set totrue
during thefillQueue()
call as the thread starts up. It essentially givesfillAndPushBuffer()
permission to immediately adjustm_samplesProcessed
to the correct post-loop value, instead of deferring things until the refilled buffer gets unloaded a second too late.These fixes combined lead to an included easy-fix of #966, since
sf::Music()
can now clamp a seek overrun to the EOF, invoking the new reset behavior at the nextonGetData()
.I'd argue that the last "bug" is a regression.
setPlayingOffset()
used to always begin playing the sound when it was called. But when that behavior was removed, the ability to manually start playing afterward from the new offset wasn't added. I found this issue during my Loop Point testing, when I tried to set the "play start" by callingsetPlayingOffset()
betweenopenFromFile()
andplay()
. I noticed that the music always began from the start regardless, due to theonSeek(Time::Zero);
call inplay()
and I didn't see any proper reason for it. The paused and playing cases were already covered, and if the music was stopped, then there are 3 possibilities. Either the music was newly loaded (already 0), newly-stopped (already 0), or had its offset explicitly adjusted after loading or stopping (why would you overrule that?). So I removed theonSeek(Time::Zero);
line, and moved them_samplesProcessed = 0;
line toinitialize()
, to make sure case 1 is still covered.So that's my summary of the bugs, in isolation from #629. I'll replace the code in that PR with fast-forward from this one soon.
EDIT: Here's a new test.cpp. It's just my newest Loop Point one with the actual loop point calls disabled by a commented-out macro at the top. I still recommend using it with count.wav.
EDIT by @mantognini: both the source code and sound file in one archive