diff --git a/pkg/media/media.go b/pkg/media/media.go index 4b00edbe7d2..020b9967b48 100644 --- a/pkg/media/media.go +++ b/pkg/media/media.go @@ -14,6 +14,7 @@ type Sample struct { Duration time.Duration PacketTimestamp uint32 PrevDroppedPackets uint16 + PrevPaddingPackets uint16 } // Writer defines an interface to handle diff --git a/pkg/media/samplebuilder/samplebuilder.go b/pkg/media/samplebuilder/samplebuilder.go index e928f14314d..a04fd4dc97c 100644 --- a/pkg/media/samplebuilder/samplebuilder.go +++ b/pkg/media/samplebuilder/samplebuilder.go @@ -35,8 +35,13 @@ type SampleBuilder struct { // prepared contains the samples that have been processed to date prepared sampleSequenceLocation + lastSampleTimestamp *uint32 + // number of packets forced to be dropped droppedPackets uint16 + + // number of padding packets detected and dropped (this will be a subset of `droppedPackets`) + paddingPackets uint16 } // New constructs a new SampleBuilder. @@ -185,6 +190,7 @@ const secondToNanoseconds = 1000000000 // buildSample creates a sample from a valid collection of RTP Packets by // walking forwards building a sample if everything looks good clear and // update buffer+values +// nolint: gocognit func (s *SampleBuilder) buildSample(purgingBuffers bool) *media.Sample { if s.active.empty() { s.active = s.filled @@ -242,7 +248,17 @@ func (s *SampleBuilder) buildSample(purgingBuffers bool) *media.Sample { // prior to decoding all the packets, check if this packet // would end being disposed anyway if !s.depacketizer.IsPartitionHead(s.buffer[consume.head].Payload) { + // libwebrtc may send empty padding packets to smooth out send rate. These packets do not carry any media payloads. + isPadding := false + for i := consume.head; i != consume.tail; i++ { + if s.lastSampleTimestamp != nil && *s.lastSampleTimestamp == s.buffer[i].Timestamp && len(s.buffer[i].Payload) == 0 { + isPadding = true + } + } s.droppedPackets += consume.count() + if isPadding { + s.paddingPackets += consume.count() + } s.purgeConsumedLocation(consume, true) s.purgeConsumedBuffers() return nil @@ -264,9 +280,13 @@ func (s *SampleBuilder) buildSample(purgingBuffers bool) *media.Sample { Duration: time.Duration((float64(samples)/float64(s.sampleRate))*secondToNanoseconds) * time.Nanosecond, PacketTimestamp: sampleTimestamp, PrevDroppedPackets: s.droppedPackets, + PrevPaddingPackets: s.paddingPackets, } s.droppedPackets = 0 + s.paddingPackets = 0 + s.lastSampleTimestamp = new(uint32) + *s.lastSampleTimestamp = sampleTimestamp s.preparedSamples[s.prepared.tail] = sample s.prepared.tail++ diff --git a/pkg/media/samplebuilder/samplebuilder_test.go b/pkg/media/samplebuilder/samplebuilder_test.go index c0d72670010..46e6e4d4fc5 100644 --- a/pkg/media/samplebuilder/samplebuilder_test.go +++ b/pkg/media/samplebuilder/samplebuilder_test.go @@ -37,6 +37,7 @@ func (f *fakeDepacketizer) IsPartitionHead(payload []byte) bool { return true } + // skip padding if len(payload) < 1 { return false } @@ -233,21 +234,23 @@ func TestSampleBuilder(t *testing.T) { // shamelessly stolen from webrtc-rs message: "Sample builder should recognize padding packets", packets: []*rtp.Packet{ - {Header: rtp.Header{SequenceNumber: 5000, Timestamp: 1}, Payload: []byte{1}}, - {Header: rtp.Header{SequenceNumber: 5001, Timestamp: 1}, Payload: []byte{2}}, - {Header: rtp.Header{SequenceNumber: 5002, Timestamp: 1, Marker: true}, Payload: []byte{3}}, - {Header: rtp.Header{SequenceNumber: 5003, Timestamp: 1}, Payload: []byte{}}, - {Header: rtp.Header{SequenceNumber: 5004, Timestamp: 1}, Payload: []byte{}}, - {Header: rtp.Header{SequenceNumber: 5005, Timestamp: 3}, Payload: []byte{1}}, - {Header: rtp.Header{SequenceNumber: 5006, Timestamp: 3, Marker: true}, Payload: []byte{7}}, - {Header: rtp.Header{SequenceNumber: 5007, Timestamp: 4}, Payload: []byte{1}}, + {Header: rtp.Header{SequenceNumber: 5000, Timestamp: 1}, Payload: []byte{1}}, // 1st packet + {Header: rtp.Header{SequenceNumber: 5001, Timestamp: 1}, Payload: []byte{2}}, // 2nd packet + {Header: rtp.Header{SequenceNumber: 5002, Timestamp: 1, Marker: true}, Payload: []byte{3}}, // 3rd packet + {Header: rtp.Header{SequenceNumber: 5003, Timestamp: 1}, Payload: []byte{}}, // Padding packet 1 + {Header: rtp.Header{SequenceNumber: 5004, Timestamp: 1}, Payload: []byte{}}, // Padding packet 2 + {Header: rtp.Header{SequenceNumber: 5005, Timestamp: 3}, Payload: []byte{1}}, // 6th packet + {Header: rtp.Header{SequenceNumber: 5006, Timestamp: 3, Marker: true}, Payload: []byte{7}}, // 7th packet + {Header: rtp.Header{SequenceNumber: 5007, Timestamp: 4}, Payload: []byte{1}}, // 7th packet }, withHeadChecker: true, headBytes: []byte{1}, samples: []*media.Sample{ - {Data: []byte{1, 2, 3}, Duration: 0, PacketTimestamp: 1}, + {Data: []byte{1, 2, 3}, Duration: 0, PacketTimestamp: 1, PrevDroppedPackets: 0}, // first sample }, - maxLate: 50, + maxLate: 50, + maxLateTimestamp: 2000, + extraPopAttempts: 1, }, { // shamelessly stolen from webrtc-rs @@ -260,10 +263,10 @@ func TestSampleBuilder(t *testing.T) { {Header: rtp.Header{SequenceNumber: 5004, Timestamp: 2, Marker: true}, Payload: []byte{1}}, // 3rd valid packet {Header: rtp.Header{SequenceNumber: 5005, Timestamp: 3}, Payload: []byte{1}}, // 4th valid packet, start of next sample }, - withHeadChecker: true, - headBytes: []byte{1}, + withHeadChecker: true, + headBytes: []byte{1}, samples: []*media.Sample{ - {Data: []byte{1, 2}, Duration: 0, PacketTimestamp: 1, PrevDroppedPackets: 0}, // 1st sample + {Data: []byte{1, 2}, Duration: 0, PacketTimestamp: 1, PrevDroppedPackets: 0}, // 1st sample }, maxLate: 50, maxLateTimestamp: 2000,