// Copyright 2023 LiveKit, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package codecmunger import ( "reflect" "testing" "github.com/stretchr/testify/require" "github.com/livekit/mediatransportutil/pkg/codec" "github.com/livekit/protocol/logger" "github.com/livekit/livekit-server/pkg/sfu/testutils" ) func compare(expected *VP8, actual *VP8) 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 newVP8() *VP8 { return NewVP8(logger.GetLogger()) } func TestSetLast(t *testing.T) { v := newVP8() params := &testutils.TestExtPacketParams{ SequenceNumber: 23333, Timestamp: 0xabcdef, SSRC: 0x12345678, } vp8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 13, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } extPkt, err := testutils.GetTestExtPacketVP8(params, vp8) require.NoError(t, err) require.NotNil(t, extPkt) expectedVP8 := VP8{ pictureIdWrapHandler: VP8PictureIdWrapHandler{ maxPictureId: 13466, maxMBit: true, totalWrap: 0, lastWrap: 0, }, extLastPictureId: 13467, pictureIdOffset: 0, pictureIdUsed: true, lastTl0PicIdx: 233, tl0PicIdxOffset: 0, tl0PicIdxUsed: true, tidUsed: true, lastKeyIdx: 23, keyIdxOffset: 0, keyIdxUsed: true, } v.SetLast(extPkt) require.True(t, compare(&expectedVP8, v)) } func TestUpdateOffsets(t *testing.T) { v := newVP8() params := &testutils.TestExtPacketParams{ SequenceNumber: 23333, Timestamp: 0xabcdef, SSRC: 0x12345678, } vp8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 13, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) v.SetLast(extPkt) params = &testutils.TestExtPacketParams{ SequenceNumber: 56789, Timestamp: 0xabcdef, SSRC: 0x87654321, } vp8 = &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 345, L: true, TL0PICIDX: 12, T: true, TID: 13, Y: true, K: true, KEYIDX: 4, HeaderSize: 6, IsKeyFrame: true, } extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) v.UpdateOffsets(extPkt) expectedVP8 := VP8{ pictureIdWrapHandler: VP8PictureIdWrapHandler{ maxPictureId: 344, maxMBit: true, totalWrap: 0, lastWrap: 0, }, extLastPictureId: 13467, pictureIdOffset: 345 - 13467 - 1, pictureIdUsed: true, lastTl0PicIdx: 233, tl0PicIdxOffset: (12 - 233 - 1) & 0xff, tl0PicIdxUsed: true, tidUsed: true, lastKeyIdx: 23, keyIdxOffset: (4 - 23 - 1) & 0x1f, keyIdxUsed: true, } require.True(t, compare(&expectedVP8, v)) } func TestOutOfOrderPictureId(t *testing.T) { v := newVP8() params := &testutils.TestExtPacketParams{ SequenceNumber: 23333, Timestamp: 0xabcdef, SSRC: 0x12345678, } vp8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 1, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) v.SetLast(extPkt) v.UpdateAndGet(extPkt, false, false, 2) // out-of-order sequence number not in the missing picture id cache vp8.PictureID = 13466 extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) nIn, buf, err := v.UpdateAndGet(extPkt, true, false, 2) require.Error(t, err) require.ErrorIs(t, err, ErrOutOfOrderVP8PictureIdCacheMiss) require.Equal(t, 0, nIn) require.Nil(t, buf) // create a hole in picture id vp8.PictureID = 13469 extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8) expectedVP8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13469, L: true, TL0PICIDX: 233, T: true, TID: 1, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } marshalledVP8, err := expectedVP8.Marshal() require.NoError(t, err) nIn, buf, err = v.UpdateAndGet(extPkt, false, true, 2) require.NoError(t, err) require.Equal(t, 6, nIn) require.Equal(t, marshalledVP8, buf) // 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) expectedVP8 = &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13468, L: true, TL0PICIDX: 233, T: true, TID: 1, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } marshalledVP8, err = expectedVP8.Marshal() require.NoError(t, err) nIn, buf, err = v.UpdateAndGet(extPkt, true, false, 2) require.NoError(t, err) require.Equal(t, 6, nIn) require.Equal(t, marshalledVP8, buf) } func TestTemporalLayerFiltering(t *testing.T) { v := newVP8() params := &testutils.TestExtPacketParams{ SequenceNumber: 23333, Timestamp: 0xabcdef, SSRC: 0x12345678, } vp8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 1, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) v.SetLast(extPkt) // translate nIn, buf, err := v.UpdateAndGet(extPkt, false, false, 0) require.Error(t, err) require.ErrorIs(t, err, ErrFilteredVP8TemporalLayer) require.Equal(t, 0, nIn) require.Nil(t, buf) dropped, _ := v.droppedPictureIds.Get(13467) require.True(t, dropped) 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) nIn, buf, err = v.UpdateAndGet(extPkt, false, false, 0) require.Error(t, err) require.ErrorIs(t, err, ErrFilteredVP8TemporalLayer) require.Equal(t, 0, nIn) require.Nil(t, buf) dropped, _ = v.droppedPictureIds.Get(13467) require.True(t, dropped) 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) nIn, buf, err = v.UpdateAndGet(extPkt, false, false, 0) require.Error(t, err) require.ErrorIs(t, err, ErrFilteredVP8TemporalLayer) require.Equal(t, 0, nIn) require.Nil(t, buf) dropped, _ = v.droppedPictureIds.Get(13467) require.True(t, dropped) require.EqualValues(t, 1, v.pictureIdOffset) } func TestGapInSequenceNumberSamePicture(t *testing.T) { v := newVP8() params := &testutils.TestExtPacketParams{ SequenceNumber: 65533, Timestamp: 0xabcdef, SSRC: 0x12345678, PayloadSize: 33, } vp8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 1, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) v.SetLast(extPkt) expectedVP8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 1, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } marshalledVP8, err := expectedVP8.Marshal() require.NoError(t, err) nIn, buf, err := v.UpdateAndGet(extPkt, false, false, 2) require.NoError(t, err) require.Equal(t, 6, nIn) require.Equal(t, marshalledVP8, buf) // telling there is a gap in sequence number will add pictures to missing picture cache expectedVP8 = &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 1, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } marshalledVP8, err = expectedVP8.Marshal() require.NoError(t, err) nIn, buf, err = v.UpdateAndGet(extPkt, false, true, 2) require.NoError(t, err) require.Equal(t, 6, nIn) require.Equal(t, marshalledVP8, buf) value, ok := v.PictureIdOffset(13467) require.True(t, ok) require.EqualValues(t, 0, value) } func TestUpdateAndGetPadding(t *testing.T) { v := newVP8() params := &testutils.TestExtPacketParams{ SequenceNumber: 23333, Timestamp: 0xabcdef, SSRC: 0x12345678, PayloadSize: 20, } vp8 := &codec.VP8{ FirstByte: 25, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 13, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8) v.SetLast(extPkt) // getting padding with repeat of last picture buf, err := v.UpdateAndGetPadding(false) require.NoError(t, err) expectedVP8 := codec.VP8{ FirstByte: 16, I: true, M: true, PictureID: 13467, L: true, TL0PICIDX: 233, T: true, TID: 0, Y: true, K: true, KEYIDX: 23, HeaderSize: 6, IsKeyFrame: true, } marshalledVP8, err := expectedVP8.Marshal() require.NoError(t, err) require.Equal(t, marshalledVP8, buf) // getting padding with new picture buf, err = v.UpdateAndGetPadding(true) require.NoError(t, err) expectedVP8 = codec.VP8{ FirstByte: 16, I: true, M: true, PictureID: 13468, L: true, TL0PICIDX: 234, T: true, TID: 0, Y: true, K: true, KEYIDX: 24, HeaderSize: 6, IsKeyFrame: true, } marshalledVP8, err = expectedVP8.Marshal() require.NoError(t, err) require.Equal(t, marshalledVP8, buf) } 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 }