Label AAC granules in the sample rate's timescale - #5
Merged
Conversation
The encoder's ring buffer built each granule's timestamp offset in the INCOMING timestamp's timescale. `workingIndex` counts frames, so the only timescale that turns it into a duration is the sample rate; inheriting the timestamp's scale is correct only when a producer happens to stamp in sample time. Capture audio does, which is why this never surfaced. A host-clock producer does not. At timescale 1e9, an offset of 1024 frames comes out as 1.024 MICROseconds instead of 21.3 ms, so every granule carved out of one appended buffer landed on essentially the same timestamp and the timeline then jumped by the whole buffer. Downstream uses those labels verbatim — MpegTsWriter drives the PCR from them and RtmpStream computes FLV deltas from them. Also closes a staleness: the final `createOutputBuffer` of an append re-arms `latestPresentationTimeStamp` before discovering there is nothing left to copy, so the next granule was labelled with the PREVIOUS buffer's timestamp. Reset on append — but only when `outputIndex` is zero, since a granule spanning two appends must keep the timestamp of the buffer that started it. Tests fail without the fix with exactly the predicted values: a granule labelled 1.000001024 instead of 2.0, and granule spacing off by 21.33 ms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
AudioEncoderRingBuffer.createOutputBufferbuilt each granule's timestamp offset like this:workingIndexcounts frames, so the only timescale that converts it to a duration is the sample rate. Inheriting the incoming timestamp's scale is correct only when the producer stamps in sample time — whichAVCaptureAudioDataOutputdoes, which is why this never surfaced.A host-clock producer stamps at timescale 1e9. There, an offset of 1024 frames is 1.024 microseconds instead of 21.3 ms. Every granule carved from one appended buffer landed on essentially the same timestamp, then the timeline jumped by the whole buffer.
MpegTsWriterdrives the PCR from these labels andRtmpStreamcomputes FLV deltas from them, so they reach the wire.(
CMTimeAdddoes not rescue this: LCM(48000, 1e9) = 3e9 overflowsInt32, so the sum keeps the host timescale.)Also fixed
The final
createOutputBufferof an append re-armslatestPresentationTimeStampbefore discovering there is nothing left to copy. That stale value then labelled the next granule with the previous buffer's timestamp. Reset on append — but only whenoutputIndex == 0, because a granule spanning two appends must keep the timestamp of the buffer that started it. That guard is why the third test exists.Verification
Reverted the fix and re-ran: the new tests fail with exactly the predicted values.
40 tests in 10 suites pass with it.
Scripts/check-containment.shclean.Upstream
This is vendored Moblin code and the bug is upstream's. Worth sending back — flagging rather than doing, since that is the repo owner's call.
🤖 Generated with Claude Code