package webrtc
import (
"strings"
"sync"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4/internal/util"
"github.com/pion/webrtc/v4/pkg/media"
)
type trackBinding struct {
id string
ssrc, ssrcRTX, ssrcFEC SSRC
payloadType, payloadTypeRTX PayloadType
writeStream TrackLocalWriter
}
type TrackLocalStaticRTP struct {
mu sync .RWMutex
bindings []trackBinding
codec RTPCodecCapability
payloader func (RTPCodecCapability ) (rtp .Payloader , error )
id, rid, streamID string
rtpTimestamp *uint32
}
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
}
func WithRTPStreamID (rid string ) func (*TrackLocalStaticRTP ) {
return func (t *TrackLocalStaticRTP ) {
t .rid = rid
}
}
func WithPayloader (h func (RTPCodecCapability ) (rtp .Payloader , error )) func (*TrackLocalStaticRTP ) {
return func (s *TrackLocalStaticRTP ) {
s .payloader = h
}
}
func WithRTPTimestamp (timestamp uint32 ) func (*TrackLocalStaticRTP ) {
return func (s *TrackLocalStaticRTP ) {
s .rtpTimestamp = ×tamp
}
}
func (s *TrackLocalStaticRTP ) Bind (trackContext TrackLocalContext ) (RTPCodecParameters , error ) {
s .mu .Lock ()
defer s .mu .Unlock ()
parameters := RTPCodecParameters {RTPCodecCapability : s .codec }
if codec , matchType := codecParametersFuzzySearch (
parameters ,
trackContext .CodecParameters (),
); matchType != codecMatchNone {
s .bindings = append (s .bindings , trackBinding {
ssrc : trackContext .SSRC (),
ssrcRTX : trackContext .SSRCRetransmission (),
ssrcFEC : trackContext .SSRCForwardErrorCorrection (),
payloadType : codec .PayloadType ,
payloadTypeRTX : findRTXPayloadType (codec .PayloadType , trackContext .CodecParameters ()),
writeStream : trackContext .WriteStream (),
id : trackContext .ID (),
})
return codec , nil
}
return RTPCodecParameters {}, ErrUnsupportedCodec
}
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
}
func (s *TrackLocalStaticRTP ) ID () string { return s .id }
func (s *TrackLocalStaticRTP ) StreamID () string { return s .streamID }
func (s *TrackLocalStaticRTP ) RID () string { return s .rid }
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 )
}
}
func (s *TrackLocalStaticRTP ) Codec () RTPCodecCapability {
return s .codec
}
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 )
}
func (s *TrackLocalStaticRTP ) WriteRTP (p *rtp .Packet ) error {
packet := getPacketAllocationFromPool ()
defer resetPacketPoolAllocation (packet )
*packet = *p
return s .writeRTP (packet )
}
func (s *TrackLocalStaticRTP ) writeRTP (packet *rtp .Packet ) error {
s .mu .RLock ()
defer s .mu .RUnlock ()
writeErrs := []error {}
for _ , b := range s .bindings {
packet .Header .SSRC = uint32 (b .ssrc )
packet .Header .PayloadType = uint8 (b .payloadType )
if packet .PaddingSize != 0 && packet .Header .PaddingSize == 0 {
packet .Header .PaddingSize = packet .PaddingSize
}
if _ , err := b .writeStream .WriteRTP (&packet .Header , packet .Payload ); err != nil {
writeErrs = append (writeErrs , err )
}
}
return util .FlattenErrs (writeErrs )
}
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 )
}
type TrackLocalStaticSample struct {
packetizer rtp .Packetizer
sequencer rtp .Sequencer
rtpTrack *TrackLocalStaticRTP
clockRate float64
}
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
}
func (s *TrackLocalStaticSample ) ID () string { return s .rtpTrack .ID () }
func (s *TrackLocalStaticSample ) StreamID () string { return s .rtpTrack .StreamID () }
func (s *TrackLocalStaticSample ) RID () string { return s .rtpTrack .RID () }
func (s *TrackLocalStaticSample ) Kind () RTPCodecType { return s .rtpTrack .Kind () }
func (s *TrackLocalStaticSample ) Codec () RTPCodecCapability {
return s .rtpTrack .Codec ()
}
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 ()
if s .packetizer != nil {
return codec , nil
}
payloadHandler := s .rtpTrack .payloader
if payloadHandler == nil {
payloadHandler = payloaderForCodec
}
payloader , err := payloadHandler (codec .RTPCodecCapability )
if err != nil {
return codec , err
}
s .sequencer = rtp .NewRandomSequencer ()
options := []rtp .PacketizerOption {}
if s .rtpTrack .rtpTimestamp != nil {
options = append (options , rtp .WithTimestamp (*s .rtpTrack .rtpTimestamp ))
}
s .packetizer = rtp .NewPacketizerWithOptions (
outboundMTU ,
payloader ,
s .sequencer ,
codec .ClockRate ,
options ...,
)
s .clockRate = float64 (codec .RTPCodecCapability .ClockRate )
return codec , nil
}
func (s *TrackLocalStaticSample ) Unbind (t TrackLocalContext ) error {
return s .rtpTrack .Unbind (t )
}
func (s *TrackLocalStaticSample ) WriteSample (sample media .Sample ) error {
s .rtpTrack .mu .RLock ()
packetizer := s .packetizer
clockRate := s .clockRate
s .rtpTrack .mu .RUnlock ()
if packetizer == nil {
return nil
}
for i := uint16 (0 ); i < sample .PrevDroppedPackets ; i ++ {
s .sequencer .NextSequenceNumber ()
}
samples := uint32 (sample .Duration .Seconds () * clockRate )
if sample .PrevDroppedPackets > 0 {
packetizer .SkipSamples (samples * uint32 (sample .PrevDroppedPackets ))
}
packets := packetizer .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 )
}
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 )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .