Skip to content

Search the stereo angle at high complexity - #209

Merged
thomas-vilte merged 2 commits into
pion:mainfrom
thomas-vilte:feat/theta-rdo
Aug 13, 2026
Merged

Search the stereo angle at high complexity#209
thomas-vilte merged 2 commits into
pion:mainfrom
thomas-vilte:feat/theta-rdo

Conversation

@thomas-vilte

Copy link
Copy Markdown
Member

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:

at complexity 8 SNR
all active 15.3420
without theta_rdo 15.1246
without secondMdct 15.3324

theta_rdo takes 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: algQuant writes the reconstruction back to x, which is what the search needs to measure distortion. In libopus, this has to be enabled separately (resynth = !encode || theta_rdo).

  • norm is copied after the call, so restoring x/y works fine on its own. The reference has to save and replace it separately.

Range Encoder Rewinding

rangecoding.Encoder wins out over SaveInto/Restore. Saving scalars and truncating isn't enough: the encoder writes to append, 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 like OPUS_COPY(bytes_save, ...) in the reference. SaveInto reuses the destination buffers, so it doesn't allocate on the hot path.

TestEncoderSaveRestoreRewindsOutput covers 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-celt at the same complexity:

complexity bitrate pion before pion after libopus
8 96000 15.0721 15.2982 15.3420
8 48000 10.1179 10.3076 10.3454
5 96000 15.0721 15.0721 15.0736

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.

@thomas-vilte
thomas-vilte requested a review from FrantaBOT August 13, 2026 17:47
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.17%. Comparing base (9bfb54d) to head (a8c86a7).

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     
Flag Coverage Δ
go 93.17% <100.00%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@thomas-vilte
thomas-vilte merged commit f247f66 into pion:main Aug 13, 2026
20 checks passed
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.

2 participants