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 }