| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
- // SPDX-License-Identifier: MIT
- //go:build !js
- // +build !js
- package webrtc
- import (
- "strings"
- "sync"
- "github.com/pion/rtp"
- "github.com/pion/webrtc/v3/internal/util"
- "github.com/pion/webrtc/v3/pkg/media"
- )
- // trackBinding is a single bind for a Track
- // Bind can be called multiple times, this stores the
- // result for a single bind call so that it can be used when writing
- type trackBinding struct {
- id string
- ssrc SSRC
- payloadType PayloadType
- writeStream TrackLocalWriter
- }
- // TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets.
- // If you wish to send a media.Sample use TrackLocalStaticSample
- type TrackLocalStaticRTP struct {
- mu sync.RWMutex
- bindings []trackBinding
- codec RTPCodecCapability
- id, rid, streamID string
- }
- // NewTrackLocalStaticRTP returns a TrackLocalStaticRTP.
- func NewTrackLocalStaticRTP(c RTPCodecCapability, id, streamID string, options ...func(*TrackLocalStaticRTP)) (*TrackLocalStaticRTP, error) {
- t := &TrackLocalStaticRTP{
- codec: c,
- bindings: []trackBinding{},
- id: id,
- streamID: streamID,
- }
- for _, option := range options {
- option(t)
- }
- return t, nil
- }
- // WithRTPStreamID sets the RTP stream ID for this TrackLocalStaticRTP.
- func WithRTPStreamID(rid string) func(*TrackLocalStaticRTP) {
- return func(t *TrackLocalStaticRTP) {
- t.rid = rid
- }
- }
- // Bind is called by the PeerConnection after negotiation is complete
- // This asserts that the code requested is supported by the remote peer.
- // If so it setups all the state (SSRC and PayloadType) to have a call
- func (s *TrackLocalStaticRTP) Bind(t TrackLocalContext) (RTPCodecParameters, error) {
- s.mu.Lock()
- defer s.mu.Unlock()
- parameters := RTPCodecParameters{RTPCodecCapability: s.codec}
- if codec, matchType := codecParametersFuzzySearch(parameters, t.CodecParameters()); matchType != codecMatchNone {
- s.bindings = append(s.bindings, trackBinding{
- ssrc: t.SSRC(),
- payloadType: codec.PayloadType,
- writeStream: t.WriteStream(),
- id: t.ID(),
- })
- return codec, nil
- }
- return RTPCodecParameters{}, ErrUnsupportedCodec
- }
- // Unbind implements the teardown logic when the track is no longer needed. This happens
- // because a track has been stopped.
- func (s *TrackLocalStaticRTP) Unbind(t TrackLocalContext) error {
- s.mu.Lock()
- defer s.mu.Unlock()
- for i := range s.bindings {
- if s.bindings[i].id == t.ID() {
- s.bindings[i] = s.bindings[len(s.bindings)-1]
- s.bindings = s.bindings[:len(s.bindings)-1]
- return nil
- }
- }
- return ErrUnbindFailed
- }
- // ID is the unique identifier for this Track. This should be unique for the
- // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
- // and StreamID would be 'desktop' or 'webcam'
- func (s *TrackLocalStaticRTP) ID() string { return s.id }
- // StreamID is the group this track belongs too. This must be unique
- func (s *TrackLocalStaticRTP) StreamID() string { return s.streamID }
- // RID is the RTP stream identifier.
- func (s *TrackLocalStaticRTP) RID() string { return s.rid }
- // Kind controls if this TrackLocal is audio or video
- func (s *TrackLocalStaticRTP) Kind() RTPCodecType {
- switch {
- case strings.HasPrefix(s.codec.MimeType, "audio/"):
- return RTPCodecTypeAudio
- case strings.HasPrefix(s.codec.MimeType, "video/"):
- return RTPCodecTypeVideo
- default:
- return RTPCodecType(0)
- }
- }
- // Codec gets the Codec of the track
- func (s *TrackLocalStaticRTP) Codec() RTPCodecCapability {
- return s.codec
- }
- // packetPool is a pool of packets used by WriteRTP and Write below
- // nolint:gochecknoglobals
- var rtpPacketPool = sync.Pool{
- New: func() interface{} {
- return &rtp.Packet{}
- },
- }
- func resetPacketPoolAllocation(localPacket *rtp.Packet) {
- *localPacket = rtp.Packet{}
- rtpPacketPool.Put(localPacket)
- }
- func getPacketAllocationFromPool() *rtp.Packet {
- ipacket := rtpPacketPool.Get()
- return ipacket.(*rtp.Packet) //nolint:forcetypeassert
- }
- // WriteRTP writes a RTP Packet to the TrackLocalStaticRTP
- // If one PeerConnection fails the packets will still be sent to
- // all PeerConnections. The error message will contain the ID of the failed
- // PeerConnections so you can remove them
- func (s *TrackLocalStaticRTP) WriteRTP(p *rtp.Packet) error {
- packet := getPacketAllocationFromPool()
- defer resetPacketPoolAllocation(packet)
- *packet = *p
- return s.writeRTP(packet)
- }
- // writeRTP is like WriteRTP, except that it may modify the packet p
- func (s *TrackLocalStaticRTP) writeRTP(p *rtp.Packet) error {
- s.mu.RLock()
- defer s.mu.RUnlock()
- writeErrs := []error{}
- for _, b := range s.bindings {
- p.Header.SSRC = uint32(b.ssrc)
- p.Header.PayloadType = uint8(b.payloadType)
- if _, err := b.writeStream.WriteRTP(&p.Header, p.Payload); err != nil {
- writeErrs = append(writeErrs, err)
- }
- }
- return util.FlattenErrs(writeErrs)
- }
- // Write writes a RTP Packet as a buffer to the TrackLocalStaticRTP
- // If one PeerConnection fails the packets will still be sent to
- // all PeerConnections. The error message will contain the ID of the failed
- // PeerConnections so you can remove them
- func (s *TrackLocalStaticRTP) Write(b []byte) (n int, err error) {
- packet := getPacketAllocationFromPool()
- defer resetPacketPoolAllocation(packet)
- if err = packet.Unmarshal(b); err != nil {
- return 0, err
- }
- return len(b), s.writeRTP(packet)
- }
- // TrackLocalStaticSample is a TrackLocal that has a pre-set codec and accepts Samples.
- // If you wish to send a RTP Packet use TrackLocalStaticRTP
- type TrackLocalStaticSample struct {
- packetizer rtp.Packetizer
- sequencer rtp.Sequencer
- rtpTrack *TrackLocalStaticRTP
- clockRate float64
- }
- // NewTrackLocalStaticSample returns a TrackLocalStaticSample
- func NewTrackLocalStaticSample(c RTPCodecCapability, id, streamID string, options ...func(*TrackLocalStaticRTP)) (*TrackLocalStaticSample, error) {
- rtpTrack, err := NewTrackLocalStaticRTP(c, id, streamID, options...)
- if err != nil {
- return nil, err
- }
- return &TrackLocalStaticSample{
- rtpTrack: rtpTrack,
- }, nil
- }
- // ID is the unique identifier for this Track. This should be unique for the
- // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
- // and StreamID would be 'desktop' or 'webcam'
- func (s *TrackLocalStaticSample) ID() string { return s.rtpTrack.ID() }
- // StreamID is the group this track belongs too. This must be unique
- func (s *TrackLocalStaticSample) StreamID() string { return s.rtpTrack.StreamID() }
- // RID is the RTP stream identifier.
- func (s *TrackLocalStaticSample) RID() string { return s.rtpTrack.RID() }
- // Kind controls if this TrackLocal is audio or video
- func (s *TrackLocalStaticSample) Kind() RTPCodecType { return s.rtpTrack.Kind() }
- // Codec gets the Codec of the track
- func (s *TrackLocalStaticSample) Codec() RTPCodecCapability {
- return s.rtpTrack.Codec()
- }
- // Bind is called by the PeerConnection after negotiation is complete
- // This asserts that the code requested is supported by the remote peer.
- // If so it setups all the state (SSRC and PayloadType) to have a call
- func (s *TrackLocalStaticSample) Bind(t TrackLocalContext) (RTPCodecParameters, error) {
- codec, err := s.rtpTrack.Bind(t)
- if err != nil {
- return codec, err
- }
- s.rtpTrack.mu.Lock()
- defer s.rtpTrack.mu.Unlock()
- // We only need one packetizer
- if s.packetizer != nil {
- return codec, nil
- }
- payloader, err := payloaderForCodec(codec.RTPCodecCapability)
- if err != nil {
- return codec, err
- }
- s.sequencer = rtp.NewRandomSequencer()
- s.packetizer = rtp.NewPacketizer(
- rtpOutboundMTU,
- 0, // Value is handled when writing
- 0, // Value is handled when writing
- payloader,
- s.sequencer,
- codec.ClockRate,
- )
- s.clockRate = float64(codec.RTPCodecCapability.ClockRate)
- return codec, nil
- }
- // Unbind implements the teardown logic when the track is no longer needed. This happens
- // because a track has been stopped.
- func (s *TrackLocalStaticSample) Unbind(t TrackLocalContext) error {
- return s.rtpTrack.Unbind(t)
- }
- // WriteSample writes a Sample to the TrackLocalStaticSample
- // If one PeerConnection fails the packets will still be sent to
- // all PeerConnections. The error message will contain the ID of the failed
- // PeerConnections so you can remove them
- func (s *TrackLocalStaticSample) WriteSample(sample media.Sample) error {
- s.rtpTrack.mu.RLock()
- p := s.packetizer
- clockRate := s.clockRate
- s.rtpTrack.mu.RUnlock()
- if p == nil {
- return nil
- }
- // skip packets by the number of previously dropped packets
- for i := uint16(0); i < sample.PrevDroppedPackets; i++ {
- s.sequencer.NextSequenceNumber()
- }
- samples := uint32(sample.Duration.Seconds() * clockRate)
- if sample.PrevDroppedPackets > 0 {
- p.SkipSamples(samples * uint32(sample.PrevDroppedPackets))
- }
- packets := p.Packetize(sample.Data, samples)
- writeErrs := []error{}
- for _, p := range packets {
- if err := s.rtpTrack.WriteRTP(p); err != nil {
- writeErrs = append(writeErrs, err)
- }
- }
- return util.FlattenErrs(writeErrs)
- }
- // GeneratePadding writes padding-only samples to the TrackLocalStaticSample
- // If one PeerConnection fails the packets will still be sent to
- // all PeerConnections. The error message will contain the ID of the failed
- // PeerConnections so you can remove them
- func (s *TrackLocalStaticSample) GeneratePadding(samples uint32) error {
- s.rtpTrack.mu.RLock()
- p := s.packetizer
- s.rtpTrack.mu.RUnlock()
- if p == nil {
- return nil
- }
- packets := p.GeneratePadding(samples)
- writeErrs := []error{}
- for _, p := range packets {
- if err := s.rtpTrack.WriteRTP(p); err != nil {
- writeErrs = append(writeErrs, err)
- }
- }
- return util.FlattenErrs(writeErrs)
- }
|