Search the stereo angle at high complexity - #209
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #209 +/- ##
==========================================
+ Coverage 93.11% 93.17% +0.06%
==========================================
Files 58 58
Lines 10408 10473 +65
==========================================
+ Hits 9691 9758 +67
+ Misses 505 504 -1
+ Partials 212 211 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
FrantaBOT
approved these changes
Aug 13, 2026
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.
Description
At complexity levels 8 and 10, the encoder performed exactly the same as at level 5: there were no high-complexity paths. libopus wins there, and I measured where by disabling each feature separately in the reference:
theta_rdosecondMdcttheta_rdotakes almost all the quantization. That's what this reference carries.The idea: the stereo angle of each band is quantized to a discrete value, and rounding to the nearest value doesn't always give the best reconstruction. The reference encodes the band twice—rounding down and up—and keeps the one that most closely resembles the original, weighting each channel by its energy (
celt/bands.c,theta_rdo).The two passes are speculative, so between each one, the range coder, the band, and the noise fill seed must be rewound.
Two things from pion's design helped:
The encoder already resynthesizes:
algQuantwrites the reconstruction back tox, which is what the search needs to measure distortion. In libopus, this has to be enabled separately (resynth = !encode || theta_rdo).normis copied after the call, so restoringx/yworks fine on its own. The reference has to save and replace it separately.Range Encoder Rewinding
rangecoding.Encoderwins out overSaveInto/Restore. Saving scalars and truncating isn't enough: the encoder writes toappend, so the second speculative pass overwrites the same positions as the first, and truncation returns the length but not the content. That's why the state also copies the bytes, just likeOPUS_COPY(bytes_save, ...)in the reference.SaveIntoreuses the destination buffers, so it doesn't allocate on the hot path.TestEncoderSaveRestoreRewindsOutputcovers precisely that: it burns 40 symbols after the snapshot, rewinds, and requires byte-by-byte output identical to an encoder that never speculated.TestEncoderSaveRestoreRewindsOutput: It burns 40 symbols after the snapshot, rewinds, and requires byte-by-byte output identical to an encoder that never speculated.Measurement
Round-trip SNR at stereo CBR over 60 seconds of music, against
opus_demo restricted-celtat the same complexity:Complexity 5 is untouched: the search is below the same threshold as the reference.
Cost
+21% CPU usage in the stereo frame at complexity 8 (363 → 440 µs here), and zero extra allocations: the search buffers reside in the Encoder, not in the per-frame state. At complexity 5, the cost is zero.
What I'm not including
secondMdct(second MDCT in transient frames, also at complexity ≥8) has a measured value of +0.01 dB, so it's not included.Reference issue
Part of #34.