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  }