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  }