From edafb0a118d188972ba3d42253cdc78dbb699667 Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Fri, 3 Dec 2021 21:57:49 +0530 Subject: [PATCH] VP8Munger tests (#229) A bit of clean up of unused bits. --- pkg/sfu/downtrack.go | 7 +- pkg/sfu/forwarder.go | 2 +- pkg/sfu/sequencer.go | 4 +- pkg/sfu/sequencer_test.go | 2 +- pkg/sfu/testutils/data.go | 12 + pkg/sfu/vp8munger.go | 27 +- pkg/sfu/vp8munger_test.go | 505 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 539 insertions(+), 20 deletions(-) create mode 100644 pkg/sfu/vp8munger_test.go diff --git a/pkg/sfu/downtrack.go b/pkg/sfu/downtrack.go index 41f76d9a3..21e59d9ff 100644 --- a/pkg/sfu/downtrack.go +++ b/pkg/sfu/downtrack.go @@ -683,10 +683,7 @@ func (d *DownTrack) writeBlankFrameRTP() error { } func (d *DownTrack) writeVP8BlankFrame(hdr *rtp.Header, frameEndNeeded bool) error { - blankVP8, err := d.forwarder.GetPaddingVP8(frameEndNeeded) - if err != nil { - return err - } + blankVP8 := d.forwarder.GetPaddingVP8(frameEndNeeded) // 1x1 key frame // Used even when closing out a previous frame. Looks like receivers @@ -694,7 +691,7 @@ func (d *DownTrack) writeVP8BlankFrame(hdr *rtp.Header, frameEndNeeded bool) err // frame, but that should be okay as there are key frames following) payload := make([]byte, blankVP8.HeaderSize+len(VP8KeyFrame1x1)) vp8Header := payload[:blankVP8.HeaderSize] - err = blankVP8.MarshalTo(vp8Header) + err := blankVP8.MarshalTo(vp8Header) if err != nil { return err } diff --git a/pkg/sfu/forwarder.go b/pkg/sfu/forwarder.go index 7d3654c38..51f00dd9a 100644 --- a/pkg/sfu/forwarder.go +++ b/pkg/sfu/forwarder.go @@ -688,7 +688,7 @@ func (f *Forwarder) GetSnTsForBlankFrames() ([]SnTs, bool, error) { return snts, frameEndNeeded, err } -func (f *Forwarder) GetPaddingVP8(frameEndNeeded bool) (*buffer.VP8, error) { +func (f *Forwarder) GetPaddingVP8(frameEndNeeded bool) *buffer.VP8 { f.lock.Lock() defer f.lock.Unlock() diff --git a/pkg/sfu/sequencer.go b/pkg/sfu/sequencer.go index 976617f27..17b3ec972 100644 --- a/pkg/sfu/sequencer.go +++ b/pkg/sfu/sequencer.go @@ -42,7 +42,7 @@ func (p *packetMeta) packVP8(vp8 *buffer.VP8) { uint64(vp8.TL0PICIDXPresent&0x1)<<54 | uint64(vp8.TIDPresent&0x1)<<53 | uint64(vp8.KEYIDXPresent&0x1)<<52 | - uint64(vp8.PictureID&0xFFFF)<<32 | + uint64(vp8.PictureID&0x7FFF)<<32 | uint64(vp8.TL0PICIDX&0xFF)<<24 | uint64(vp8.TID&0x3)<<22 | uint64(vp8.Y&0x1)<<21 | @@ -54,7 +54,7 @@ func (p *packetMeta) unpackVP8() *buffer.VP8 { return &buffer.VP8{ FirstByte: byte(p.misc >> 56), PictureIDPresent: int((p.misc >> 55) & 0x1), - PictureID: uint16((p.misc >> 32) & 0xFFFF), + PictureID: uint16((p.misc >> 32) & 0x7FFF), TL0PICIDXPresent: int((p.misc >> 54) & 0x1), TL0PICIDX: uint8((p.misc >> 24) & 0xFF), TIDPresent: int((p.misc >> 53) & 0x1), diff --git a/pkg/sfu/sequencer_test.go b/pkg/sfu/sequencer_test.go index 7c4194620..187c24469 100644 --- a/pkg/sfu/sequencer_test.go +++ b/pkg/sfu/sequencer_test.go @@ -118,7 +118,7 @@ func Test_packetMeta_VP8(t *testing.T) { expectedVP8 := &buffer.VP8{ FirstByte: 25, PictureIDPresent: 1, - PictureID: 55467, + PictureID: 55467 % 32768, MBit: false, TL0PICIDXPresent: 1, TL0PICIDX: 233, diff --git a/pkg/sfu/testutils/data.go b/pkg/sfu/testutils/data.go index 2ff520cc8..eddb7fc91 100644 --- a/pkg/sfu/testutils/data.go +++ b/pkg/sfu/testutils/data.go @@ -56,3 +56,15 @@ func GetTestExtPacket(params *TestExtPacketParams) (*buffer.ExtPacket, error) { } //-------------------------------------- + +func GetTestExtPacketVP8(params *TestExtPacketParams, vp8 *buffer.VP8) (*buffer.ExtPacket, error) { + ep, err := GetTestExtPacket(params) + if err != nil { + return nil, err + } + + ep.Payload = *vp8 + return ep, nil +} + +//-------------------------------------- diff --git a/pkg/sfu/vp8munger.go b/pkg/sfu/vp8munger.go index 4f1e68a7c..278953828 100644 --- a/pkg/sfu/vp8munger.go +++ b/pkg/sfu/vp8munger.go @@ -95,10 +95,10 @@ func (v *VP8Munger) UpdateAndGet(extPkt *buffer.ExtPacket, ordering SequenceNumb return nil, ErrNotVP8 } - extPictureId, newer := v.pictureIdWrapHandler.Unwrap(vp8.PictureID, vp8.MBit) + extPictureId := v.pictureIdWrapHandler.Unwrap(vp8.PictureID, vp8.MBit) // if out-of-order, look up missing picture id cache - if !newer { + if ordering == SequenceNumberOrderingOutOfOrder { value, ok := v.missingPictureIds.Get(extPictureId) if !ok { return nil, ErrOutOfOrderVP8PictureIdCacheMiss @@ -215,7 +215,7 @@ func (v *VP8Munger) UpdateAndGet(extPkt *buffer.ExtPacket, ordering SequenceNumb }, nil } -func (v *VP8Munger) UpdateAndGetPadding(newPicture bool) (*buffer.VP8, error) { +func (v *VP8Munger) UpdateAndGetPadding(newPicture bool) *buffer.VP8 { offset := 0 if newPicture { offset = 1 @@ -273,13 +273,19 @@ func (v *VP8Munger) UpdateAndGetPadding(newPicture bool) (*buffer.VP8, error) { IsKeyFrame: true, HeaderSize: headerSize, } - return vp8Packet, nil + return vp8Packet +} + +// for testing only +func (v *VP8Munger) PictureIdOffset(extPictureId int32) (int32, bool) { + value, ok := v.missingPictureIds.Get(extPictureId) + return value.(int32), ok } //----------------------------- // -// VP8Munger +// VP8PictureIdWrapHandler // func isWrapping7Bit(val1 int32, val2 int32) bool { return val2 < val1 && (val1-val2) > (1<<6) @@ -307,8 +313,8 @@ func (v *VP8PictureIdWrapHandler) MaxPictureId() int32 { return v.maxPictureId } -// unwrap picture id and update the maxPictureId. return unwrapped value, and whether picture id is newer -func (v *VP8PictureIdWrapHandler) Unwrap(pictureId uint16, mBit bool) (int32, bool) { +// unwrap picture id and update the maxPictureId. return unwrapped value +func (v *VP8PictureIdWrapHandler) Unwrap(pictureId uint16, mBit bool) int32 { // // VP8 Picture ID is specified very flexibly. // @@ -360,7 +366,7 @@ func (v *VP8PictureIdWrapHandler) Unwrap(pictureId uint16, mBit bool) (int32, bo // if v.totalWrap > 0 { if (v.maxPictureId + (v.lastWrap >> 1)) < (newPictureId + v.totalWrap) { - return newPictureId + v.totalWrap - v.lastWrap, false + return newPictureId + v.totalWrap - v.lastWrap } } @@ -371,7 +377,7 @@ func (v *VP8PictureIdWrapHandler) Unwrap(pictureId uint16, mBit bool) (int32, bo // 2. Wrapping from 15-bit -> 15-bit (32767 -> 0) // 3. Wrapping from 8-bit -> 8-bit (127 -> 0) // In all cases, looking at the mode of previous picture id will - // ensure that we are calculating the rap properly. + // ensure that we are calculating the wrap properly. // wrap := int32(0) if v.maxMBit { @@ -390,8 +396,7 @@ func (v *VP8PictureIdWrapHandler) Unwrap(pictureId uint16, mBit bool) (int32, bo } newPictureId += v.totalWrap - // >= in the below check as there could be multiple packets per picture - return newPictureId, newPictureId >= v.maxPictureId + return newPictureId } func (v *VP8PictureIdWrapHandler) UpdateMaxPictureId(extPictureId int32, mBit bool) { diff --git a/pkg/sfu/vp8munger_test.go b/pkg/sfu/vp8munger_test.go new file mode 100644 index 000000000..472c8b950 --- /dev/null +++ b/pkg/sfu/vp8munger_test.go @@ -0,0 +1,505 @@ +package sfu + +import ( + "reflect" + "testing" + + "github.com/livekit/livekit-server/pkg/sfu/buffer" + "github.com/livekit/livekit-server/pkg/sfu/testutils" + + "github.com/stretchr/testify/require" +) + +func compare(expected *VP8Munger, actual *VP8Munger) bool { + return reflect.DeepEqual(expected.pictureIdWrapHandler, actual.pictureIdWrapHandler) && + expected.extLastPictureId == actual.extLastPictureId && + expected.pictureIdOffset == actual.pictureIdOffset && + expected.pictureIdUsed == actual.pictureIdUsed && + expected.lastTl0PicIdx == actual.lastTl0PicIdx && + expected.tl0PicIdxOffset == actual.tl0PicIdxOffset && + expected.tl0PicIdxUsed == actual.tl0PicIdxUsed && + expected.tidUsed == actual.tidUsed && + expected.lastKeyIdx == actual.lastKeyIdx && + expected.keyIdxOffset == actual.keyIdxOffset && + expected.keyIdxUsed == actual.keyIdxUsed +} + +func TestSetLast(t *testing.T) { + v := NewVP8Munger() + + params := &testutils.TestExtPacketParams{ + SequenceNumber: 23333, + Timestamp: 0xabcdef, + SSRC: 0x12345678, + } + vp8 := &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 13, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + } + extPkt, err := testutils.GetTestExtPacketVP8(params, vp8) + require.NoError(t, err) + require.NotNil(t, extPkt) + + expectedVP8Munger := VP8Munger{ + VP8MungerParams: VP8MungerParams{ + pictureIdWrapHandler: VP8PictureIdWrapHandler{ + maxPictureId: 13466, + maxMBit: true, + totalWrap: 0, + lastWrap: 0, + }, + extLastPictureId: 13467, + pictureIdOffset: 0, + pictureIdUsed: 1, + lastTl0PicIdx: 233, + tl0PicIdxOffset: 0, + tl0PicIdxUsed: 1, + tidUsed: 1, + lastKeyIdx: 23, + keyIdxOffset: 0, + keyIdxUsed: 1, + lastDroppedPictureId: -1, + }, + } + + v.SetLast(extPkt) + require.True(t, compare(&expectedVP8Munger, v)) +} + +func TestUpdateOffsets(t *testing.T) { + v := NewVP8Munger() + + params := &testutils.TestExtPacketParams{ + SequenceNumber: 23333, + Timestamp: 0xabcdef, + SSRC: 0x12345678, + } + vp8 := &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 13, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + } + extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) + v.SetLast(extPkt) + + params = &testutils.TestExtPacketParams{ + SequenceNumber: 56789, + Timestamp: 0xabcdef, + SSRC: 0x87654321, + } + vp8 = &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 345, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 12, + TIDPresent: 1, + TID: 13, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 4, + HeaderSize: 6, + IsKeyFrame: true, + } + extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) + v.UpdateOffsets(extPkt) + + expectedVP8Munger := VP8Munger{ + VP8MungerParams: VP8MungerParams{ + pictureIdWrapHandler: VP8PictureIdWrapHandler{ + maxPictureId: 344, + maxMBit: true, + totalWrap: 0, + lastWrap: 0, + }, + extLastPictureId: 13467, + pictureIdOffset: 345 - 13467 - 1, + pictureIdUsed: 1, + lastTl0PicIdx: 233, + tl0PicIdxOffset: (12 - 233 - 1) & 0xff, + tl0PicIdxUsed: 1, + tidUsed: 1, + lastKeyIdx: 23, + keyIdxOffset: (4 - 23 - 1) & 0x1f, + keyIdxUsed: 1, + lastDroppedPictureId: -1, + }, + } + require.True(t, compare(&expectedVP8Munger, v)) +} + +func TestOutOfOrderPictureId(t *testing.T) { + v := NewVP8Munger() + + params := &testutils.TestExtPacketParams{ + SequenceNumber: 23333, + Timestamp: 0xabcdef, + SSRC: 0x12345678, + } + vp8 := &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 1, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + } + extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) + v.SetLast(extPkt) + v.UpdateAndGet(extPkt, SequenceNumberOrderingContiguous, 2) + + // out-of-order sequence number not in the missing picture id cache + vp8.PictureID = 13466 + extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) + + tp, err := v.UpdateAndGet(extPkt, SequenceNumberOrderingOutOfOrder, 2) + require.Error(t, err) + require.ErrorIs(t, err, ErrOutOfOrderVP8PictureIdCacheMiss) + require.Nil(t, tp) + + // create a hole in picture id + vp8.PictureID = 13469 + extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) + + tpExpected := TranslationParamsVP8{ + header: &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13469, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 1, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + }, + } + tp, err = v.UpdateAndGet(extPkt, SequenceNumberOrderingGap, 2) + require.NoError(t, err) + require.NotNil(t, tp) + require.True(t, reflect.DeepEqual(tpExpected, *tp)) + + // all three, the last, the current and the in-between should have been added to missing picture id cache + value, ok := v.PictureIdOffset(13467) + require.True(t, ok) + require.EqualValues(t, 0, value) + + value, ok = v.PictureIdOffset(13468) + require.True(t, ok) + require.EqualValues(t, 0, value) + + value, ok = v.PictureIdOffset(13469) + require.True(t, ok) + require.EqualValues(t, 0, value) + + // out-of-order sequence number should be in the missing picture id cache + vp8.PictureID = 13468 + extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) + + tpExpected = TranslationParamsVP8{ + header: &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13468, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 1, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + }, + } + tp, err = v.UpdateAndGet(extPkt, SequenceNumberOrderingOutOfOrder, 2) + require.NoError(t, err) + require.NotNil(t, tp) + require.True(t, reflect.DeepEqual(tpExpected, *tp)) +} + +func TestTemporalLayerFiltering(t *testing.T) { + v := NewVP8Munger() + + params := &testutils.TestExtPacketParams{ + SequenceNumber: 23333, + Timestamp: 0xabcdef, + SSRC: 0x12345678, + } + vp8 := &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 1, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + } + extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) + v.SetLast(extPkt) + + // translate + tp, err := v.UpdateAndGet(extPkt, SequenceNumberOrderingContiguous, 0) + require.Error(t, err) + require.ErrorIs(t, err, ErrFilteredVP8TemporalLayer) + require.Nil(t, tp) + require.EqualValues(t, 13467, v.lastDroppedPictureId) + require.EqualValues(t, 1, v.pictureIdOffset) + + // another packet with the same picture id. + // It should be dropped, but offset should not be updated. + params.SequenceNumber = 23334 + extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) + + tp, err = v.UpdateAndGet(extPkt, SequenceNumberOrderingContiguous, 0) + require.Error(t, err) + require.ErrorIs(t, err, ErrFilteredVP8TemporalLayer) + require.Nil(t, tp) + require.EqualValues(t, 13467, v.lastDroppedPictureId) + require.EqualValues(t, 1, v.pictureIdOffset) + + // another packet with the same picture id, but a gap in sequence number. + // It should be dropped, but offset should not be updated. + params.SequenceNumber = 23337 + extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) + + tp, err = v.UpdateAndGet(extPkt, SequenceNumberOrderingContiguous, 0) + require.Error(t, err) + require.ErrorIs(t, err, ErrFilteredVP8TemporalLayer) + require.Nil(t, tp) + require.EqualValues(t, 13467, v.lastDroppedPictureId) + require.EqualValues(t, 1, v.pictureIdOffset) +} + +func TestGapInSequenceNumberSamePicture(t *testing.T) { + v := NewVP8Munger() + + params := &testutils.TestExtPacketParams{ + IsHead: true, + SequenceNumber: 65533, + Timestamp: 0xabcdef, + SSRC: 0x12345678, + PayloadSize: 33, + } + vp8 := &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 1, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + } + extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) + v.SetLast(extPkt) + + tpExpected := TranslationParamsVP8{ + header: &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 1, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + }, + } + tp, err := v.UpdateAndGet(extPkt, SequenceNumberOrderingContiguous, 2) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(*tp, tpExpected)) + + // telling there is a gap in sequence number will add pictures to missing picture cache + tpExpected = TranslationParamsVP8{ + header: &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 1, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + }, + } + tp, err = v.UpdateAndGet(extPkt, SequenceNumberOrderingGap, 2) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(*tp, tpExpected)) + + value, ok := v.PictureIdOffset(13467) + require.True(t, ok) + require.EqualValues(t, 0, value) +} + +func TestUpdateAndGetPadding(t *testing.T) { + v := NewVP8Munger() + + params := &testutils.TestExtPacketParams{ + IsHead: true, + SequenceNumber: 23333, + Timestamp: 0xabcdef, + SSRC: 0x12345678, + PayloadSize: 20, + } + vp8 := &buffer.VP8{ + FirstByte: 25, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 13, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + } + extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) + + v.SetLast(extPkt) + + // getting padding with repeat of last picture + blankVP8 := v.UpdateAndGetPadding(false) + expectedVP8 := buffer.VP8{ + FirstByte: 16, + PictureIDPresent: 1, + PictureID: 13467, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 233, + TIDPresent: 1, + TID: 0, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 23, + HeaderSize: 6, + IsKeyFrame: true, + } + require.True(t, reflect.DeepEqual(expectedVP8, *blankVP8)) + + // getting padding with new picture + blankVP8 = v.UpdateAndGetPadding(true) + expectedVP8 = buffer.VP8{ + FirstByte: 16, + PictureIDPresent: 1, + PictureID: 13468, + MBit: true, + TL0PICIDXPresent: 1, + TL0PICIDX: 234, + TIDPresent: 1, + TID: 0, + Y: 1, + KEYIDXPresent: 1, + KEYIDX: 24, + HeaderSize: 6, + IsKeyFrame: true, + } + require.True(t, reflect.DeepEqual(expectedVP8, *blankVP8)) +} + +func TestVP8PictureIdWrapHandler(t *testing.T) { + v := &VP8PictureIdWrapHandler{} + + v.Init(109, false) + require.Equal(t, int32(109), v.MaxPictureId()) + require.False(t, v.maxMBit) + + v.UpdateMaxPictureId(109350, true) + require.Equal(t, int32(109350), v.MaxPictureId()) + require.True(t, v.maxMBit) + + // start with something close to the 15-bit wrap around point + v.Init(32766, true) + + // out-of-order, do not wrap + extPictureId := v.Unwrap(32750, true) + require.Equal(t, int32(32750), extPictureId) + require.Equal(t, int32(0), v.totalWrap) + require.Equal(t, int32(0), v.lastWrap) + + // wrap at 15-bits + extPictureId = v.Unwrap(5, false) + require.Equal(t, int32(32773), extPictureId) // 15-bit wrap at 32768 + 5 = 32773 + require.Equal(t, int32(32768), v.totalWrap) + require.Equal(t, int32(32768), v.lastWrap) + + // set things near 7-bit wrap point + v.UpdateMaxPictureId(32893, false) // 32768 + 125 + + // wrap at 7-bits + extPictureId = v.Unwrap(5, true) + require.Equal(t, int32(32901), extPictureId) // 15-bit wrap at 32768 + 7-bit wrap at 128 + 5 = 32901 + require.Equal(t, int32(32896), v.totalWrap) // one 15-bit wrap + one 7-bit wrap + require.Equal(t, int32(128), v.lastWrap) + + // a new picture in 7-bit mode much with a gap in between. + // A big enough gap which would have been treated as out-of-order in 7-bit mode. + v.UpdateMaxPictureId(32901, false) + extPictureId = v.Unwrap(73, false) + require.Equal(t, int32(32841), extPictureId) // 15-bit wrap at 32768 + 73 = 32841 + + // a new picture in 15-bit mode much with a gap in between. + // A big enough gap which would have been treated as out-of-order in 7-bit mode. + v.UpdateMaxPictureId(32901, true) + v.lastWrap = int32(32768) + extPictureId = v.Unwrap(73, false) + require.Equal(t, int32(32969), extPictureId) // 15-bit wrap at 32768 + 7-bit wrap at 128 + 73 = 32969 +}