github.com/pion/webrtc/v3@v3.2.24/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/v3/internal/util" 15 "github.com/pion/webrtc/v3/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 SSRC 24 payloadType 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 setups 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 payloadType: codec.PayloadType, 72 writeStream: t.WriteStream(), 73 id: t.ID(), 74 }) 75 return codec, nil 76 } 77 78 return RTPCodecParameters{}, ErrUnsupportedCodec 79 } 80 81 // Unbind implements the teardown logic when the track is no longer needed. This happens 82 // because a track has been stopped. 83 func (s *TrackLocalStaticRTP) Unbind(t TrackLocalContext) error { 84 s.mu.Lock() 85 defer s.mu.Unlock() 86 87 for i := range s.bindings { 88 if s.bindings[i].id == t.ID() { 89 s.bindings[i] = s.bindings[len(s.bindings)-1] 90 s.bindings = s.bindings[:len(s.bindings)-1] 91 return nil 92 } 93 } 94 95 return ErrUnbindFailed 96 } 97 98 // ID is the unique identifier for this Track. This should be unique for the 99 // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' 100 // and StreamID would be 'desktop' or 'webcam' 101 func (s *TrackLocalStaticRTP) ID() string { return s.id } 102 103 // StreamID is the group this track belongs too. This must be unique 104 func (s *TrackLocalStaticRTP) StreamID() string { return s.streamID } 105 106 // RID is the RTP stream identifier. 107 func (s *TrackLocalStaticRTP) RID() string { return s.rid } 108 109 // Kind controls if this TrackLocal is audio or video 110 func (s *TrackLocalStaticRTP) Kind() RTPCodecType { 111 switch { 112 case strings.HasPrefix(s.codec.MimeType, "audio/"): 113 return RTPCodecTypeAudio 114 case strings.HasPrefix(s.codec.MimeType, "video/"): 115 return RTPCodecTypeVideo 116 default: 117 return RTPCodecType(0) 118 } 119 } 120 121 // Codec gets the Codec of the track 122 func (s *TrackLocalStaticRTP) Codec() RTPCodecCapability { 123 return s.codec 124 } 125 126 // packetPool is a pool of packets used by WriteRTP and Write below 127 // nolint:gochecknoglobals 128 var rtpPacketPool = sync.Pool{ 129 New: func() interface{} { 130 return &rtp.Packet{} 131 }, 132 } 133 134 func resetPacketPoolAllocation(localPacket *rtp.Packet) { 135 *localPacket = rtp.Packet{} 136 rtpPacketPool.Put(localPacket) 137 } 138 139 func getPacketAllocationFromPool() *rtp.Packet { 140 ipacket := rtpPacketPool.Get() 141 return ipacket.(*rtp.Packet) //nolint:forcetypeassert 142 } 143 144 // WriteRTP writes a RTP Packet to the TrackLocalStaticRTP 145 // If one PeerConnection fails the packets will still be sent to 146 // all PeerConnections. The error message will contain the ID of the failed 147 // PeerConnections so you can remove them 148 func (s *TrackLocalStaticRTP) WriteRTP(p *rtp.Packet) error { 149 packet := getPacketAllocationFromPool() 150 151 defer resetPacketPoolAllocation(packet) 152 153 *packet = *p 154 155 return s.writeRTP(packet) 156 } 157 158 // writeRTP is like WriteRTP, except that it may modify the packet p 159 func (s *TrackLocalStaticRTP) writeRTP(p *rtp.Packet) error { 160 s.mu.RLock() 161 defer s.mu.RUnlock() 162 163 writeErrs := []error{} 164 165 for _, b := range s.bindings { 166 p.Header.SSRC = uint32(b.ssrc) 167 p.Header.PayloadType = uint8(b.payloadType) 168 if _, err := b.writeStream.WriteRTP(&p.Header, p.Payload); err != nil { 169 writeErrs = append(writeErrs, err) 170 } 171 } 172 173 return util.FlattenErrs(writeErrs) 174 } 175 176 // Write writes a RTP Packet as a buffer to the TrackLocalStaticRTP 177 // If one PeerConnection fails the packets will still be sent to 178 // all PeerConnections. The error message will contain the ID of the failed 179 // PeerConnections so you can remove them 180 func (s *TrackLocalStaticRTP) Write(b []byte) (n int, err error) { 181 packet := getPacketAllocationFromPool() 182 183 defer resetPacketPoolAllocation(packet) 184 185 if err = packet.Unmarshal(b); err != nil { 186 return 0, err 187 } 188 189 return len(b), s.writeRTP(packet) 190 } 191 192 // TrackLocalStaticSample is a TrackLocal that has a pre-set codec and accepts Samples. 193 // If you wish to send a RTP Packet use TrackLocalStaticRTP 194 type TrackLocalStaticSample struct { 195 packetizer rtp.Packetizer 196 sequencer rtp.Sequencer 197 rtpTrack *TrackLocalStaticRTP 198 clockRate float64 199 } 200 201 // NewTrackLocalStaticSample returns a TrackLocalStaticSample 202 func NewTrackLocalStaticSample(c RTPCodecCapability, id, streamID string, options ...func(*TrackLocalStaticRTP)) (*TrackLocalStaticSample, error) { 203 rtpTrack, err := NewTrackLocalStaticRTP(c, id, streamID, options...) 204 if err != nil { 205 return nil, err 206 } 207 208 return &TrackLocalStaticSample{ 209 rtpTrack: rtpTrack, 210 }, nil 211 } 212 213 // ID is the unique identifier for this Track. This should be unique for the 214 // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' 215 // and StreamID would be 'desktop' or 'webcam' 216 func (s *TrackLocalStaticSample) ID() string { return s.rtpTrack.ID() } 217 218 // StreamID is the group this track belongs too. This must be unique 219 func (s *TrackLocalStaticSample) StreamID() string { return s.rtpTrack.StreamID() } 220 221 // RID is the RTP stream identifier. 222 func (s *TrackLocalStaticSample) RID() string { return s.rtpTrack.RID() } 223 224 // Kind controls if this TrackLocal is audio or video 225 func (s *TrackLocalStaticSample) Kind() RTPCodecType { return s.rtpTrack.Kind() } 226 227 // Codec gets the Codec of the track 228 func (s *TrackLocalStaticSample) Codec() RTPCodecCapability { 229 return s.rtpTrack.Codec() 230 } 231 232 // Bind is called by the PeerConnection after negotiation is complete 233 // This asserts that the code requested is supported by the remote peer. 234 // If so it setups all the state (SSRC and PayloadType) to have a call 235 func (s *TrackLocalStaticSample) Bind(t TrackLocalContext) (RTPCodecParameters, error) { 236 codec, err := s.rtpTrack.Bind(t) 237 if err != nil { 238 return codec, err 239 } 240 241 s.rtpTrack.mu.Lock() 242 defer s.rtpTrack.mu.Unlock() 243 244 // We only need one packetizer 245 if s.packetizer != nil { 246 return codec, nil 247 } 248 249 payloader, err := payloaderForCodec(codec.RTPCodecCapability) 250 if err != nil { 251 return codec, err 252 } 253 254 s.sequencer = rtp.NewRandomSequencer() 255 s.packetizer = rtp.NewPacketizer( 256 rtpOutboundMTU, 257 0, // Value is handled when writing 258 0, // Value is handled when writing 259 payloader, 260 s.sequencer, 261 codec.ClockRate, 262 ) 263 s.clockRate = float64(codec.RTPCodecCapability.ClockRate) 264 return codec, nil 265 } 266 267 // Unbind implements the teardown logic when the track is no longer needed. This happens 268 // because a track has been stopped. 269 func (s *TrackLocalStaticSample) Unbind(t TrackLocalContext) error { 270 return s.rtpTrack.Unbind(t) 271 } 272 273 // WriteSample writes a Sample to the TrackLocalStaticSample 274 // If one PeerConnection fails the packets will still be sent to 275 // all PeerConnections. The error message will contain the ID of the failed 276 // PeerConnections so you can remove them 277 func (s *TrackLocalStaticSample) WriteSample(sample media.Sample) error { 278 s.rtpTrack.mu.RLock() 279 p := s.packetizer 280 clockRate := s.clockRate 281 s.rtpTrack.mu.RUnlock() 282 283 if p == nil { 284 return nil 285 } 286 287 // skip packets by the number of previously dropped packets 288 for i := uint16(0); i < sample.PrevDroppedPackets; i++ { 289 s.sequencer.NextSequenceNumber() 290 } 291 292 samples := uint32(sample.Duration.Seconds() * clockRate) 293 if sample.PrevDroppedPackets > 0 { 294 p.SkipSamples(samples * uint32(sample.PrevDroppedPackets)) 295 } 296 packets := p.Packetize(sample.Data, samples) 297 298 writeErrs := []error{} 299 for _, p := range packets { 300 if err := s.rtpTrack.WriteRTP(p); err != nil { 301 writeErrs = append(writeErrs, err) 302 } 303 } 304 305 return util.FlattenErrs(writeErrs) 306 }