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