mirror of
https://github.com/pion/webrtc.git
synced 2025-09-27 03:25:58 +08:00

Use complete sentences, with periods. Explain all parameters. Cross-reference other useful packages.
159 lines
4.7 KiB
Go
159 lines
4.7 KiB
Go
// Package samplebuilder provides functionality to reconstruct media frames from RTP packets.
|
|
package samplebuilder
|
|
|
|
import (
|
|
"github.com/pion/rtp"
|
|
"github.com/pion/webrtc/v2/pkg/media"
|
|
)
|
|
|
|
// SampleBuilder buffers packets until media frames are complete.
|
|
type SampleBuilder struct {
|
|
maxLate uint16 // how many packets to wait until we get a valid Sample
|
|
buffer [65536]*rtp.Packet
|
|
|
|
// Interface that allows us to take RTP packets to samples
|
|
depacketizer rtp.Depacketizer
|
|
|
|
// Last seqnum that has been added to buffer
|
|
lastPush uint16
|
|
|
|
// Last seqnum that has been successfully popped
|
|
// isContiguous is false when we start or when we have a gap
|
|
// that is older then maxLate
|
|
isContiguous bool
|
|
lastPopSeq uint16
|
|
lastPopTimestamp uint32
|
|
|
|
// Interface that checks whether the packet is the first fragment of the frame or not
|
|
partitionHeadChecker rtp.PartitionHeadChecker
|
|
}
|
|
|
|
// New constructs a new SampleBuilder.
|
|
// maxLate is how long to wait until we can construct a completed media.Sample.
|
|
// maxLate is measured in RTP packet sequence numbers.
|
|
// A large maxLate will result in less packet loss but higher latency.
|
|
// The depacketizer extracts media samples from RTP packets.
|
|
// Several depacketizers are available in package github.com/pion/rtp/codecs.
|
|
func New(maxLate uint16, depacketizer rtp.Depacketizer, opts ...Option) *SampleBuilder {
|
|
s := &SampleBuilder{maxLate: maxLate, depacketizer: depacketizer}
|
|
for _, o := range opts {
|
|
o(s)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Push adds an RTP Packet to s's buffer.
|
|
func (s *SampleBuilder) Push(p *rtp.Packet) {
|
|
s.buffer[p.SequenceNumber] = p
|
|
s.lastPush = p.SequenceNumber
|
|
s.buffer[p.SequenceNumber-s.maxLate] = nil
|
|
}
|
|
|
|
// We have a valid collection of RTP Packets
|
|
// walk forwards building a sample if everything looks good clear and update buffer+values
|
|
func (s *SampleBuilder) buildSample(firstBuffer uint16) (*media.Sample, uint32) {
|
|
data := []byte{}
|
|
|
|
for i := firstBuffer; s.buffer[i] != nil; i++ {
|
|
if s.buffer[i].Timestamp != s.buffer[firstBuffer].Timestamp {
|
|
lastTimeStamp := s.lastPopTimestamp
|
|
if !s.isContiguous {
|
|
if s.buffer[firstBuffer-1] != nil {
|
|
lastTimeStamp = s.buffer[firstBuffer-1].Timestamp
|
|
} else {
|
|
// If PartitionHeadChecker detects that the first packet is a head,
|
|
// the duration of the packet is not guessable
|
|
lastTimeStamp = s.buffer[firstBuffer].Timestamp
|
|
}
|
|
}
|
|
|
|
samples := s.buffer[i-1].Timestamp - lastTimeStamp
|
|
s.lastPopSeq = i - 1
|
|
s.isContiguous = true
|
|
s.lastPopTimestamp = s.buffer[i-1].Timestamp
|
|
for j := firstBuffer; j < i; j++ {
|
|
s.buffer[j] = nil
|
|
}
|
|
return &media.Sample{Data: data, Samples: samples}, s.lastPopTimestamp
|
|
}
|
|
|
|
p, err := s.depacketizer.Unmarshal(s.buffer[i].Payload)
|
|
if err != nil {
|
|
return nil, 0
|
|
}
|
|
|
|
data = append(data, p...)
|
|
}
|
|
return nil, 0
|
|
}
|
|
|
|
// Distance between two seqnums
|
|
func seqnumDistance(x, y uint16) uint16 {
|
|
diff := int16(x - y)
|
|
if diff < 0 {
|
|
return uint16(-diff)
|
|
}
|
|
|
|
return uint16(diff)
|
|
}
|
|
|
|
// Pop scans s's buffer for a valid sample.
|
|
// It returns nil if no valid samples have been found.
|
|
func (s *SampleBuilder) Pop() *media.Sample {
|
|
sample, _ := s.PopWithTimestamp()
|
|
return sample
|
|
}
|
|
|
|
// PopWithTimestamp scans s's buffer for a valid sample and its RTP timestamp.
|
|
// It returns nil, 0 when no valid samples have been found.
|
|
func (s *SampleBuilder) PopWithTimestamp() (*media.Sample, uint32) {
|
|
var i uint16
|
|
if !s.isContiguous {
|
|
i = s.lastPush - s.maxLate
|
|
} else {
|
|
if seqnumDistance(s.lastPopSeq, s.lastPush) > s.maxLate {
|
|
i = s.lastPush - s.maxLate
|
|
s.isContiguous = false
|
|
} else {
|
|
i = s.lastPopSeq + 1
|
|
}
|
|
}
|
|
|
|
for ; i != s.lastPush; i++ {
|
|
curr := s.buffer[i]
|
|
if curr == nil {
|
|
continue // we haven't hit a buffer yet, keep moving
|
|
}
|
|
|
|
if !s.isContiguous {
|
|
if s.partitionHeadChecker == nil {
|
|
if s.buffer[i-1] == nil {
|
|
continue // We have never popped a buffer, so we can't assert that the first RTP packet we encounter is valid
|
|
} else if s.buffer[i-1].Timestamp == curr.Timestamp {
|
|
continue // We have the same timestamps, so it is data that spans multiple RTP packets
|
|
}
|
|
} else {
|
|
if !s.partitionHeadChecker.IsPartitionHead(curr.Payload) {
|
|
continue
|
|
}
|
|
// We can start using this frame as it is a head of frame partition
|
|
}
|
|
}
|
|
|
|
// Initial validity checks have passed, walk forward
|
|
return s.buildSample(i)
|
|
}
|
|
return nil, 0
|
|
}
|
|
|
|
// An Option configures a SampleBuilder.
|
|
type Option func(o *SampleBuilder)
|
|
|
|
// WithPartitionHeadChecker assigns a codec-specific PartitionHeadChecker to SampleBuilder.
|
|
// Several PartitionHeadCheckers are available in package github.com/pion/rtp/codecs.
|
|
func WithPartitionHeadChecker(checker rtp.PartitionHeadChecker) Option {
|
|
return func(o *SampleBuilder) {
|
|
o.partitionHeadChecker = checker
|
|
}
|
|
}
|