diff --git a/api.go b/api.go index db3846c04f1..f440f0bb358 100644 --- a/api.go +++ b/api.go @@ -4,6 +4,7 @@ package webrtc import ( "github.com/pion/logging" + "github.com/pion/webrtc/v3/pkg/interceptor" ) // API bundles the global functions of the WebRTC and ORTC API. @@ -13,7 +14,7 @@ import ( type API struct { settingEngine *SettingEngine mediaEngine *MediaEngine - interceptor Interceptor + interceptor interceptor.Interceptor } // NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects @@ -37,7 +38,7 @@ func NewAPI(options ...func(*API)) *API { } if a.interceptor == nil { - a.interceptor = &NoOpInterceptor{} + a.interceptor = &interceptor.NoOp{} } return a diff --git a/deleteme.go b/deleteme.go new file mode 100644 index 00000000000..d38775551cd --- /dev/null +++ b/deleteme.go @@ -0,0 +1,74 @@ +package webrtc + +import ( + "github.com/pion/webrtc/v3/pkg/interceptor/movetopionrtp" +) + +func convertRTPParameters(in RTPParameters) movetopionrtp.RTPParameters { + return movetopionrtp.RTPParameters{ + HeaderExtensions: convertHeaderExtensions(in.HeaderExtensions), + Codecs: convertRTPCodecParameters(in.Codecs), + } +} + +func convertHeaderExtensions(in []RTPHeaderExtensionParameter) []movetopionrtp.RTPHeaderExtensionParameter { + result := make([]movetopionrtp.RTPHeaderExtensionParameter, 0, len(in)) + for _, v := range in { + result = append(result, convertHeaderExtension(v)) + } + + return result +} + +func convertHeaderExtension(in RTPHeaderExtensionParameter) movetopionrtp.RTPHeaderExtensionParameter { + return movetopionrtp.RTPHeaderExtensionParameter{ + URI: in.URI, + ID: in.ID, + } +} + +func convertRTPCodecParameters(in []RTPCodecParameters) []movetopionrtp.RTPCodecParameters { + result := make([]movetopionrtp.RTPCodecParameters, 0, len(in)) + for _, v := range in { + result = append(result, convertRTPCodecParameter(v)) + } + + return result +} + +func convertRTPCodecParameter(in RTPCodecParameters) movetopionrtp.RTPCodecParameters { + return movetopionrtp.RTPCodecParameters{ + RTPCodecCapability: movetopionrtp.RTPCodecCapability{ + MimeType: in.MimeType, + ClockRate: in.ClockRate, + Channels: in.Channels, + SDPFmtpLine: in.SDPFmtpLine, + RTCPFeedback: convertRTCPFeedbacks(in.RTCPFeedback), + }, + PayloadType: convertPayloadType(in.PayloadType), + } +} + +func convertRTCPFeedbacks(in []RTCPFeedback) []movetopionrtp.RTCPFeedback { + result := make([]movetopionrtp.RTCPFeedback, 0, len(in)) + for _, v := range in { + result = append(result, convertRTCPFeedback(v)) + } + + return result +} + +func convertRTCPFeedback(in RTCPFeedback) movetopionrtp.RTCPFeedback { + return movetopionrtp.RTCPFeedback{ + Type: in.Type, + Parameter: in.Parameter, + } +} + +func convertPayloadType(in PayloadType) movetopionrtp.PayloadType { + return movetopionrtp.PayloadType(in) +} + +func convertSSRC(in SSRC) movetopionrtp.SSRC { + return movetopionrtp.SSRC(in) +} diff --git a/examples/save-to-disk/main.go b/examples/save-to-disk/main.go index 9ed87fa225b..f2be3df9193 100644 --- a/examples/save-to-disk/main.go +++ b/examples/save-to-disk/main.go @@ -10,7 +10,6 @@ import ( "github.com/pion/rtcp" "github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3/examples/internal/signal" - "github.com/pion/webrtc/v3/pkg/interceptor" "github.com/pion/webrtc/v3/pkg/media" "github.com/pion/webrtc/v3/pkg/media/ivfwriter" "github.com/pion/webrtc/v3/pkg/media/oggwriter" @@ -56,7 +55,7 @@ func main() { } ir := &webrtc.InterceptorRegistry{} - if err := interceptor.RegisterDefaults(&m, ir); err != nil { + if err := webrtc.RegisterDefaultInterceptors(&m, ir); err != nil { panic(err) } diff --git a/interceptor.go b/interceptor.go deleted file mode 100644 index 4f806324f01..00000000000 --- a/interceptor.go +++ /dev/null @@ -1,90 +0,0 @@ -// +build !js - -package webrtc - -import ( - "io" - - "github.com/pion/rtcp" - "github.com/pion/rtp" -) - -// WriteRTP is used by Interceptor.BindLocalTrack. -type WriteRTP func(p *rtp.Packet, attributes map[interface{}]interface{}) (int, error) - -// ReadRTP is used by Interceptor.BindRemoteTrack. -type ReadRTP func() (*rtp.Packet, map[interface{}]interface{}, error) - -// WriteRTCP is used by Interceptor.BindWriteRTCP. -type WriteRTCP func(pkts []rtcp.Packet, attributes map[interface{}]interface{}) (int, error) - -// ReadRTCP is used by Interceptor.BindReadRTCP. -type ReadRTCP func() ([]rtcp.Packet, map[interface{}]interface{}, error) - -// Interceptor can be used to add functionality to you PeerConnections by modifying any incoming/outgoing rtp/rtcp -// packets, or sending your own packets as needed. -type Interceptor interface { - - // BindReadRTCP lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might - // change in the future. The returned method will be called once per packet batch. - BindReadRTCP(read ReadRTCP) ReadRTCP - - // BindWriteRTCP lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method - // will be called once per packet batch. - BindWriteRTCP(write WriteRTCP) WriteRTCP - - // BindLocalTrack lets you modify any outgoing RTP packets. It is called once for per LocalTrack. The returned method - // will be called once per rtp packet. - BindLocalTrack(ctx *TrackLocalContext, write WriteRTP) WriteRTP - - // UnbindLocalTrack is called when the Track is removed. It can be used to clean up any data related to that track. - UnbindLocalTrack(ctx *TrackLocalContext) - - // BindRemoteTrack lets you modify any incoming RTP packets. It is called once for per RemoteTrack. The returned method - // will be called once per rtp packet. - BindRemoteTrack(ctx *TrackRemoteContext, read ReadRTP) ReadRTP - - // UnbindRemoteTrack is called when the Track is removed. It can be used to clean up any data related to that track. - UnbindRemoteTrack(ctx *TrackRemoteContext) - - io.Closer -} - -// NoOpInterceptor is an Interceptor that does not modify any packets. It can embedded in other interceptors, so it's -// possible to implement only a subset of the methods. -type NoOpInterceptor struct{} - -// BindReadRTCP lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might -// change in the future. The returned method will be called once per packet batch. -func (i *NoOpInterceptor) BindReadRTCP(read ReadRTCP) ReadRTCP { - return read -} - -// BindWriteRTCP lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method -// will be called once per packet batch. -func (i *NoOpInterceptor) BindWriteRTCP(write WriteRTCP) WriteRTCP { - return write -} - -// BindLocalTrack lets you modify any outgoing RTP packets. It is called once for per LocalTrack. The returned method -// will be called once per rtp packet. -func (i *NoOpInterceptor) BindLocalTrack(_ *TrackLocalContext, write WriteRTP) WriteRTP { - return write -} - -// UnbindLocalTrack is called when the Track is removed. It can be used to clean up any data related to that track. -func (i *NoOpInterceptor) UnbindLocalTrack(_ *TrackLocalContext) {} - -// BindRemoteTrack lets you modify any incoming RTP packets. It is called once for per RemoteTrack. The returned method -// will be called once per rtp packet. -func (i *NoOpInterceptor) BindRemoteTrack(_ *TrackRemoteContext, read ReadRTP) ReadRTP { - return read -} - -// UnbindRemoteTrack is called when the Track is removed. It can be used to clean up any data related to that track. -func (i *NoOpInterceptor) UnbindRemoteTrack(_ *TrackRemoteContext) {} - -// Close closes the Interceptor, cleaning up any data if necessary. -func (i *NoOpInterceptor) Close() error { - return nil -} diff --git a/interceptor_registry.go b/interceptor_registry.go index b500b9a8d4d..221634e6cd2 100644 --- a/interceptor_registry.go +++ b/interceptor_registry.go @@ -2,20 +2,43 @@ package webrtc +import ( + "github.com/pion/webrtc/v3/pkg/interceptor" +) + // InterceptorRegistry is a collector for interceptors. type InterceptorRegistry struct { - interceptors []Interceptor + interceptors []interceptor.Interceptor } // Add adds a new Interceptor to the registry. -func (i *InterceptorRegistry) Add(interceptor Interceptor) { - i.interceptors = append(i.interceptors, interceptor) +func (i *InterceptorRegistry) Add(icpr interceptor.Interceptor) { + i.interceptors = append(i.interceptors, icpr) } -func (i *InterceptorRegistry) build() Interceptor { +func (i *InterceptorRegistry) build() interceptor.Interceptor { if len(i.interceptors) == 0 { - return &NoOpInterceptor{} + return &interceptor.NoOp{} } - return &chainInterceptor{interceptors: i.interceptors} + return interceptor.NewChain(i.interceptors) +} + +// RegisterDefaults will register some useful interceptors. If you want to customize which interceptors are loaded, +// you should copy the code from this method and remove unwanted interceptors. +func RegisterDefaultInterceptors(mediaEngine *MediaEngine, interceptorRegistry *InterceptorRegistry) error { + err := ConfigureNack(mediaEngine, interceptorRegistry) + if err != nil { + return err + } + + return nil +} + +// ConfigureNack will setup everything necessary for handling generating/responding to nack messages. +func ConfigureNack(mediaEngine *MediaEngine, interceptorRegistry *InterceptorRegistry) error { + mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack"}, RTPCodecTypeVideo) + mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack", Parameter: "pli"}, RTPCodecTypeVideo) + interceptorRegistry.Add(&interceptor.NACK{}) + return nil } diff --git a/interceptor_test.go b/interceptor_test.go index 85d74de6bf5..172937cc16f 100644 --- a/interceptor_test.go +++ b/interceptor_test.go @@ -11,6 +11,7 @@ import ( "github.com/pion/rtcp" "github.com/pion/rtp" "github.com/pion/transport/test" + "github.com/pion/webrtc/v3/pkg/interceptor" "github.com/pion/webrtc/v3/pkg/media" "github.com/stretchr/testify/assert" ) @@ -18,25 +19,25 @@ import ( type testInterceptor struct { t *testing.T extensionID uint8 - writeRTCP atomic.Value + rtcpWriter atomic.Value lastRTCP atomic.Value - NoOpInterceptor + interceptor.NoOp } -func (t *testInterceptor) BindLocalTrack(_ *TrackLocalContext, write WriteRTP) WriteRTP { - return func(p *rtp.Packet, attributes map[interface{}]interface{}) (int, error) { +func (t *testInterceptor) BindLocalTrack(_ *interceptor.TrackInfo, writer interceptor.RTPWriter) interceptor.RTPWriter { + return interceptor.RTPWriterFunc(func(p *rtp.Packet, attributes interceptor.Attributes) (int, error) { // set extension on outgoing packet p.Header.Extension = true p.Header.ExtensionProfile = 0xBEDE assert.NoError(t.t, p.Header.SetExtension(t.extensionID, []byte("write"))) - return write(p, attributes) - } + return writer.Write(p, attributes) + }) } -func (t *testInterceptor) BindRemoteTrack(ctx *TrackRemoteContext, read ReadRTP) ReadRTP { - return func() (*rtp.Packet, map[interface{}]interface{}, error) { - p, attributes, err := read() +func (t *testInterceptor) BindRemoteTrack(info *interceptor.TrackInfo, reader interceptor.RTPReader) interceptor.RTPReader { + return interceptor.RTPReaderFunc(func() (*rtp.Packet, interceptor.Attributes, error) { + p, attributes, err := reader.Read() if err != nil { return nil, nil, err } @@ -46,18 +47,18 @@ func (t *testInterceptor) BindRemoteTrack(ctx *TrackRemoteContext, read ReadRTP) assert.NoError(t.t, p.Header.SetExtension(t.extensionID, []byte("read"))) // write back a pli - writeRTCP := t.writeRTCP.Load().(WriteRTCP) - pli := &rtcp.PictureLossIndication{SenderSSRC: uint32(ctx.SSRC()), MediaSSRC: uint32(ctx.SSRC())} - _, err = writeRTCP([]rtcp.Packet{pli}, make(map[interface{}]interface{})) + rtcpWriter := t.rtcpWriter.Load().(interceptor.RTCPWriter) + pli := &rtcp.PictureLossIndication{SenderSSRC: uint32(info.SSRC), MediaSSRC: uint32(info.SSRC)} + _, err = rtcpWriter.Write([]rtcp.Packet{pli}, make(interceptor.Attributes)) assert.NoError(t.t, err) return p, attributes, nil - } + }) } -func (t *testInterceptor) BindReadRTCP(read ReadRTCP) ReadRTCP { - return func() ([]rtcp.Packet, map[interface{}]interface{}, error) { - pkts, attributes, err := read() +func (t *testInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader { + return interceptor.RTCPReaderFunc(func() ([]rtcp.Packet, interceptor.Attributes, error) { + pkts, attributes, err := reader.Read() if err != nil { return nil, nil, err } @@ -65,7 +66,7 @@ func (t *testInterceptor) BindReadRTCP(read ReadRTCP) ReadRTCP { t.lastRTCP.Store(pkts[0]) return pkts, attributes, nil - } + }) } func (t *testInterceptor) lastReadRTCP() rtcp.Packet { @@ -73,9 +74,9 @@ func (t *testInterceptor) lastReadRTCP() rtcp.Packet { return p } -func (t *testInterceptor) BindWriteRTCP(write WriteRTCP) WriteRTCP { - t.writeRTCP.Store(write) - return write +func (t *testInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter { + t.rtcpWriter.Store(writer) + return writer } func TestPeerConnection_Interceptor(t *testing.T) { @@ -85,7 +86,7 @@ func TestPeerConnection_Interceptor(t *testing.T) { report := test.CheckRoutines(t) defer report() - createPC := func(interceptor Interceptor) *PeerConnection { + createPC := func(interceptor interceptor.Interceptor) *PeerConnection { m := &MediaEngine{} err := m.RegisterDefaultCodecs() if err != nil { diff --git a/interceptor_track_local.go b/interceptor_track_local.go index 1833798ffb4..cbe0c78f8ec 100644 --- a/interceptor_track_local.go +++ b/interceptor_track_local.go @@ -6,23 +6,24 @@ import ( "sync/atomic" "github.com/pion/rtp" + "github.com/pion/webrtc/v3/pkg/interceptor" ) type interceptorTrackLocalWriter struct { TrackLocalWriter - writeRTP atomic.Value + rtpWriter atomic.Value } -func (i *interceptorTrackLocalWriter) setWriteRTP(writeRTP WriteRTP) { - i.writeRTP.Store(writeRTP) +func (i *interceptorTrackLocalWriter) setRTPWriter(writer interceptor.RTPWriter) { + i.rtpWriter.Store(writer) } func (i *interceptorTrackLocalWriter) WriteRTP(header *rtp.Header, payload []byte) (int, error) { - writeRTP := i.writeRTP.Load().(WriteRTP) + writer := i.rtpWriter.Load().(interceptor.RTPWriter) - if writeRTP == nil { + if writer == nil { return 0, nil } - return writeRTP(&rtp.Packet{Header: *header, Payload: payload}, make(map[interface{}]interface{})) + return writer.Write(&rtp.Packet{Header: *header, Payload: payload}, make(interceptor.Attributes)) } diff --git a/peerconnection.go b/peerconnection.go index a7a935e30ab..ac1f3d24679 100644 --- a/peerconnection.go +++ b/peerconnection.go @@ -19,6 +19,7 @@ import ( "github.com/pion/rtcp" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3/internal/util" + "github.com/pion/webrtc/v3/pkg/interceptor" "github.com/pion/webrtc/v3/pkg/rtcerr" ) @@ -77,7 +78,7 @@ type PeerConnection struct { api *API log logging.LeveledLogger - interceptorWriteRTCP func(pkts []rtcp.Packet, attributes map[interface{}]interface{}) (int, error) + interceptorRTCPWriter interceptor.RTCPWriter } // NewPeerConnection creates a peerconnection with the default @@ -121,7 +122,7 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection, log: api.settingEngine.LoggerFactory.NewLogger("pc"), } - pc.interceptorWriteRTCP = api.interceptor.BindWriteRTCP(pc.writeRTCP) + pc.interceptorRTCPWriter = api.interceptor.BindRTCPWriter(interceptor.RTCPWriterFunc(pc.writeRTCP)) var err error if err = pc.initConfiguration(configuration); err != nil { @@ -1732,11 +1733,11 @@ func (pc *PeerConnection) SetIdentityProvider(provider string) error { // WriteRTCP sends a user provided RTCP packet to the connected peer. If no peer is connected the // packet is discarded. It also runs any configured interceptors. func (pc *PeerConnection) WriteRTCP(pkts []rtcp.Packet) error { - _, err := pc.interceptorWriteRTCP(pkts, make(map[interface{}]interface{})) + _, err := pc.interceptorRTCPWriter.Write(pkts, make(interceptor.Attributes)) return err } -func (pc *PeerConnection) writeRTCP(pkts []rtcp.Packet, _ map[interface{}]interface{}) (int, error) { +func (pc *PeerConnection) writeRTCP(pkts []rtcp.Packet, _ interceptor.Attributes) (int, error) { raw, err := rtcp.Marshal(pkts) if err != nil { return 0, err diff --git a/interceptor_chain.go b/pkg/interceptor/chaing.go similarity index 54% rename from interceptor_chain.go rename to pkg/interceptor/chaing.go index 1e9eb580ace..4e46348bd54 100644 --- a/interceptor_chain.go +++ b/pkg/interceptor/chaing.go @@ -1,47 +1,53 @@ // +build !js -package webrtc +package interceptor import ( "github.com/pion/webrtc/v3/internal/util" ) -type chainInterceptor struct { +// Chain is an interceptor that runs all child interceptors in order. +type Chain struct { interceptors []Interceptor } -// BindReadRTCP lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might +// NewChain returns a new Chain interceptor. +func NewChain(interceptors []Interceptor) *Chain { + return &Chain{interceptors: interceptors} +} + +// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might // change in the future. The returned method will be called once per packet batch. -func (i *chainInterceptor) BindReadRTCP(read ReadRTCP) ReadRTCP { +func (i *Chain) BindRTCPReader(reader RTCPReader) RTCPReader { for _, interceptor := range i.interceptors { - read = interceptor.BindReadRTCP(read) + reader = interceptor.BindRTCPReader(reader) } - return read + return reader } -// BindWriteRTCP lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method +// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method // will be called once per packet batch. -func (i *chainInterceptor) BindWriteRTCP(write WriteRTCP) WriteRTCP { +func (i *Chain) BindRTCPWriter(writer RTCPWriter) RTCPWriter { for _, interceptor := range i.interceptors { - write = interceptor.BindWriteRTCP(write) + writer = interceptor.BindRTCPWriter(writer) } - return write + return writer } // BindLocalTrack lets you modify any outgoing RTP packets. It is called once for per LocalTrack. The returned method // will be called once per rtp packet. -func (i *chainInterceptor) BindLocalTrack(ctx *TrackLocalContext, write WriteRTP) WriteRTP { +func (i *Chain) BindLocalTrack(ctx *TrackInfo, writer RTPWriter) RTPWriter { for _, interceptor := range i.interceptors { - write = interceptor.BindLocalTrack(ctx, write) + writer = interceptor.BindLocalTrack(ctx, writer) } - return write + return writer } // UnbindLocalTrack is called when the Track is removed. It can be used to clean up any data related to that track. -func (i *chainInterceptor) UnbindLocalTrack(ctx *TrackLocalContext) { +func (i *Chain) UnbindLocalTrack(ctx *TrackInfo) { for _, interceptor := range i.interceptors { interceptor.UnbindLocalTrack(ctx) } @@ -49,23 +55,23 @@ func (i *chainInterceptor) UnbindLocalTrack(ctx *TrackLocalContext) { // BindRemoteTrack lets you modify any incoming RTP packets. It is called once for per RemoteTrack. The returned method // will be called once per rtp packet. -func (i *chainInterceptor) BindRemoteTrack(ctx *TrackRemoteContext, read ReadRTP) ReadRTP { +func (i *Chain) BindRemoteTrack(ctx *TrackInfo, reader RTPReader) RTPReader { for _, interceptor := range i.interceptors { - read = interceptor.BindRemoteTrack(ctx, read) + reader = interceptor.BindRemoteTrack(ctx, reader) } - return read + return reader } // UnbindRemoteTrack is called when the Track is removed. It can be used to clean up any data related to that track. -func (i *chainInterceptor) UnbindRemoteTrack(ctx *TrackRemoteContext) { +func (i *Chain) UnbindRemoteTrack(ctx *TrackInfo) { for _, interceptor := range i.interceptors { interceptor.UnbindRemoteTrack(ctx) } } // Close closes the Interceptor, cleaning up any data if necessary. -func (i *chainInterceptor) Close() error { +func (i *Chain) Close() error { var errs []error for _, interceptor := range i.interceptors { if err := interceptor.Close(); err != nil { diff --git a/pkg/interceptor/interceptor.go b/pkg/interceptor/interceptor.go index 49ac05d7cc1..183d92523b4 100644 --- a/pkg/interceptor/interceptor.go +++ b/pkg/interceptor/interceptor.go @@ -1,19 +1,118 @@ // +build !js -// Package interceptor contains useful default interceptors that should be safe to use in most cases. +// Package interceptor contains the Interceptor interface, with some useful interceptors that should be safe to use +// in most cases. package interceptor import ( - "github.com/pion/webrtc/v3" + "io" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + "github.com/pion/webrtc/v3/pkg/interceptor/movetopionrtp" ) -// RegisterDefaults will register some useful interceptors. If you want to customize which interceptors are loaded, -// you should copy the code from this method and remove unwanted interceptors. -func RegisterDefaults(mediaEngine *webrtc.MediaEngine, interceptorRegistry *webrtc.InterceptorRegistry) error { - err := ConfigureNack(mediaEngine, interceptorRegistry) - if err != nil { - return err - } +// Interceptor can be used to add functionality to you PeerConnections by modifying any incoming/outgoing rtp/rtcp +// packets, or sending your own packets as needed. +type Interceptor interface { + + // BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might + // change in the future. The returned method will be called once per packet batch. + BindRTCPReader(reader RTCPReader) RTCPReader + + // BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method + // will be called once per packet batch. + BindRTCPWriter(writer RTCPWriter) RTCPWriter + + // BindLocalTrack lets you modify any outgoing RTP packets. It is called once for per LocalTrack. The returned method + // will be called once per rtp packet. + BindLocalTrack(info *TrackInfo, writer RTPWriter) RTPWriter + + // UnbindLocalTrack is called when the Track is removed. It can be used to clean up any data related to that track. + UnbindLocalTrack(info *TrackInfo) + + // BindRemoteTrack lets you modify any incoming RTP packets. It is called once for per RemoteTrack. The returned method + // will be called once per rtp packet. + BindRemoteTrack(info *TrackInfo, reader RTPReader) RTPReader + + // UnbindRemoteTrack is called when the Track is removed. It can be used to clean up any data related to that track. + UnbindRemoteTrack(info *TrackInfo) + + io.Closer +} + +// TrackInfo is the Context passed when a TrackLocal has been Binded/Unbinded from a PeerConnection, and used +// in Interceptors. +type TrackInfo struct { + ID string + Params movetopionrtp.RTPParameters + SSRC movetopionrtp.SSRC +} + +// RTPWriter is used by Interceptor.BindLocalTrack. +type RTPWriter interface { + // Write a rtp packet + Write(p *rtp.Packet, attributes Attributes) (int, error) +} + +// RTPReader is used by Interceptor.BindRemoteTrack. +type RTPReader interface { + // Read a rtp packet + Read() (*rtp.Packet, Attributes, error) +} + +// RTCPWriter is used by Interceptor.BindRTCPWriter. +type RTCPWriter interface { + // Write a batch of rtcp packets + Write(pkts []rtcp.Packet, attributes Attributes) (int, error) +} + +// RTCPReader is used by Interceptor.BindRTCPReader. +type RTCPReader interface { + // Read a batch of rtcp packets + Read() ([]rtcp.Packet, Attributes, error) +} + +type Attributes map[interface{}]interface{} + +// RTPWriterFunc is an adapter for RTPWrite interface +type RTPWriterFunc func(p *rtp.Packet, attributes Attributes) (int, error) + +// RTPReaderFunc is an adapter for RTPReader interface +type RTPReaderFunc func() (*rtp.Packet, Attributes, error) + +// RTCPWriterFunc is an adapter for RTCPWriter interface +type RTCPWriterFunc func(pkts []rtcp.Packet, attributes Attributes) (int, error) + +// RTCPReaderFunc is an adapter for RTCPReader interface +type RTCPReaderFunc func() ([]rtcp.Packet, Attributes, error) + +// Write a rtp packet +func (f RTPWriterFunc) Write(p *rtp.Packet, attributes Attributes) (int, error) { + return f(p, attributes) +} + +// Read a rtp packet +func (f RTPReaderFunc) Read() (*rtp.Packet, Attributes, error) { + return f() +} + +// Write a batch of rtcp packets +func (f RTCPWriterFunc) Write(pkts []rtcp.Packet, attributes Attributes) (int, error) { + return f(pkts, attributes) +} + +// Read a batch of rtcp packets +func (f RTCPReaderFunc) Read() ([]rtcp.Packet, Attributes, error) { + return f() +} + +// Get returns the attribute associated with key. +func (a Attributes) Get(key interface{}) interface{} { + return a[key] +} - return nil +// Set sets the attribute associated with key to the given value. +func (a Attributes) Set(key interface{}, val interface{}) { + a[key] = val } diff --git a/pkg/interceptor/movetopionrtp/other.go b/pkg/interceptor/movetopionrtp/other.go new file mode 100644 index 00000000000..9536fff3fb0 --- /dev/null +++ b/pkg/interceptor/movetopionrtp/other.go @@ -0,0 +1,16 @@ +package movetopionrtp + +// SSRC represents a synchronization source +// A synchronization source is a randomly chosen +// value meant to be globally unique within a particular +// RTP session. Used to identify a single stream of media. +// +// https://tools.ietf.org/html/rfc3550#section-3 +type SSRC uint32 + +// PayloadType identifies the format of the RTP payload and determines +// its interpretation by the application. Each codec in a RTP Session +// will have a different PayloadType +// +// https://tools.ietf.org/html/rfc3550#section-3 +type PayloadType uint8 diff --git a/pkg/interceptor/movetopionrtp/rtcpfeedback.go b/pkg/interceptor/movetopionrtp/rtcpfeedback.go new file mode 100644 index 00000000000..9fdcb6369f6 --- /dev/null +++ b/pkg/interceptor/movetopionrtp/rtcpfeedback.go @@ -0,0 +1,31 @@ +package movetopionrtp + +const ( + // TypeRTCPFBTransportCC .. + TypeRTCPFBTransportCC = "transport-cc" + + // TypeRTCPFBGoogREMB .. + TypeRTCPFBGoogREMB = "goog-remb" + + // TypeRTCPFBACK .. + TypeRTCPFBACK = "ack" + + // TypeRTCPFBCCM .. + TypeRTCPFBCCM = "ccm" + + // TypeRTCPFBNACK .. + TypeRTCPFBNACK = "nack" +) + +// RTCPFeedback signals the connection to use additional RTCP packet types. +// https://draft.ortc.org/#dom-rtcrtcpfeedback +type RTCPFeedback struct { + // Type is the type of feedback. + // see: https://draft.ortc.org/#dom-rtcrtcpfeedback + // valid: ack, ccm, nack, goog-remb, transport-cc + Type string + + // The parameter value depends on the type. + // For example, type="nack" parameter="pli" will send Picture Loss Indicator packets. + Parameter string +} diff --git a/pkg/interceptor/movetopionrtp/rtpcodec.go b/pkg/interceptor/movetopionrtp/rtpcodec.go new file mode 100644 index 00000000000..7d72592252d --- /dev/null +++ b/pkg/interceptor/movetopionrtp/rtpcodec.go @@ -0,0 +1,98 @@ +package movetopionrtp + +import ( + "errors" + "strings" +) + +// RTPCodecType determines the type of a codec +type RTPCodecType int + +// ErrUnknownType indicates an error with Unknown info. +var ErrUnknownType = errors.New("unknown") + +const ( + + // RTPCodecTypeAudio indicates this is an audio codec + RTPCodecTypeAudio RTPCodecType = iota + 1 + + // RTPCodecTypeVideo indicates this is a video codec + RTPCodecTypeVideo +) + +func (t RTPCodecType) String() string { + switch t { + case RTPCodecTypeAudio: + return "audio" + case RTPCodecTypeVideo: + return "video" //nolint: goconst + default: + return ErrUnknownType.Error() + } +} + +// NewRTPCodecType creates a RTPCodecType from a string +func NewRTPCodecType(r string) RTPCodecType { + switch { + case strings.EqualFold(r, RTPCodecTypeAudio.String()): + return RTPCodecTypeAudio + case strings.EqualFold(r, RTPCodecTypeVideo.String()): + return RTPCodecTypeVideo + default: + return RTPCodecType(0) + } +} + +// RTPCodecCapability provides information about codec capabilities. +// +// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpcodeccapability-members +type RTPCodecCapability struct { + MimeType string + ClockRate uint32 + Channels uint16 + SDPFmtpLine string + RTCPFeedback []RTCPFeedback +} + +// RTPHeaderExtensionCapability is used to define a RFC5285 RTP header extension supported by the codec. +// +// https://w3c.github.io/webrtc-pc/#dom-rtcrtpcapabilities-headerextensions +type RTPHeaderExtensionCapability struct { + URI string +} + +// RTPHeaderExtensionParameter represents a negotiated RFC5285 RTP header extension. +// +// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpheaderextensionparameters-members +type RTPHeaderExtensionParameter struct { + URI string + ID int +} + +// RTPCodecParameters is a sequence containing the media codecs that an RtpSender +// will choose from, as well as entries for RTX, RED and FEC mechanisms. This also +// includes the PayloadType that has been negotiated +// +// https://w3c.github.io/webrtc-pc/#rtcrtpcodecparameters +type RTPCodecParameters struct { + RTPCodecCapability + PayloadType PayloadType + + statsID string +} + +// RTCRtpCapabilities is a list of supported codecs and header extensions +// +// https://w3c.github.io/webrtc-pc/#rtcrtpcapabilities +type RTCRtpCapabilities struct { + HeaderExtensions []RTPHeaderExtensionCapability + Codecs []RTPCodecCapability +} + +// RTPParameters is a list of negotiated codecs and header extensions +// +// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpparameters-members +type RTPParameters struct { + HeaderExtensions []RTPHeaderExtensionParameter + Codecs []RTPCodecParameters +} diff --git a/pkg/interceptor/nack.go b/pkg/interceptor/nack.go index 5adc4af6ab9..0be63aabe3b 100644 --- a/pkg/interceptor/nack.go +++ b/pkg/interceptor/nack.go @@ -2,25 +2,13 @@ package interceptor -import ( - "github.com/pion/webrtc/v3" -) - // NACK interceptor generates/responds to nack messages. type NACK struct { - webrtc.NoOpInterceptor -} - -// ConfigureNack will setup everything necessary for handling generating/responding to nack messages. -func ConfigureNack(mediaEngine *webrtc.MediaEngine, interceptorRegistry *webrtc.InterceptorRegistry) error { - mediaEngine.RegisterFeedback(webrtc.RTCPFeedback{Type: "nack"}, webrtc.RTPCodecTypeVideo) - mediaEngine.RegisterFeedback(webrtc.RTCPFeedback{Type: "nack", Parameter: "pli"}, webrtc.RTPCodecTypeVideo) - interceptorRegistry.Add(&NACK{}) - return nil + NoOp } // BindRemoteTrack lets you modify any incoming RTP packets. It is called once for per RemoteTrack. The returned method // will be called once per rtp packet. -func (n *NACK) BindRemoteTrack(ctx *webrtc.TrackRemoteContext, read webrtc.ReadRTP) webrtc.ReadRTP { - return read +func (n *NACK) BindRemoteTrack(_ *TrackInfo, reader RTPReader) RTPReader { + return reader } diff --git a/pkg/interceptor/noop.go b/pkg/interceptor/noop.go new file mode 100644 index 00000000000..6c7f040c79b --- /dev/null +++ b/pkg/interceptor/noop.go @@ -0,0 +1,40 @@ +package interceptor + +// NoOp is an Interceptor that does not modify any packets. It can embedded in other interceptors, so it's +// possible to implement only a subset of the methods. +type NoOp struct{} + +// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might +// change in the future. The returned method will be called once per packet batch. +func (i *NoOp) BindRTCPReader(reader RTCPReader) RTCPReader { + return reader +} + +// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method +// will be called once per packet batch. +func (i *NoOp) BindRTCPWriter(writer RTCPWriter) RTCPWriter { + return writer +} + +// BindLocalTrack lets you modify any outgoing RTP packets. It is called once for per LocalTrack. The returned method +// will be called once per rtp packet. +func (i *NoOp) BindLocalTrack(_ *TrackInfo, writer RTPWriter) RTPWriter { + return writer +} + +// UnbindLocalTrack is called when the Track is removed. It can be used to clean up any data related to that track. +func (i *NoOp) UnbindLocalTrack(_ *TrackInfo) {} + +// BindRemoteTrack lets you modify any incoming RTP packets. It is called once for per RemoteTrack. The returned method +// will be called once per rtp packet. +func (i *NoOp) BindRemoteTrack(_ *TrackInfo, reader RTPReader) RTPReader { + return reader +} + +// UnbindRemoteTrack is called when the Track is removed. It can be used to clean up any data related to that track. +func (i *NoOp) UnbindRemoteTrack(_ *TrackInfo) {} + +// Close closes the Interceptor, cleaning up any data if necessary. +func (i *NoOp) Close() error { + return nil +} diff --git a/rtpreceiver.go b/rtpreceiver.go index 681f2a16a55..6aa0541eacd 100644 --- a/rtpreceiver.go +++ b/rtpreceiver.go @@ -9,6 +9,7 @@ import ( "github.com/pion/rtcp" "github.com/pion/srtp" + "github.com/pion/webrtc/v3/pkg/interceptor" ) // trackStreams maintains a mapping of RTP/RTCP streams to a specific track @@ -32,7 +33,7 @@ type RTPReceiver struct { // A reference to the associated api object api *API - interceptorReadRTCP func() ([]rtcp.Packet, map[interface{}]interface{}, error) + interceptorRTCPReader interceptor.RTCPReader } // NewRTPReceiver constructs a new RTPReceiver @@ -49,7 +50,7 @@ func (api *API) NewRTPReceiver(kind RTPCodecType, transport *DTLSTransport) (*RT received: make(chan interface{}), tracks: []trackStreams{}, } - r.interceptorReadRTCP = api.interceptor.BindReadRTCP(r.readRTCP) + r.interceptorRTCPReader = api.interceptor.BindRTCPReader(interceptor.RTCPReaderFunc(r.readRTCP)) return r, nil } @@ -158,12 +159,12 @@ func (r *RTPReceiver) ReadSimulcast(b []byte, rid string) (n int, err error) { // ReadRTCP is a convenience method that wraps Read and unmarshal for you. // It also runs any configured interceptors. func (r *RTPReceiver) ReadRTCP() ([]rtcp.Packet, error) { - pkts, _, err := r.interceptorReadRTCP() + pkts, _, err := r.interceptorRTCPReader.Read() return pkts, err } // ReadRTCP is a convenience method that wraps Read and unmarshal for you -func (r *RTPReceiver) readRTCP() ([]rtcp.Packet, map[interface{}]interface{}, error) { +func (r *RTPReceiver) readRTCP() ([]rtcp.Packet, interceptor.Attributes, error) { b := make([]byte, receiveMTU) i, err := r.Read(b) if err != nil { @@ -175,7 +176,7 @@ func (r *RTPReceiver) readRTCP() ([]rtcp.Packet, map[interface{}]interface{}, er return nil, nil, err } - return pkts, make(map[interface{}]interface{}), nil + return pkts, make(interceptor.Attributes), nil } // ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you diff --git a/rtpsender.go b/rtpsender.go index b798ffeedcc..8102ad8c70e 100644 --- a/rtpsender.go +++ b/rtpsender.go @@ -10,6 +10,7 @@ import ( "github.com/pion/rtcp" "github.com/pion/rtp" "github.com/pion/srtp" + "github.com/pion/webrtc/v3/pkg/interceptor" ) // RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer @@ -37,7 +38,7 @@ type RTPSender struct { mu sync.RWMutex sendCalled, stopCalled chan interface{} - interceptorReadRTCP func() ([]rtcp.Packet, map[interface{}]interface{}, error) + interceptorRTCPReader interceptor.RTCPReader } // NewRTPSender constructs a new RTPSender @@ -62,7 +63,7 @@ func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSe ssrc: SSRC(randutil.NewMathRandomGenerator().Uint32()), id: id, } - r.interceptorReadRTCP = api.interceptor.BindReadRTCP(r.readRTCP) + r.interceptorRTCPReader = api.interceptor.BindRTCPReader(interceptor.RTCPReaderFunc(r.readRTCP)) return r, nil } @@ -164,9 +165,18 @@ func (r *RTPSender) Send(parameters RTPSendParameters) error { } r.context.params.Codecs = []RTPCodecParameters{codec} - writeStream.setWriteRTP(r.api.interceptor.BindLocalTrack(&r.context, func(p *rtp.Packet, attributes map[interface{}]interface{}) (int, error) { - return rtpWriteStream.WriteRTP(&p.Header, p.Payload) - })) + info := &interceptor.TrackInfo{ + ID: r.context.id, + Params: convertRTPParameters(r.context.params), + SSRC: convertSSRC(r.context.ssrc), + } + writeStream.setRTPWriter( + r.api.interceptor.BindLocalTrack( + info, + interceptor.RTPWriterFunc(func(p *rtp.Packet, attributes interceptor.Attributes) (int, error) { + return rtpWriteStream.WriteRTP(&p.Header, p.Payload) + }), + )) close(r.sendCalled) return nil @@ -204,11 +214,11 @@ func (r *RTPSender) Read(b []byte) (n int, err error) { // ReadRTCP is a convenience method that wraps Read and unmarshals for you. // It also runs any configured interceptors. func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, error) { - pkts, _, err := r.interceptorReadRTCP() + pkts, _, err := r.interceptorRTCPReader.Read() return pkts, err } -func (r *RTPSender) readRTCP() ([]rtcp.Packet, map[interface{}]interface{}, error) { +func (r *RTPSender) readRTCP() ([]rtcp.Packet, interceptor.Attributes, error) { b := make([]byte, receiveMTU) i, err := r.Read(b) if err != nil { @@ -220,7 +230,7 @@ func (r *RTPSender) readRTCP() ([]rtcp.Packet, map[interface{}]interface{}, erro return nil, nil, err } - return pkts, make(map[interface{}]interface{}), nil + return pkts, make(interceptor.Attributes), nil } // hasSent tells if data has been ever sent for this instance diff --git a/track_remote.go b/track_remote.go index d53469dcb53..c7411573829 100644 --- a/track_remote.go +++ b/track_remote.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/pion/rtp" + "github.com/pion/webrtc/v3/pkg/interceptor" ) // TrackRemote represents a single inbound source of media @@ -25,14 +26,7 @@ type TrackRemote struct { receiver *RTPReceiver peeked []byte - interceptorReadRTP func() (*rtp.Packet, map[interface{}]interface{}, error) -} - -// TrackRemoteContext is the Context used in Interceptors. -type TrackRemoteContext struct { - id string - params RTPParameters - ssrc SSRC + interceptorRTPReader interceptor.RTPReader } // NewTrackRemote creates a new TrackRemote. @@ -43,7 +37,11 @@ func NewTrackRemote(kind RTPCodecType, ssrc SSRC, rid string, receiver *RTPRecei rid: rid, receiver: receiver, } - t.interceptorReadRTP = t.receiver.api.interceptor.BindRemoteTrack(t.context(), t.readRTP) + t.interceptorRTPReader = t.receiver.api.interceptor.BindRemoteTrack(&interceptor.TrackInfo{ + ID: t.id, + Params: convertRTPParameters(t.params), + SSRC: convertSSRC(t.ssrc), + }, interceptor.RTPReaderFunc(t.readRTP)) return t } @@ -151,11 +149,11 @@ func (t *TrackRemote) peek(b []byte) (n int, err error) { // ReadRTP is a convenience method that wraps Read and unmarshals for you. // It also runs any configured interceptors. func (t *TrackRemote) ReadRTP() (*rtp.Packet, error) { - p, _, err := t.interceptorReadRTP() + p, _, err := t.interceptorRTPReader.Read() return p, err } -func (t *TrackRemote) readRTP() (*rtp.Packet, map[interface{}]interface{}, error) { +func (t *TrackRemote) readRTP() (*rtp.Packet, interceptor.Attributes, error) { b := make([]byte, receiveMTU) i, err := t.Read(b) if err != nil { @@ -166,15 +164,7 @@ func (t *TrackRemote) readRTP() (*rtp.Packet, map[interface{}]interface{}, error if err := r.Unmarshal(b[:i]); err != nil { return nil, nil, err } - return r, make(map[interface{}]interface{}), nil -} - -func (t *TrackRemote) context() *TrackRemoteContext { - return &TrackRemoteContext{ - id: t.id, - params: t.params, - ssrc: t.ssrc, - } + return r, make(interceptor.Attributes), nil } // determinePayloadType blocks and reads a single packet to determine the PayloadType for this Track @@ -196,26 +186,3 @@ func (t *TrackRemote) determinePayloadType() error { return nil } - -// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both -// PeerConnections and the SSRC/PayloadTypes -func (t *TrackRemoteContext) CodecParameters() []RTPCodecParameters { - return t.params.Codecs -} - -// HeaderExtensions returns the negotiated RTPHeaderExtensionParameters. These are the header extensions supported by -// both PeerConnections and the SSRC/PayloadTypes -func (t *TrackRemoteContext) HeaderExtensions() []RTPHeaderExtensionParameter { - return t.params.HeaderExtensions -} - -// SSRC requires the negotiated SSRC of this track -// This track may have multiple if RTX is enabled -func (t *TrackRemoteContext) SSRC() SSRC { - return t.ssrc -} - -// ID is a unique identifier that is used for both Bind/Unbind -func (t *TrackRemoteContext) ID() string { - return t.id -}