github.com/pion/webrtc/v4@v4.0.1/track_local_static.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 package webrtc 8 9 import ( 10 "strings" 11 "sync" 12 13 "github.com/pion/rtp" 14 "github.com/pion/webrtc/v4/internal/util" 15 "github.com/pion/webrtc/v4/pkg/media" 16 ) 17 18 // trackBinding is a single bind for a Track 19 // Bind can be called multiple times, this stores the 20 // result for a single bind call so that it can be used when writing 21 type trackBinding struct { 22 id string 23 ssrc, ssrcRTX, ssrcFEC SSRC 24 payloadType, payloadTypeRTX PayloadType 25 writeStream TrackLocalWriter 26 } 27 28 // TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets. 29 // If you wish to send a media.Sample use TrackLocalStaticSample 30 type TrackLocalStaticRTP struct { 31 mu sync.RWMutex 32 bindings []trackBinding 33 codec RTPCodecCapability 34 id, rid, streamID string 35 } 36 37 // NewTrackLocalStaticRTP returns a TrackLocalStaticRTP. 38 func NewTrackLocalStaticRTP(c RTPCodecCapability, id, streamID string, options ...func(*TrackLocalStaticRTP)) (*TrackLocalStaticRTP, error) { 39 t := &TrackLocalStaticRTP{ 40 codec: c, 41 bindings: []trackBinding{}, 42 id: id, 43 streamID: streamID, 44 } 45 46 for _, option := range options { 47 option(t) 48 } 49 50 return t, nil 51 } 52 53 // WithRTPStreamID sets the RTP stream ID for this TrackLocalStaticRTP. 54 func WithRTPStreamID(rid string) func(*TrackLocalStaticRTP) { 55 return func(t *TrackLocalStaticRTP) { 56 t.rid = rid 57 } 58 } 59 60 // Bind is called by the PeerConnection after negotiation is complete 61 // This asserts that the code requested is supported by the remote peer. 62 // If so it sets up all the state (SSRC and PayloadType) to have a call 63 func (s *TrackLocalStaticRTP) Bind(t TrackLocalContext) (RTPCodecParameters, error) { 64 s.mu.Lock() 65 defer s.mu.Unlock() 66 67 parameters := RTPCodecParameters{RTPCodecCapability: s.codec} 68 if codec, matchType := codecParametersFuzzySearch(parameters, t.CodecParameters()); matchType != codecMatchNone { 69 s.bindings = append(s.bindings, trackBinding{ 70 ssrc: t.SSRC(), 71 ssrcRTX: t.SSRCRetransmission(), 72 ssrcFEC: t.SSRCForwardErrorCorrection(), 73 payloadType: codec.PayloadType, 74 payloadTypeRTX: findRTXPayloadType(codec.PayloadType, t.CodecParameters()), 75 writeStream: t.WriteStream(), 76 id: t.ID(), 77 }) 78 79 return codec, nil 80 } 81 82 return RTPCodecParameters{}, ErrUnsupportedCodec 83 } 84 85 // Unbind implements the teardown logic when the track is no longer needed. This happens 86 // because a track has been stopped. 87 func (s *TrackLocalStaticRTP) Unbind(t TrackLocalContext) error { 88 s.mu.Lock() 89 defer s.mu.Unlock() 90 91 for i := range s.bindings { 92 if s.bindings[i].id == t.ID() { 93 s.bindings[i] = s.bindings[len(s.bindings)-1] 94 s.bindings = s.bindings[:len(s.bindings)-1] 95 return nil 96 } 97 } 98 99 return ErrUnbindFailed 100 } 101 102 // ID is the unique identifier for this Track. This should be unique for the 103 // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' 104 // and StreamID would be 'desktop' or 'webcam' 105 func (s *TrackLocalStaticRTP) ID() string { return s.id } 106 107 // StreamID is the group this track belongs too. This must be unique 108 func (s *TrackLocalStaticRTP) StreamID() string { return s.streamID } 109 110 // RID is the RTP stream identifier. 111 func (s *TrackLocalStaticRTP) RID() string { return s.rid } 112 113 // Kind controls if this TrackLocal is audio or video 114 func (s *TrackLocalStaticRTP) Kind() RTPCodecType { 115 switch { 116 case strings.HasPrefix(s.codec.MimeType, "audio/"): 117 return RTPCodecTypeAudio 118 case strings.HasPrefix(s.codec.MimeType, "video/"): 119 return RTPCodecTypeVideo 120 default: 121 return RTPCodecType(0) 122 } 123 } 124 125 // Codec gets the Codec of the track 126 func (s *TrackLocalStaticRTP) Codec() RTPCodecCapability { 127 return s.codec 128 } 129 130 // packetPool is a pool of packets used by WriteRTP and Write below 131 // nolint:gochecknoglobals 132 var rtpPacketPool = sync.Pool{ 133 New: func() interface{} { 134 return &rtp.Packet{} 135 }, 136 } 137 138 func resetPacketPoolAllocation(localPacket *rtp.Packet) { 139 *localPacket = rtp.Packet{} 140 rtpPacketPool.Put(localPacket) 141 } 142 143 func getPacketAllocationFromPool() *rtp.Packet { 144 ipacket := rtpPacketPool.Get() 145 return ipacket.(*rtp.Packet) //nolint:forcetypeassert 146 } 147 148 // WriteRTP writes a RTP Packet to the TrackLocalStaticRTP 149 // If one PeerConnection fails the packets will still be sent to 150 // all PeerConnections. The error message will contain the ID of the failed 151 // PeerConnections so you can remove them 152 func (s *TrackLocalStaticRTP) WriteRTP(p *rtp.Packet) error { 153 packet := getPacketAllocationFromPool() 154 155 defer resetPacketPoolAllocation(packet) 156 157 *packet = *p 158 159 return s.writeRTP(packet) 160 } 161 162 // writeRTP is like WriteRTP, except that it may modify the packet p 163 func (s *TrackLocalStaticRTP) writeRTP(p *rtp.Packet) error { 164 s.mu.RLock() 165 defer s.mu.RUnlock() 166 167 writeErrs := []error{} 168 169 for _, b := range s.bindings { 170 p.Header.SSRC = uint32(b.ssrc) 171 p.Header.PayloadType = uint8(b.payloadType) 172 if _, err := b.writeStream.WriteRTP(&p.Header, p.Payload); err != nil { 173 writeErrs = append(writeErrs, err) 174 } 175 } 176 177 return util.FlattenErrs(writeErrs) 178 } 179 180 // Write writes a RTP Packet as a buffer to the TrackLocalStaticRTP 181 // If one PeerConnection fails the packets will still be sent to 182 // all PeerConnections. The error message will contain the ID of the failed 183 // PeerConnections so you can remove them 184 func (s *TrackLocalStaticRTP) Write(b []byte) (n int, err error) { 185 packet := getPacketAllocationFromPool() 186 187 defer resetPacketPoolAllocation(packet) 188 189 if err = packet.Unmarshal(b); err != nil { 190 return 0, err 191 } 192 193 return len(b), s.writeRTP(packet) 194 } 195 196 // TrackLocalStaticSample is a TrackLocal that has a pre-set codec and accepts Samples. 197 // If you wish to send a RTP Packet use TrackLocalStaticRTP 198 type TrackLocalStaticSample struct { 199 packetizer rtp.Packetizer 200 sequencer rtp.Sequencer 201 rtpTrack *TrackLocalStaticRTP 202 clockRate float64 203 } 204 205 // NewTrackLocalStaticSample returns a TrackLocalStaticSample 206 func NewTrackLocalStaticSample(c RTPCodecCapability, id, streamID string, options ...func(*TrackLocalStaticRTP)) (*TrackLocalStaticSample, error) { 207 rtpTrack, err := NewTrackLocalStaticRTP(c, id, streamID, options...) 208 if err != nil { 209 return nil, err 210 } 211 212 return &TrackLocalStaticSample{ 213 rtpTrack: rtpTrack, 214 }, nil 215 } 216 217 // ID is the unique identifier for this Track. This should be unique for the 218 // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' 219 // and StreamID would be 'desktop' or 'webcam' 220 func (s *TrackLocalStaticSample) ID() string { return s.rtpTrack.ID() } 221 222 // StreamID is the group this track belongs too. This must be unique 223 func (s *TrackLocalStaticSample) StreamID() string { return s.rtpTrack.StreamID() } 224 225 // RID is the RTP stream identifier. 226 func (s *TrackLocalStaticSample) RID() string { return s.rtpTrack.RID() } 227 228 // Kind controls if this TrackLocal is audio or video 229 func (s *TrackLocalStaticSample) Kind() RTPCodecType { return s.rtpTrack.Kind() } 230 231 // Codec gets the Codec of the track 232 func (s *TrackLocalStaticSample) Codec() RTPCodecCapability { 233 return s.rtpTrack.Codec() 234 } 235 236 // Bind is called by the PeerConnection after negotiation is complete 237 // This asserts that the code requested is supported by the remote peer. 238 // If so it setups all the state (SSRC and PayloadType) to have a call 239 func (s *TrackLocalStaticSample) Bind(t TrackLocalContext) (RTPCodecParameters, error) { 240 codec, err := s.rtpTrack.Bind(t) 241 if err != nil { 242 return codec, err 243 } 244 245 s.rtpTrack.mu.Lock() 246 defer s.rtpTrack.mu.Unlock() 247 248 // We only need one packetizer 249 if s.packetizer != nil { 250 return codec, nil 251 } 252 253 payloader, err := payloaderForCodec(codec.RTPCodecCapability) 254 if err != nil { 255 return codec, err 256 } 257 258 s.sequencer = rtp.NewRandomSequencer() 259 s.packetizer = rtp.NewPacketizer( 260 rtpOutboundMTU, 261 0, // Value is handled when writing 262 0, // Value is handled when writing 263 payloader, 264 s.sequencer, 265 codec.ClockRate, 266 ) 267 s.clockRate = float64(codec.RTPCodecCapability.ClockRate) 268 return codec, nil 269 } 270 271 // Unbind implements the teardown logic when the track is no longer needed. This happens 272 // because a track has been stopped. 273 func (s *TrackLocalStaticSample) Unbind(t TrackLocalContext) error { 274 return s.rtpTrack.Unbind(t) 275 } 276 277 // WriteSample writes a Sample to the TrackLocalStaticSample 278 // If one PeerConnection fails the packets will still be sent to 279 // all PeerConnections. The error message will contain the ID of the failed 280 // PeerConnections so you can remove them 281 func (s *TrackLocalStaticSample) WriteSample(sample media.Sample) error { 282 s.rtpTrack.mu.RLock() 283 p := s.packetizer 284 clockRate := s.clockRate 285 s.rtpTrack.mu.RUnlock() 286 287 if p == nil { 288 return nil 289 } 290 291 // skip packets by the number of previously dropped packets 292 for i := uint16(0); i < sample.PrevDroppedPackets; i++ { 293 s.sequencer.NextSequenceNumber() 294 } 295 296 samples := uint32(sample.Duration.Seconds() * clockRate) 297 if sample.PrevDroppedPackets > 0 { 298 p.SkipSamples(samples * uint32(sample.PrevDroppedPackets)) 299 } 300 packets := p.Packetize(sample.Data, samples) 301 302 writeErrs := []error{} 303 for _, p := range packets { 304 if err := s.rtpTrack.WriteRTP(p); err != nil { 305 writeErrs = append(writeErrs, err) 306 } 307 } 308 309 return util.FlattenErrs(writeErrs) 310 } 311 312 // GeneratePadding writes padding-only samples to the TrackLocalStaticSample 313 // If one PeerConnection fails the packets will still be sent to 314 // all PeerConnections. The error message will contain the ID of the failed 315 // PeerConnections so you can remove them 316 func (s *TrackLocalStaticSample) GeneratePadding(samples uint32) error { 317 s.rtpTrack.mu.RLock() 318 p := s.packetizer 319 s.rtpTrack.mu.RUnlock() 320 321 if p == nil { 322 return nil 323 } 324 325 packets := p.GeneratePadding(samples) 326 327 writeErrs := []error{} 328 for _, p := range packets { 329 if err := s.rtpTrack.WriteRTP(p); err != nil { 330 writeErrs = append(writeErrs, err) 331 } 332 } 333 334 return util.FlattenErrs(writeErrs) 335 }