github.com/pion/webrtc/v4@v4.0.1/pkg/media/samplebuilder/samplebuilder.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  // Package samplebuilder provides functionality to reconstruct media frames from RTP packets.
     5  package samplebuilder
     6  
     7  import (
     8  	"math"
     9  	"time"
    10  
    11  	"github.com/pion/rtp"
    12  	"github.com/pion/webrtc/v4/pkg/media"
    13  )
    14  
    15  // SampleBuilder buffers packets until media frames are complete.
    16  type SampleBuilder struct {
    17  	maxLate          uint16 // how many packets to wait until we get a valid Sample
    18  	maxLateTimestamp uint32 // max timestamp between old and new timestamps before dropping packets
    19  	buffer           [math.MaxUint16 + 1]*rtp.Packet
    20  	preparedSamples  [math.MaxUint16 + 1]*media.Sample
    21  
    22  	// Interface that allows us to take RTP packets to samples
    23  	depacketizer rtp.Depacketizer
    24  
    25  	// sampleRate allows us to compute duration of media.SamplecA
    26  	sampleRate uint32
    27  
    28  	// the handler to be called when the builder is about to remove the
    29  	// reference to some packet.
    30  	packetReleaseHandler func(*rtp.Packet)
    31  
    32  	// filled contains the head/tail of the packets inserted into the buffer
    33  	filled sampleSequenceLocation
    34  
    35  	// active contains the active head/tail of the timestamp being actively processed
    36  	active sampleSequenceLocation
    37  
    38  	// prepared contains the samples that have been processed to date
    39  	prepared sampleSequenceLocation
    40  
    41  	lastSampleTimestamp *uint32
    42  
    43  	// number of packets forced to be dropped
    44  	droppedPackets uint16
    45  
    46  	// number of padding packets detected and dropped (this will be a subset of `droppedPackets`)
    47  	paddingPackets uint16
    48  
    49  	// allows inspecting head packets of each sample and then returns a custom metadata
    50  	packetHeadHandler func(headPacket interface{}) interface{}
    51  
    52  	// return array of RTP headers as Sample.RTPHeaders
    53  	returnRTPHeaders bool
    54  }
    55  
    56  // New constructs a new SampleBuilder.
    57  // maxLate is how long to wait until we can construct a completed media.Sample.
    58  // maxLate is measured in RTP packet sequence numbers.
    59  // A large maxLate will result in less packet loss but higher latency.
    60  // The depacketizer extracts media samples from RTP packets.
    61  // Several depacketizers are available in package github.com/pion/rtp/codecs.
    62  func New(maxLate uint16, depacketizer rtp.Depacketizer, sampleRate uint32, opts ...Option) *SampleBuilder {
    63  	s := &SampleBuilder{maxLate: maxLate, depacketizer: depacketizer, sampleRate: sampleRate}
    64  	for _, o := range opts {
    65  		o(s)
    66  	}
    67  	return s
    68  }
    69  
    70  func (s *SampleBuilder) tooOld(location sampleSequenceLocation) bool {
    71  	if s.maxLateTimestamp == 0 {
    72  		return false
    73  	}
    74  
    75  	var foundHead *rtp.Packet
    76  	var foundTail *rtp.Packet
    77  
    78  	for i := location.head; i != location.tail; i++ {
    79  		if packet := s.buffer[i]; packet != nil {
    80  			foundHead = packet
    81  			break
    82  		}
    83  	}
    84  
    85  	if foundHead == nil {
    86  		return false
    87  	}
    88  
    89  	for i := location.tail - 1; i != location.head; i-- {
    90  		if packet := s.buffer[i]; packet != nil {
    91  			foundTail = packet
    92  			break
    93  		}
    94  	}
    95  
    96  	if foundTail == nil {
    97  		return false
    98  	}
    99  
   100  	return timestampDistance(foundHead.Timestamp, foundTail.Timestamp) > s.maxLateTimestamp
   101  }
   102  
   103  // fetchTimestamp returns the timestamp associated with a given sample location
   104  func (s *SampleBuilder) fetchTimestamp(location sampleSequenceLocation) (timestamp uint32, hasData bool) {
   105  	if location.empty() {
   106  		return 0, false
   107  	}
   108  	packet := s.buffer[location.head]
   109  	if packet == nil {
   110  		return 0, false
   111  	}
   112  	return packet.Timestamp, true
   113  }
   114  
   115  func (s *SampleBuilder) releasePacket(i uint16) {
   116  	var p *rtp.Packet
   117  	p, s.buffer[i] = s.buffer[i], nil
   118  	if p != nil && s.packetReleaseHandler != nil {
   119  		s.packetReleaseHandler(p)
   120  	}
   121  }
   122  
   123  // purgeConsumedBuffers clears all buffers that have already been consumed by
   124  // popping.
   125  func (s *SampleBuilder) purgeConsumedBuffers() {
   126  	s.purgeConsumedLocation(s.active, false)
   127  }
   128  
   129  // purgeConsumedLocation clears all buffers that have already been consumed
   130  // during a sample building method.
   131  func (s *SampleBuilder) purgeConsumedLocation(consume sampleSequenceLocation, forceConsume bool) {
   132  	if !s.filled.hasData() {
   133  		return
   134  	}
   135  
   136  	switch consume.compare(s.filled.head) {
   137  	case slCompareInside:
   138  		if !forceConsume {
   139  			break
   140  		}
   141  		fallthrough
   142  	case slCompareBefore:
   143  		s.releasePacket(s.filled.head)
   144  		s.filled.head++
   145  	}
   146  }
   147  
   148  // purgeBuffers flushes all buffers that are already consumed or those buffers
   149  // that are too late to consume.
   150  func (s *SampleBuilder) purgeBuffers(flush bool) {
   151  	s.purgeConsumedBuffers()
   152  
   153  	for (s.tooOld(s.filled) || (s.filled.count() > s.maxLate) || flush) && s.filled.hasData() {
   154  		if s.active.empty() {
   155  			// refill the active based on the filled packets
   156  			s.active = s.filled
   157  		}
   158  
   159  		if s.active.hasData() && (s.active.head == s.filled.head) {
   160  			// attempt to force the active packet to be consumed even though
   161  			// outstanding data may be pending arrival
   162  			if s.buildSample(true) != nil {
   163  				continue
   164  			}
   165  
   166  			// could not build the sample so drop it
   167  			s.active.head++
   168  			s.droppedPackets++
   169  		}
   170  
   171  		s.releasePacket(s.filled.head)
   172  		s.filled.head++
   173  	}
   174  }
   175  
   176  // Push adds an RTP Packet to s's buffer.
   177  //
   178  // Push does not copy the input. If you wish to reuse
   179  // this memory make sure to copy before calling Push
   180  func (s *SampleBuilder) Push(p *rtp.Packet) {
   181  	s.buffer[p.SequenceNumber] = p
   182  
   183  	switch s.filled.compare(p.SequenceNumber) {
   184  	case slCompareVoid:
   185  		s.filled.head = p.SequenceNumber
   186  		s.filled.tail = p.SequenceNumber + 1
   187  	case slCompareBefore:
   188  		s.filled.head = p.SequenceNumber
   189  	case slCompareAfter:
   190  		s.filled.tail = p.SequenceNumber + 1
   191  	case slCompareInside:
   192  		break
   193  	}
   194  	s.purgeBuffers(false)
   195  }
   196  
   197  // Flush marks all samples in the buffer to be popped.
   198  func (s *SampleBuilder) Flush() {
   199  	s.purgeBuffers(true)
   200  }
   201  
   202  const secondToNanoseconds = 1000000000
   203  
   204  // buildSample creates a sample from a valid collection of RTP Packets by
   205  // walking forwards building a sample if everything looks good clear and
   206  // update buffer+values
   207  // nolint: gocognit
   208  func (s *SampleBuilder) buildSample(purgingBuffers bool) *media.Sample {
   209  	if s.active.empty() {
   210  		s.active = s.filled
   211  	}
   212  
   213  	if s.active.empty() {
   214  		return nil
   215  	}
   216  
   217  	if s.filled.compare(s.active.tail) == slCompareInside {
   218  		s.active.tail = s.filled.tail
   219  	}
   220  
   221  	var consume sampleSequenceLocation
   222  
   223  	for i := s.active.head; s.buffer[i] != nil && s.active.compare(i) != slCompareAfter; i++ {
   224  		if s.depacketizer.IsPartitionTail(s.buffer[i].Marker, s.buffer[i].Payload) {
   225  			consume.head = s.active.head
   226  			consume.tail = i + 1
   227  			break
   228  		}
   229  		headTimestamp, hasData := s.fetchTimestamp(s.active)
   230  		if hasData && s.buffer[i].Timestamp != headTimestamp {
   231  			consume.head = s.active.head
   232  			consume.tail = i
   233  			break
   234  		}
   235  	}
   236  
   237  	if consume.empty() {
   238  		return nil
   239  	}
   240  
   241  	if !purgingBuffers && s.buffer[consume.tail] == nil {
   242  		// wait for the next packet after this set of packets to arrive
   243  		// to ensure at least one post sample timestamp is known
   244  		// (unless we have to release right now)
   245  		return nil
   246  	}
   247  
   248  	sampleTimestamp, _ := s.fetchTimestamp(s.active)
   249  	afterTimestamp := sampleTimestamp
   250  
   251  	// scan for any packet after the current and use that time stamp as the diff point
   252  	for i := consume.tail; i < s.active.tail; i++ {
   253  		if s.buffer[i] != nil {
   254  			afterTimestamp = s.buffer[i].Timestamp
   255  			break
   256  		}
   257  	}
   258  
   259  	// the head set of packets is now fully consumed
   260  	s.active.head = consume.tail
   261  
   262  	// prior to decoding all the packets, check if this packet
   263  	// would end being disposed anyway
   264  	if !s.depacketizer.IsPartitionHead(s.buffer[consume.head].Payload) {
   265  		isPadding := false
   266  		for i := consume.head; i != consume.tail; i++ {
   267  			if s.lastSampleTimestamp != nil && *s.lastSampleTimestamp == s.buffer[i].Timestamp && len(s.buffer[i].Payload) == 0 {
   268  				isPadding = true
   269  			}
   270  		}
   271  		s.droppedPackets += consume.count()
   272  		if isPadding {
   273  			s.paddingPackets += consume.count()
   274  		}
   275  		s.purgeConsumedLocation(consume, true)
   276  		s.purgeConsumedBuffers()
   277  		return nil
   278  	}
   279  
   280  	// merge all the buffers into a sample
   281  	data := []byte{}
   282  	var metadata interface{}
   283  	var rtpHeaders []*rtp.Header
   284  	for i := consume.head; i != consume.tail; i++ {
   285  		p, err := s.depacketizer.Unmarshal(s.buffer[i].Payload)
   286  		if err != nil {
   287  			return nil
   288  		}
   289  		if i == consume.head && s.packetHeadHandler != nil {
   290  			metadata = s.packetHeadHandler(s.depacketizer)
   291  		}
   292  		if s.returnRTPHeaders {
   293  			h := s.buffer[i].Header.Clone()
   294  			rtpHeaders = append(rtpHeaders, &h)
   295  		}
   296  
   297  		data = append(data, p...)
   298  	}
   299  	samples := afterTimestamp - sampleTimestamp
   300  
   301  	sample := &media.Sample{
   302  		Data:               data,
   303  		Duration:           time.Duration((float64(samples)/float64(s.sampleRate))*secondToNanoseconds) * time.Nanosecond,
   304  		PacketTimestamp:    sampleTimestamp,
   305  		PrevDroppedPackets: s.droppedPackets,
   306  		Metadata:           metadata,
   307  		RTPHeaders:         rtpHeaders,
   308  	}
   309  
   310  	s.droppedPackets = 0
   311  	s.paddingPackets = 0
   312  	s.lastSampleTimestamp = new(uint32)
   313  	*s.lastSampleTimestamp = sampleTimestamp
   314  
   315  	s.preparedSamples[s.prepared.tail] = sample
   316  	s.prepared.tail++
   317  
   318  	s.purgeConsumedLocation(consume, true)
   319  	s.purgeConsumedBuffers()
   320  
   321  	return sample
   322  }
   323  
   324  // Pop compiles pushed RTP packets into media samples and then
   325  // returns the next valid sample (or nil if no sample is compiled).
   326  func (s *SampleBuilder) Pop() *media.Sample {
   327  	_ = s.buildSample(false)
   328  	if s.prepared.empty() {
   329  		return nil
   330  	}
   331  	var result *media.Sample
   332  	result, s.preparedSamples[s.prepared.head] = s.preparedSamples[s.prepared.head], nil
   333  	s.prepared.head++
   334  	return result
   335  }
   336  
   337  // seqnumDistance computes the distance between two sequence numbers
   338  func seqnumDistance(x, y uint16) uint16 {
   339  	diff := int16(x - y)
   340  	if diff < 0 {
   341  		return uint16(-diff)
   342  	}
   343  
   344  	return uint16(diff)
   345  }
   346  
   347  // timestampDistance computes the distance between two timestamps
   348  func timestampDistance(x, y uint32) uint32 {
   349  	diff := int32(x - y)
   350  	if diff < 0 {
   351  		return uint32(-diff)
   352  	}
   353  
   354  	return uint32(diff)
   355  }
   356  
   357  // An Option configures a SampleBuilder.
   358  type Option func(o *SampleBuilder)
   359  
   360  // WithPacketReleaseHandler set a callback when the builder is about to release
   361  // some packet.
   362  func WithPacketReleaseHandler(h func(*rtp.Packet)) Option {
   363  	return func(o *SampleBuilder) {
   364  		o.packetReleaseHandler = h
   365  	}
   366  }
   367  
   368  // WithPacketHeadHandler set a head packet handler to allow inspecting
   369  // the packet to extract certain information and return as custom metadata
   370  func WithPacketHeadHandler(h func(headPacket interface{}) interface{}) Option {
   371  	return func(o *SampleBuilder) {
   372  		o.packetHeadHandler = h
   373  	}
   374  }
   375  
   376  // WithMaxTimeDelay ensures that packets that are too old in the buffer get
   377  // purged based on time rather than building up an extraordinarily long delay.
   378  func WithMaxTimeDelay(maxLateDuration time.Duration) Option {
   379  	return func(o *SampleBuilder) {
   380  		totalMillis := maxLateDuration.Milliseconds()
   381  		o.maxLateTimestamp = uint32(int64(o.sampleRate) * totalMillis / 1000)
   382  	}
   383  }
   384  
   385  // WithRTPHeaders enables to collect RTP headers forming a Sample.
   386  // Useful for accessing RTP extensions associated to the Sample.
   387  func WithRTPHeaders(enable bool) Option {
   388  	return func(o *SampleBuilder) {
   389  		o.returnRTPHeaders = enable
   390  	}
   391  }