Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions internal/celt/allocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,28 +546,6 @@ func pulsesToBits(band, lm, pulses int) int {
return int(pulseCacheBits[cacheStart+pulses]) + 1
}

func intensityStartBand(bitrateBps, frameMs int) int {
framesPerSec := 1000 / frameMs
effectiveKbps := (bitrateBps - 80*framesPerSec) / 1000

switch {
case effectiveKbps < 35:
return 8
case effectiveKbps < 50:
return 12
case effectiveKbps < 68:
return 16
case effectiveKbps < 84:
return 17
case effectiveKbps < 102:
return 19
case effectiveKbps < 130:
return 20
default:
return maxBands
}
}

// decodeFineEnergy applies the first RFC 6716 Section 4.3.2.2 fine-energy
// refinement, using the number of raw bits assigned by Section 4.3.3.
func (d *Decoder) decodeFineEnergy(info *frameSideInfo, fineQuant [maxBands]int) {
Expand Down
45 changes: 25 additions & 20 deletions internal/celt/allocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,36 +81,41 @@ func rangeDecoderWithRawBits(bits byte) rangecoding.Decoder {
return decoder
}

func TestIntensityStartBand(t *testing.T) {
// RFC 6716 Table 66 thresholds for 20ms frames (frameMs=20, framesPerSec=50).
// effectiveKbps = (bitrateBps - 80*50) / 1000.
func TestIntensityBandForRate(t *testing.T) {
// libopus intensity_thresholds, read against equiv_rate in kb/s. The values
// are the bands opus_demo picks at each of these rates for 20 ms stereo.
cases := []struct {
bitrateBps int
startBand int
kbps int
band int
}{
{32000, 8},
{45000, 12},
{64000, 16},
{96000, 19},
{128000, 20},
{160000, maxBands},
{23, 9},
{31, 10},
{47, 12},
{63, 15},
{95, 19},
{127, 20},
}

for _, tc := range cases {
t.Run("", func(t *testing.T) {
got := intensityStartBand(tc.bitrateBps, 20)
assert.Equal(t, tc.startBand, got,
"bitrateBps=%d", tc.bitrateBps)
})
got := intensityBandForRate(tc.kbps, 0)
assert.Equal(t, tc.band, got, "kbps=%d", tc.kbps)
}
}

func TestIntensityStartBandMonotonic(t *testing.T) {
func TestIntensityBandForRateHysteresis(t *testing.T) {
// 24 kb/s alone lands on band 10, but coming from 9 the entry's margin
// (thresholds[9]+hysteresis[9] = 26) holds it there for another frame.
assert.Equal(t, 10, intensityBandForRate(24, 0))
assert.Equal(t, 9, intensityBandForRate(24, 9))
assert.Equal(t, 10, intensityBandForRate(26, 9))
}

func TestIntensityBandForRateMonotonic(t *testing.T) {
prev := 0
for bitrate := range 200000 {
got := intensityStartBand(bitrate, 20)
for kbps := range 200 {
got := intensityBandForRate(kbps, 0)
assert.GreaterOrEqual(t, got, prev,
"intensity must be monotonically non-decreasing: bitrate=%d got=%d prev=%d", bitrate, got, prev)
"intensity must be monotonically non-decreasing: kbps=%d got=%d prev=%d", kbps, got, prev)
prev = got
}
}
Expand Down
21 changes: 6 additions & 15 deletions internal/celt/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,23 +494,14 @@ func (e *Encoder) computeIntensityAndDualStereo(
return 0, 0
}

frameSampleCount := shortBlockSampleCount << info.lm
bitrateBps := int(info.totalBits) * sampleRate / frameSampleCount
frameMs := max(1, frameSampleCount*1000/sampleRate)
raw := intensityStartBand(bitrateBps, frameMs)
if e.prevIntensityBand == 0 {
e.prevIntensityBand = raw
}
// ±1 dead band: require two consecutive frames to confirm a direction
// change, matching the hysteresis pattern in libopus CELTEncoder.
if raw > e.prevIntensityBand+1 {
raw = e.prevIntensityBand + 1
} else if raw < e.prevIntensityBand-1 {
raw = e.prevIntensityBand - 1
}
// equiv_rate is the frame budget expressed as a steady bit rate, net of the
// per-packet overhead the reference charges (celt_encoder.c:1926).
frameBytes := int(info.totalBits) / 8
equivRate := frameBytes*8*50<<(3-info.lm) - (40*info.channelCount+20)*((400>>info.lm)-50)
raw := intensityBandForRate(equivRate/1000, e.prevIntensityBand)
e.prevIntensityBand = raw

targetIntensity = raw
targetIntensity = min(info.endBand, max(info.startBand, raw))
if chooseDualStereo(normalized, info.lm) {
targetDualStereo = 1
}
Expand Down
32 changes: 32 additions & 0 deletions internal/celt/hysteresis.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,35 @@ func hysteresisDecision(val float32, prevDecision int, thresholds [3]float32) in
return spreadNone
}
}

// intensityThresholds and intensityHysteresis are the reference tables from
// libopus celt_encoder.c, read against equiv_rate in kb/s. Index i is the
// intensity band chosen when the rate clears thresholds[i-1] but not
// thresholds[i].
//
//nolint:gochecknoglobals // Reference tables.
var (
intensityThresholds = [21]int{1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 36, 44, 50, 56, 62, 67, 72, 79, 88, 106, 134}
intensityHysteresis = [21]int{1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 6, 8, 8}
)

// intensityBandForRate picks the first band coded in intensity stereo, porting
// libopus hysteresis_decision (celt/bands.c). The per-entry hysteresis keeps
// the band from oscillating when the rate sits on a threshold: a move away from
// prev only sticks once the rate clears that entry's margin.
func intensityBandForRate(kbps, prev int) int {
band := 0
for ; band < len(intensityThresholds); band++ {
if kbps < intensityThresholds[band] {
break
}
}
if band > prev && kbps < intensityThresholds[prev]+intensityHysteresis[prev] {
band = prev
}
if band < prev && kbps > intensityThresholds[prev-1]-intensityHysteresis[prev-1] {
band = prev
}

return band
}
Loading