mirror of
https://github.com/pion/webrtc.git
synced 2025-09-27 03:25:58 +08:00
Implement Interceptors
Provide API so that handling around RTP can be easily defined by the user. See the design doc here[0] [0] https://github.com/pion/webrtc-v3-design/issues/34
This commit is contained in:
14
api.go
14
api.go
@@ -3,6 +3,7 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/pion/interceptor"
|
||||||
"github.com/pion/logging"
|
"github.com/pion/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
type API struct {
|
type API struct {
|
||||||
settingEngine *SettingEngine
|
settingEngine *SettingEngine
|
||||||
mediaEngine *MediaEngine
|
mediaEngine *MediaEngine
|
||||||
|
interceptor interceptor.Interceptor
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects
|
// NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects
|
||||||
@@ -35,6 +37,10 @@ func NewAPI(options ...func(*API)) *API {
|
|||||||
a.mediaEngine = &MediaEngine{}
|
a.mediaEngine = &MediaEngine{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.interceptor == nil {
|
||||||
|
a.interceptor = &interceptor.NoOp{}
|
||||||
|
}
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,3 +63,11 @@ func WithSettingEngine(s SettingEngine) func(a *API) {
|
|||||||
a.settingEngine = &s
|
a.settingEngine = &s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInterceptorRegistry allows providing Interceptors to the API.
|
||||||
|
// Settings should not be changed after passing the registry to an API.
|
||||||
|
func WithInterceptorRegistry(interceptorRegistry *interceptor.Registry) func(a *API) {
|
||||||
|
return func(a *API) {
|
||||||
|
a.interceptor = interceptorRegistry.Build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
go.mod
1
go.mod
@@ -6,6 +6,7 @@ require (
|
|||||||
github.com/pion/datachannel v1.4.21
|
github.com/pion/datachannel v1.4.21
|
||||||
github.com/pion/dtls/v2 v2.0.3
|
github.com/pion/dtls/v2 v2.0.3
|
||||||
github.com/pion/ice/v2 v2.0.11
|
github.com/pion/ice/v2 v2.0.11
|
||||||
|
github.com/pion/interceptor v0.0.3
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
github.com/pion/quic v0.1.4
|
github.com/pion/quic v0.1.4
|
||||||
github.com/pion/randutil v0.1.0
|
github.com/pion/randutil v0.1.0
|
||||||
|
2
go.sum
2
go.sum
@@ -106,6 +106,8 @@ github.com/pion/dtls/v2 v2.0.3 h1:3qQ0s4+TXD00rsllL8g8KQcxAs+Y/Z6oz618RXX6p14=
|
|||||||
github.com/pion/dtls/v2 v2.0.3/go.mod h1:TUjyL8bf8LH95h81Xj7kATmzMRt29F/4lxpIPj2Xe4Y=
|
github.com/pion/dtls/v2 v2.0.3/go.mod h1:TUjyL8bf8LH95h81Xj7kATmzMRt29F/4lxpIPj2Xe4Y=
|
||||||
github.com/pion/ice/v2 v2.0.11 h1:XAKZPtglESY/w18eHJR8YLWx3sS8ms7LbWvtOpEM1Dg=
|
github.com/pion/ice/v2 v2.0.11 h1:XAKZPtglESY/w18eHJR8YLWx3sS8ms7LbWvtOpEM1Dg=
|
||||||
github.com/pion/ice/v2 v2.0.11/go.mod h1:Sqdo0oy3ZkaOCsK7Ai9ksLpJkREG03R3fHMJ0PXmHO8=
|
github.com/pion/ice/v2 v2.0.11/go.mod h1:Sqdo0oy3ZkaOCsK7Ai9ksLpJkREG03R3fHMJ0PXmHO8=
|
||||||
|
github.com/pion/interceptor v0.0.3 h1:VQtmPts/2IgYQtb9sZLTp6B0kIdHE5zBMQ6tCcgdJcM=
|
||||||
|
github.com/pion/interceptor v0.0.3/go.mod h1:lPVrf5xfosI989ZcmgPS4WwwRhd+XAyTFaYI2wHf7nU=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
|
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
|
||||||
|
26
interceptor.go
Normal file
26
interceptor.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// +build !js
|
||||||
|
|
||||||
|
package webrtc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pion/interceptor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterDefaultInterceptors 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 *interceptor.Registry) 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 *interceptor.Registry) error {
|
||||||
|
mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack"}, RTPCodecTypeVideo)
|
||||||
|
mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack", Parameter: "pli"}, RTPCodecTypeVideo)
|
||||||
|
interceptorRegistry.Add(&interceptor.NACK{})
|
||||||
|
return nil
|
||||||
|
}
|
188
interceptor_test.go
Normal file
188
interceptor_test.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// +build !js
|
||||||
|
|
||||||
|
package webrtc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pion/interceptor"
|
||||||
|
"github.com/pion/rtcp"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
"github.com/pion/transport/test"
|
||||||
|
"github.com/pion/webrtc/v3/pkg/media"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testInterceptor struct {
|
||||||
|
t *testing.T
|
||||||
|
extensionID uint8
|
||||||
|
rtcpWriter atomic.Value
|
||||||
|
lastRTCP atomic.Value
|
||||||
|
interceptor.NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testInterceptor) BindLocalStream(_ *interceptor.StreamInfo, 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 writer.Write(p, attributes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testInterceptor) BindRemoteStream(info *interceptor.StreamInfo, 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
|
||||||
|
}
|
||||||
|
// set extension on incoming packet
|
||||||
|
p.Header.Extension = true
|
||||||
|
p.Header.ExtensionProfile = 0xBEDE
|
||||||
|
assert.NoError(t.t, p.Header.SetExtension(t.extensionID, []byte("read")))
|
||||||
|
|
||||||
|
// write back a pli
|
||||||
|
rtcpWriter := t.rtcpWriter.Load().(interceptor.RTCPWriter)
|
||||||
|
pli := &rtcp.PictureLossIndication{SenderSSRC: info.SSRC, MediaSSRC: info.SSRC}
|
||||||
|
_, err = rtcpWriter.Write([]rtcp.Packet{pli}, make(interceptor.Attributes))
|
||||||
|
assert.NoError(t.t, err)
|
||||||
|
|
||||||
|
return p, attributes, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
t.lastRTCP.Store(pkts[0])
|
||||||
|
|
||||||
|
return pkts, attributes, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testInterceptor) lastReadRTCP() rtcp.Packet {
|
||||||
|
p, _ := t.lastRTCP.Load().(rtcp.Packet)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
|
||||||
|
t.rtcpWriter.Store(writer)
|
||||||
|
return writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeerConnection_Interceptor(t *testing.T) {
|
||||||
|
to := test.TimeOut(time.Second * 20)
|
||||||
|
defer to.Stop()
|
||||||
|
|
||||||
|
report := test.CheckRoutines(t)
|
||||||
|
defer report()
|
||||||
|
|
||||||
|
createPC := func(i interceptor.Interceptor) *PeerConnection {
|
||||||
|
m := &MediaEngine{}
|
||||||
|
err := m.RegisterDefaultCodecs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ir := &interceptor.Registry{}
|
||||||
|
ir.Add(i)
|
||||||
|
pc, err := NewAPI(WithMediaEngine(m), WithInterceptorRegistry(ir)).NewPeerConnection(Configuration{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pc
|
||||||
|
}
|
||||||
|
|
||||||
|
sendInterceptor := &testInterceptor{t: t, extensionID: 1}
|
||||||
|
senderPC := createPC(sendInterceptor)
|
||||||
|
receiverPC := createPC(&testInterceptor{t: t, extensionID: 2})
|
||||||
|
|
||||||
|
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sender, err := senderPC.AddTrack(track)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pending := new(int32)
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
*pending++
|
||||||
|
receiverPC.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) {
|
||||||
|
p, readErr := track.ReadRTP()
|
||||||
|
if readErr != nil {
|
||||||
|
t.Fatal(readErr)
|
||||||
|
}
|
||||||
|
assert.Equal(t, p.Extension, true)
|
||||||
|
assert.Equal(t, "write", string(p.GetExtension(1)))
|
||||||
|
assert.Equal(t, "read", string(p.GetExtension(2)))
|
||||||
|
atomic.AddInt32(pending, -1)
|
||||||
|
wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, readErr = track.ReadRTP()
|
||||||
|
if readErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
*pending++
|
||||||
|
go func() {
|
||||||
|
_, readErr := sender.ReadRTCP()
|
||||||
|
assert.NoError(t, readErr)
|
||||||
|
atomic.AddInt32(pending, -1)
|
||||||
|
wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, readErr = sender.ReadRTCP()
|
||||||
|
if readErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = signalPair(senderPC, receiverPC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
if routineErr := track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil {
|
||||||
|
t.Error(routineErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if atomic.LoadInt32(pending) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
assert.NoError(t, senderPC.Close())
|
||||||
|
assert.NoError(t, receiverPC.Close())
|
||||||
|
|
||||||
|
pli, _ := sendInterceptor.lastReadRTCP().(*rtcp.PictureLossIndication)
|
||||||
|
if pli == nil || pli.SenderSSRC == 0 {
|
||||||
|
t.Errorf("pli not found by send interceptor")
|
||||||
|
}
|
||||||
|
}
|
29
interceptor_track_local.go
Normal file
29
interceptor_track_local.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// +build !js
|
||||||
|
|
||||||
|
package webrtc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pion/interceptor"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type interceptorTrackLocalWriter struct {
|
||||||
|
TrackLocalWriter
|
||||||
|
rtpWriter atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interceptorTrackLocalWriter) setRTPWriter(writer interceptor.RTPWriter) {
|
||||||
|
i.rtpWriter.Store(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interceptorTrackLocalWriter) WriteRTP(header *rtp.Header, payload []byte) (int, error) {
|
||||||
|
writer := i.rtpWriter.Load().(interceptor.RTPWriter)
|
||||||
|
|
||||||
|
if writer == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return writer.Write(&rtp.Packet{Header: *header, Payload: payload}, make(interceptor.Attributes))
|
||||||
|
}
|
@@ -233,6 +233,22 @@ func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapabi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterFeedback adds feedback mechanism to already registered codecs.
|
||||||
|
func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType) {
|
||||||
|
switch typ {
|
||||||
|
case RTPCodecTypeVideo:
|
||||||
|
for i, v := range m.videoCodecs {
|
||||||
|
v.RTCPFeedback = append(v.RTCPFeedback, feedback)
|
||||||
|
m.videoCodecs[i] = v
|
||||||
|
}
|
||||||
|
case RTPCodecTypeAudio:
|
||||||
|
for i, v := range m.audioCodecs {
|
||||||
|
v.RTCPFeedback = append(v.RTCPFeedback, feedback)
|
||||||
|
m.audioCodecs[i] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetHeaderExtensionID returns the negotiated ID for a header extension.
|
// GetHeaderExtensionID returns the negotiated ID for a header extension.
|
||||||
// If the Header Extension isn't enabled ok will be false
|
// If the Header Extension isn't enabled ok will be false
|
||||||
func (m *MediaEngine) GetHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) {
|
func (m *MediaEngine) GetHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) {
|
||||||
@@ -249,19 +265,19 @@ func (m *MediaEngine) GetHeaderExtensionID(extension RTPHeaderExtensionCapabilit
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, error) {
|
func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, RTPCodecType, error) {
|
||||||
for _, codec := range m.negotiatedVideoCodecs {
|
for _, codec := range m.negotiatedVideoCodecs {
|
||||||
if codec.PayloadType == payloadType {
|
if codec.PayloadType == payloadType {
|
||||||
return codec, nil
|
return codec, RTPCodecTypeVideo, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, codec := range m.negotiatedAudioCodecs {
|
for _, codec := range m.negotiatedAudioCodecs {
|
||||||
if codec.PayloadType == payloadType {
|
if codec.PayloadType == payloadType {
|
||||||
return codec, nil
|
return codec, RTPCodecTypeAudio, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return RTPCodecParameters{}, ErrCodecNotFound
|
return RTPCodecParameters{}, 0, ErrCodecNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MediaEngine) collectStats(collector *statsReportCollector) {
|
func (m *MediaEngine) collectStats(collector *statsReportCollector) {
|
||||||
@@ -309,7 +325,7 @@ func (m *MediaEngine) updateCodecParameters(remoteCodec RTPCodecParameters, typ
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = m.getCodecByPayload(PayloadType(payloadType)); err != nil {
|
if _, _, err = m.getCodecByPayload(PayloadType(payloadType)); err != nil {
|
||||||
return nil // not an error, we just ignore this codec we don't support
|
return nil // not an error, we just ignore this codec we don't support
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,8 +394,8 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for id, extension := range extensions {
|
for extension, id := range extensions {
|
||||||
if err = m.updateHeaderExtension(extension, id, typ); err != nil {
|
if err = m.updateHeaderExtension(id, extension, typ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,6 +421,39 @@ func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType) RTPParameters {
|
||||||
|
headerExtensions := make([]RTPHeaderExtensionParameter, 0)
|
||||||
|
for id, e := range m.negotiatedHeaderExtensions {
|
||||||
|
if e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo {
|
||||||
|
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RTPParameters{
|
||||||
|
HeaderExtensions: headerExtensions,
|
||||||
|
Codecs: m.getCodecsByKind(typ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MediaEngine) getRTPParametersByPayloadType(payloadType PayloadType) (RTPParameters, error) {
|
||||||
|
codec, typ, err := m.getCodecByPayload(payloadType)
|
||||||
|
if err != nil {
|
||||||
|
return RTPParameters{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
headerExtensions := make([]RTPHeaderExtensionParameter, 0)
|
||||||
|
for id, e := range m.negotiatedHeaderExtensions {
|
||||||
|
if e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo {
|
||||||
|
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RTPParameters{
|
||||||
|
HeaderExtensions: headerExtensions,
|
||||||
|
Codecs: []RTPCodecParameters{codec},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MediaEngine) negotiatedHeaderExtensionsForType(typ RTPCodecType) map[int]mediaEngineHeaderExtension {
|
func (m *MediaEngine) negotiatedHeaderExtensionsForType(typ RTPCodecType) map[int]mediaEngineHeaderExtension {
|
||||||
headerExtensions := map[int]mediaEngineHeaderExtension{}
|
headerExtensions := map[int]mediaEngineHeaderExtension{}
|
||||||
for id, e := range m.negotiatedHeaderExtensions {
|
for id, e := range m.negotiatedHeaderExtensions {
|
||||||
|
@@ -63,7 +63,7 @@ a=fmtp:111 minptime=10; useinbandfec=1
|
|||||||
assert.False(t, m.negotiatedVideo)
|
assert.False(t, m.negotiatedVideo)
|
||||||
assert.True(t, m.negotiatedAudio)
|
assert.True(t, m.negotiatedAudio)
|
||||||
|
|
||||||
opusCodec, err := m.getCodecByPayload(111)
|
opusCodec, _, err := m.getCodecByPayload(111)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
|
assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
|
||||||
})
|
})
|
||||||
@@ -85,10 +85,10 @@ a=fmtp:112 minptime=10; useinbandfec=1
|
|||||||
assert.False(t, m.negotiatedVideo)
|
assert.False(t, m.negotiatedVideo)
|
||||||
assert.True(t, m.negotiatedAudio)
|
assert.True(t, m.negotiatedAudio)
|
||||||
|
|
||||||
_, err := m.getCodecByPayload(111)
|
_, _, err := m.getCodecByPayload(111)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
opusCodec, err := m.getCodecByPayload(112)
|
opusCodec, _, err := m.getCodecByPayload(112)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
|
assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
|
||||||
})
|
})
|
||||||
@@ -110,7 +110,7 @@ a=fmtp:111 minptime=10; useinbandfec=1
|
|||||||
assert.False(t, m.negotiatedVideo)
|
assert.False(t, m.negotiatedVideo)
|
||||||
assert.True(t, m.negotiatedAudio)
|
assert.True(t, m.negotiatedAudio)
|
||||||
|
|
||||||
opusCodec, err := m.getCodecByPayload(111)
|
opusCodec, _, err := m.getCodecByPayload(111)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, opusCodec.MimeType, "audio/OPUS")
|
assert.Equal(t, opusCodec.MimeType, "audio/OPUS")
|
||||||
})
|
})
|
||||||
@@ -131,7 +131,7 @@ a=rtpmap:111 opus/48000/2
|
|||||||
assert.False(t, m.negotiatedVideo)
|
assert.False(t, m.negotiatedVideo)
|
||||||
assert.True(t, m.negotiatedAudio)
|
assert.True(t, m.negotiatedAudio)
|
||||||
|
|
||||||
opusCodec, err := m.getCodecByPayload(111)
|
opusCodec, _, err := m.getCodecByPayload(111)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
|
assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
|
||||||
})
|
})
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/ice/v2"
|
"github.com/pion/ice/v2"
|
||||||
|
"github.com/pion/interceptor"
|
||||||
"github.com/pion/logging"
|
"github.com/pion/logging"
|
||||||
"github.com/pion/rtcp"
|
"github.com/pion/rtcp"
|
||||||
"github.com/pion/sdp/v3"
|
"github.com/pion/sdp/v3"
|
||||||
@@ -76,6 +77,8 @@ type PeerConnection struct {
|
|||||||
// A reference to the associated API state used by this connection
|
// A reference to the associated API state used by this connection
|
||||||
api *API
|
api *API
|
||||||
log logging.LeveledLogger
|
log logging.LeveledLogger
|
||||||
|
|
||||||
|
interceptorRTCPWriter interceptor.RTCPWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPeerConnection creates a peerconnection with the default
|
// NewPeerConnection creates a peerconnection with the default
|
||||||
@@ -119,6 +122,8 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
|
|||||||
log: api.settingEngine.LoggerFactory.NewLogger("pc"),
|
log: api.settingEngine.LoggerFactory.NewLogger("pc"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pc.interceptorRTCPWriter = api.interceptor.BindRTCPWriter(interceptor.RTCPWriterFunc(pc.writeRTCP))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if err = pc.initConfiguration(configuration); err != nil {
|
if err = pc.initConfiguration(configuration); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1125,7 +1130,7 @@ func (pc *PeerConnection) startReceiver(incoming trackDetails, receiver *RTPRece
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
codec, err := pc.api.mediaEngine.getCodecByPayload(receiver.Track().PayloadType())
|
params, err := pc.api.mediaEngine.getRTPParametersByPayloadType(receiver.Track().PayloadType())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pc.log.Warnf("no codec could be found for payloadType %d", receiver.Track().PayloadType())
|
pc.log.Warnf("no codec could be found for payloadType %d", receiver.Track().PayloadType())
|
||||||
return
|
return
|
||||||
@@ -1133,7 +1138,9 @@ func (pc *PeerConnection) startReceiver(incoming trackDetails, receiver *RTPRece
|
|||||||
|
|
||||||
receiver.Track().mu.Lock()
|
receiver.Track().mu.Lock()
|
||||||
receiver.Track().kind = receiver.kind
|
receiver.Track().kind = receiver.kind
|
||||||
receiver.Track().codec = codec
|
receiver.Track().codec = params.Codecs[0]
|
||||||
|
receiver.Track().params = params
|
||||||
|
receiver.Track().bindInterceptor()
|
||||||
receiver.Track().mu.Unlock()
|
receiver.Track().mu.Unlock()
|
||||||
|
|
||||||
pc.onTrack(receiver.Track(), receiver)
|
pc.onTrack(receiver.Track(), receiver)
|
||||||
@@ -1335,7 +1342,7 @@ func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc SSRC) e
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
codec, err := pc.api.mediaEngine.getCodecByPayload(payloadType)
|
params, err := pc.api.mediaEngine.getRTPParametersByPayloadType(payloadType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1345,7 +1352,7 @@ func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc SSRC) e
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
track, err := t.Receiver().receiveForRid(rid, codec, ssrc)
|
track, err := t.Receiver().receiveForRid(rid, params, ssrc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1730,28 +1737,33 @@ func (pc *PeerConnection) SetIdentityProvider(provider string) error {
|
|||||||
return errPeerConnSetIdentityProviderNotImplemented
|
return errPeerConnSetIdentityProviderNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteRTCP sends a user provided RTCP packet to the connected peer
|
// WriteRTCP sends a user provided RTCP packet to the connected peer. If no peer is connected the
|
||||||
// If no peer is connected the packet is discarded
|
// packet is discarded. It also runs any configured interceptors.
|
||||||
func (pc *PeerConnection) WriteRTCP(pkts []rtcp.Packet) error {
|
func (pc *PeerConnection) WriteRTCP(pkts []rtcp.Packet) error {
|
||||||
|
_, err := pc.interceptorRTCPWriter.Write(pkts, make(interceptor.Attributes))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *PeerConnection) writeRTCP(pkts []rtcp.Packet, _ interceptor.Attributes) (int, error) {
|
||||||
raw, err := rtcp.Marshal(pkts)
|
raw, err := rtcp.Marshal(pkts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
srtcpSession, err := pc.dtlsTransport.getSRTCPSession()
|
srtcpSession, err := pc.dtlsTransport.getSRTCPSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStream, err := srtcpSession.OpenWriteStream()
|
writeStream, err := srtcpSession.OpenWriteStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %v", errPeerConnWriteRTCPOpenWriteStream, err)
|
return 0, fmt.Errorf("%w: %v", errPeerConnWriteRTCPOpenWriteStream, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := writeStream.Write(raw); err != nil {
|
if n, err := writeStream.Write(raw); err != nil {
|
||||||
return err
|
return n, err
|
||||||
}
|
}
|
||||||
return nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close ends the PeerConnection
|
// Close ends the PeerConnection
|
||||||
@@ -1775,6 +1787,8 @@ func (pc *PeerConnection) Close() error {
|
|||||||
// continue the chain the Mux has to be closed.
|
// continue the chain the Mux has to be closed.
|
||||||
closeErrs := make([]error, 4)
|
closeErrs := make([]error, 4)
|
||||||
|
|
||||||
|
closeErrs = append(closeErrs, pc.api.interceptor.Close())
|
||||||
|
|
||||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #4)
|
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #4)
|
||||||
for _, t := range pc.GetTransceivers() {
|
for _, t := range pc.GetTransceivers() {
|
||||||
if !t.stopped {
|
if !t.stopped {
|
||||||
|
16
rtpcodec.go
16
rtpcodec.go
@@ -57,6 +57,14 @@ type RTPHeaderExtensionCapability struct {
|
|||||||
URI string
|
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
|
// 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
|
// will choose from, as well as entries for RTX, RED and FEC mechanisms. This also
|
||||||
// includes the PayloadType that has been negotiated
|
// includes the PayloadType that has been negotiated
|
||||||
@@ -77,6 +85,14 @@ type RTCRtpCapabilities struct {
|
|||||||
Codecs []RTPCodecCapability
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Do a fuzzy find for a codec in the list of codecs
|
// Do a fuzzy find for a codec in the list of codecs
|
||||||
// Used for lookup up a codec in an existing list to find a match
|
// Used for lookup up a codec in an existing list to find a match
|
||||||
func codecParametersFuzzySearch(needle RTPCodecParameters, haystack []RTPCodecParameters) (RTPCodecParameters, error) {
|
func codecParametersFuzzySearch(needle RTPCodecParameters, haystack []RTPCodecParameters) (RTPCodecParameters, error) {
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pion/interceptor"
|
||||||
"github.com/pion/rtcp"
|
"github.com/pion/rtcp"
|
||||||
"github.com/pion/srtp"
|
"github.com/pion/srtp"
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,8 @@ type RTPReceiver struct {
|
|||||||
|
|
||||||
// A reference to the associated api object
|
// A reference to the associated api object
|
||||||
api *API
|
api *API
|
||||||
|
|
||||||
|
interceptorRTCPReader interceptor.RTCPReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTPReceiver constructs a new RTPReceiver
|
// NewRTPReceiver constructs a new RTPReceiver
|
||||||
@@ -39,14 +42,17 @@ func (api *API) NewRTPReceiver(kind RTPCodecType, transport *DTLSTransport) (*RT
|
|||||||
return nil, errRTPReceiverDTLSTransportNil
|
return nil, errRTPReceiverDTLSTransportNil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &RTPReceiver{
|
r := &RTPReceiver{
|
||||||
kind: kind,
|
kind: kind,
|
||||||
transport: transport,
|
transport: transport,
|
||||||
api: api,
|
api: api,
|
||||||
closed: make(chan interface{}),
|
closed: make(chan interface{}),
|
||||||
received: make(chan interface{}),
|
received: make(chan interface{}),
|
||||||
tracks: []trackStreams{},
|
tracks: []trackStreams{},
|
||||||
}, nil
|
}
|
||||||
|
r.interceptorRTCPReader = api.interceptor.BindRTCPReader(interceptor.RTCPReaderFunc(r.readRTCP))
|
||||||
|
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transport returns the currently-configured *DTLSTransport or nil
|
// Transport returns the currently-configured *DTLSTransport or nil
|
||||||
@@ -94,11 +100,12 @@ func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error {
|
|||||||
|
|
||||||
if len(parameters.Encodings) == 1 && parameters.Encodings[0].SSRC != 0 {
|
if len(parameters.Encodings) == 1 && parameters.Encodings[0].SSRC != 0 {
|
||||||
t := trackStreams{
|
t := trackStreams{
|
||||||
track: &TrackRemote{
|
track: newTrackRemote(
|
||||||
kind: r.kind,
|
r.kind,
|
||||||
ssrc: parameters.Encodings[0].SSRC,
|
parameters.Encodings[0].SSRC,
|
||||||
receiver: r,
|
"",
|
||||||
},
|
r,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -111,11 +118,12 @@ func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error {
|
|||||||
} else {
|
} else {
|
||||||
for _, encoding := range parameters.Encodings {
|
for _, encoding := range parameters.Encodings {
|
||||||
r.tracks = append(r.tracks, trackStreams{
|
r.tracks = append(r.tracks, trackStreams{
|
||||||
track: &TrackRemote{
|
track: newTrackRemote(
|
||||||
kind: r.kind,
|
r.kind,
|
||||||
rid: encoding.RID,
|
0,
|
||||||
receiver: r,
|
encoding.RID,
|
||||||
},
|
r,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,15 +156,27 @@ func (r *RTPReceiver) ReadSimulcast(b []byte, rid string) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRTCP is a convenience method that wraps Read and unmarshal for you
|
// 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) {
|
func (r *RTPReceiver) ReadRTCP() ([]rtcp.Packet, error) {
|
||||||
|
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, interceptor.Attributes, error) {
|
||||||
b := make([]byte, receiveMTU)
|
b := make([]byte, receiveMTU)
|
||||||
i, err := r.Read(b)
|
i, err := r.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtcp.Unmarshal(b[:i])
|
pkts, err := rtcp.Unmarshal(b[:i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkts, make(interceptor.Attributes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you
|
// ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you
|
||||||
@@ -232,7 +252,7 @@ func (r *RTPReceiver) readRTP(b []byte, reader *TrackRemote) (n int, err error)
|
|||||||
|
|
||||||
// receiveForRid is the sibling of Receive expect for RIDs instead of SSRCs
|
// receiveForRid is the sibling of Receive expect for RIDs instead of SSRCs
|
||||||
// It populates all the internal state for the given RID
|
// It populates all the internal state for the given RID
|
||||||
func (r *RTPReceiver) receiveForRid(rid string, codec RTPCodecParameters, ssrc SSRC) (*TrackRemote, error) {
|
func (r *RTPReceiver) receiveForRid(rid string, params RTPParameters, ssrc SSRC) (*TrackRemote, error) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
@@ -240,8 +260,10 @@ func (r *RTPReceiver) receiveForRid(rid string, codec RTPCodecParameters, ssrc S
|
|||||||
if r.tracks[i].track.RID() == rid {
|
if r.tracks[i].track.RID() == rid {
|
||||||
r.tracks[i].track.mu.Lock()
|
r.tracks[i].track.mu.Lock()
|
||||||
r.tracks[i].track.kind = r.kind
|
r.tracks[i].track.kind = r.kind
|
||||||
r.tracks[i].track.codec = codec
|
r.tracks[i].track.codec = params.Codecs[0]
|
||||||
|
r.tracks[i].track.params = params
|
||||||
r.tracks[i].track.ssrc = ssrc
|
r.tracks[i].track.ssrc = ssrc
|
||||||
|
r.tracks[i].track.bindInterceptor()
|
||||||
r.tracks[i].track.mu.Unlock()
|
r.tracks[i].track.mu.Unlock()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
89
rtpsender.go
89
rtpsender.go
@@ -6,8 +6,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pion/interceptor"
|
||||||
"github.com/pion/randutil"
|
"github.com/pion/randutil"
|
||||||
"github.com/pion/rtcp"
|
"github.com/pion/rtcp"
|
||||||
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/srtp"
|
"github.com/pion/srtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,13 +18,12 @@ type RTPSender struct {
|
|||||||
track TrackLocal
|
track TrackLocal
|
||||||
|
|
||||||
rtcpReadStream *srtp.ReadStreamSRTCP
|
rtcpReadStream *srtp.ReadStreamSRTCP
|
||||||
rtpWriteStream *srtp.WriteStreamSRTP
|
context TrackLocalContext
|
||||||
|
|
||||||
transport *DTLSTransport
|
transport *DTLSTransport
|
||||||
|
|
||||||
payloadType PayloadType
|
payloadType PayloadType
|
||||||
ssrc SSRC
|
ssrc SSRC
|
||||||
codec RTPCodecParameters
|
|
||||||
|
|
||||||
// nolint:godox
|
// nolint:godox
|
||||||
// TODO(sgotti) remove this when in future we'll avoid replacing
|
// TODO(sgotti) remove this when in future we'll avoid replacing
|
||||||
@@ -36,6 +37,8 @@ type RTPSender struct {
|
|||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
sendCalled, stopCalled chan interface{}
|
sendCalled, stopCalled chan interface{}
|
||||||
|
|
||||||
|
interceptorRTCPReader interceptor.RTCPReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTPSender constructs a new RTPSender
|
// NewRTPSender constructs a new RTPSender
|
||||||
@@ -51,7 +54,7 @@ func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &RTPSender{
|
r := &RTPSender{
|
||||||
track: track,
|
track: track,
|
||||||
transport: transport,
|
transport: transport,
|
||||||
api: api,
|
api: api,
|
||||||
@@ -59,7 +62,10 @@ func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSe
|
|||||||
stopCalled: make(chan interface{}),
|
stopCalled: make(chan interface{}),
|
||||||
ssrc: SSRC(randutil.NewMathRandomGenerator().Uint32()),
|
ssrc: SSRC(randutil.NewMathRandomGenerator().Uint32()),
|
||||||
id: id,
|
id: id,
|
||||||
}, nil
|
}
|
||||||
|
r.interceptorRTCPReader = api.interceptor.BindRTCPReader(interceptor.RTCPReaderFunc(r.readRTCP))
|
||||||
|
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RTPSender) isNegotiated() bool {
|
func (r *RTPSender) isNegotiated() bool {
|
||||||
@@ -97,11 +103,7 @@ func (r *RTPSender) ReplaceTrack(track TrackLocal) error {
|
|||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if r.hasSent() {
|
if r.hasSent() {
|
||||||
if err := r.track.Unbind(TrackLocalContext{
|
if err := r.track.Unbind(r.context); err != nil {
|
||||||
id: r.id,
|
|
||||||
ssrc: r.ssrc,
|
|
||||||
writeStream: r.rtpWriteStream,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,12 +113,7 @@ func (r *RTPSender) ReplaceTrack(track TrackLocal) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := track.Bind(TrackLocalContext{
|
if _, err := track.Bind(r.context); err != nil {
|
||||||
id: r.id,
|
|
||||||
codecs: []RTPCodecParameters{r.codec},
|
|
||||||
ssrc: r.ssrc,
|
|
||||||
writeStream: r.rtpWriteStream,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,18 +145,53 @@ func (r *RTPSender) Send(parameters RTPSendParameters) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.rtpWriteStream, err = srtpSession.OpenWriteStream(); err != nil {
|
rtpWriteStream, err := srtpSession.OpenWriteStream()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.codec, err = r.track.Bind(TrackLocalContext{
|
writeStream := &interceptorTrackLocalWriter{TrackLocalWriter: rtpWriteStream}
|
||||||
|
|
||||||
|
r.context = TrackLocalContext{
|
||||||
id: r.id,
|
id: r.id,
|
||||||
codecs: r.api.mediaEngine.getCodecsByKind(r.track.Kind()),
|
params: r.api.mediaEngine.getRTPParametersByKind(r.track.Kind()),
|
||||||
ssrc: parameters.Encodings.SSRC,
|
ssrc: parameters.Encodings.SSRC,
|
||||||
writeStream: r.rtpWriteStream,
|
writeStream: writeStream,
|
||||||
}); err != nil {
|
}
|
||||||
|
|
||||||
|
codec, err := r.track.Bind(r.context)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
r.context.params.Codecs = []RTPCodecParameters{codec}
|
||||||
|
|
||||||
|
headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(r.context.params.HeaderExtensions))
|
||||||
|
for _, h := range r.context.params.HeaderExtensions {
|
||||||
|
headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI})
|
||||||
|
}
|
||||||
|
feedbacks := make([]interceptor.RTCPFeedback, 0, len(codec.RTCPFeedback))
|
||||||
|
for _, f := range codec.RTCPFeedback {
|
||||||
|
feedbacks = append(feedbacks, interceptor.RTCPFeedback{Type: f.Type, Parameter: f.Parameter})
|
||||||
|
}
|
||||||
|
info := &interceptor.StreamInfo{
|
||||||
|
ID: r.context.id,
|
||||||
|
Attributes: interceptor.Attributes{},
|
||||||
|
SSRC: uint32(r.context.ssrc),
|
||||||
|
PayloadType: uint8(codec.PayloadType),
|
||||||
|
RTPHeaderExtensions: headerExtensions,
|
||||||
|
MimeType: codec.MimeType,
|
||||||
|
ClockRate: codec.ClockRate,
|
||||||
|
Channels: codec.Channels,
|
||||||
|
SDPFmtpLine: codec.SDPFmtpLine,
|
||||||
|
RTCPFeedback: feedbacks,
|
||||||
|
}
|
||||||
|
writeStream.setRTPWriter(
|
||||||
|
r.api.interceptor.BindLocalStream(
|
||||||
|
info,
|
||||||
|
interceptor.RTPWriterFunc(func(p *rtp.Packet, attributes interceptor.Attributes) (int, error) {
|
||||||
|
return rtpWriteStream.WriteRTP(&p.Header, p.Payload)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
|
||||||
close(r.sendCalled)
|
close(r.sendCalled)
|
||||||
return nil
|
return nil
|
||||||
@@ -194,15 +226,26 @@ func (r *RTPSender) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRTCP is a convenience method that wraps Read and unmarshals for you
|
// 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) {
|
func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, error) {
|
||||||
|
pkts, _, err := r.interceptorRTCPReader.Read()
|
||||||
|
return pkts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RTPSender) readRTCP() ([]rtcp.Packet, interceptor.Attributes, error) {
|
||||||
b := make([]byte, receiveMTU)
|
b := make([]byte, receiveMTU)
|
||||||
i, err := r.Read(b)
|
i, err := r.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtcp.Unmarshal(b[:i])
|
pkts, err := rtcp.Unmarshal(b[:i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkts, make(interceptor.Attributes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasSent tells if data has been ever sent for this instance
|
// hasSent tells if data has been ever sent for this instance
|
||||||
|
@@ -11,10 +11,11 @@ type TrackLocalWriter interface {
|
|||||||
Write(b []byte) (int, error)
|
Write(b []byte) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackLocalContext is the Context passed when a TrackLocal has been Binded/Unbinded from a PeerConnection
|
// TrackLocalContext is the Context passed when a TrackLocal has been Binded/Unbinded from a PeerConnection, and used
|
||||||
|
// in Interceptors.
|
||||||
type TrackLocalContext struct {
|
type TrackLocalContext struct {
|
||||||
id string
|
id string
|
||||||
codecs []RTPCodecParameters
|
params RTPParameters
|
||||||
ssrc SSRC
|
ssrc SSRC
|
||||||
writeStream TrackLocalWriter
|
writeStream TrackLocalWriter
|
||||||
}
|
}
|
||||||
@@ -22,7 +23,13 @@ type TrackLocalContext struct {
|
|||||||
// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both
|
// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both
|
||||||
// PeerConnections and the SSRC/PayloadTypes
|
// PeerConnections and the SSRC/PayloadTypes
|
||||||
func (t *TrackLocalContext) CodecParameters() []RTPCodecParameters {
|
func (t *TrackLocalContext) CodecParameters() []RTPCodecParameters {
|
||||||
return t.codecs
|
return t.params.Codecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderExtensions returns the negotiated RTPHeaderExtensionParameters. These are the header extensions supported by
|
||||||
|
// both PeerConnections and the SSRC/PayloadTypes
|
||||||
|
func (t *TrackLocalContext) HeaderExtensions() []RTPHeaderExtensionParameter {
|
||||||
|
return t.params.HeaderExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSRC requires the negotiated SSRC of this track
|
// SSRC requires the negotiated SSRC of this track
|
||||||
|
@@ -5,6 +5,7 @@ package webrtc
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pion/interceptor"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,10 +20,49 @@ type TrackRemote struct {
|
|||||||
kind RTPCodecType
|
kind RTPCodecType
|
||||||
ssrc SSRC
|
ssrc SSRC
|
||||||
codec RTPCodecParameters
|
codec RTPCodecParameters
|
||||||
|
params RTPParameters
|
||||||
rid string
|
rid string
|
||||||
|
|
||||||
receiver *RTPReceiver
|
receiver *RTPReceiver
|
||||||
peeked []byte
|
peeked []byte
|
||||||
|
|
||||||
|
interceptorRTPReader interceptor.RTPReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTrackRemote(kind RTPCodecType, ssrc SSRC, rid string, receiver *RTPReceiver) *TrackRemote {
|
||||||
|
t := &TrackRemote{
|
||||||
|
kind: kind,
|
||||||
|
ssrc: ssrc,
|
||||||
|
rid: rid,
|
||||||
|
receiver: receiver,
|
||||||
|
}
|
||||||
|
t.interceptorRTPReader = interceptor.RTPReaderFunc(t.readRTP)
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TrackRemote) bindInterceptor() {
|
||||||
|
headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(t.params.HeaderExtensions))
|
||||||
|
for _, h := range t.params.HeaderExtensions {
|
||||||
|
headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI})
|
||||||
|
}
|
||||||
|
feedbacks := make([]interceptor.RTCPFeedback, 0, len(t.codec.RTCPFeedback))
|
||||||
|
for _, f := range t.codec.RTCPFeedback {
|
||||||
|
feedbacks = append(feedbacks, interceptor.RTCPFeedback{Type: f.Type, Parameter: f.Parameter})
|
||||||
|
}
|
||||||
|
info := &interceptor.StreamInfo{
|
||||||
|
ID: t.id,
|
||||||
|
Attributes: interceptor.Attributes{},
|
||||||
|
SSRC: uint32(t.ssrc),
|
||||||
|
PayloadType: uint8(t.payloadType),
|
||||||
|
RTPHeaderExtensions: headerExtensions,
|
||||||
|
MimeType: t.codec.MimeType,
|
||||||
|
ClockRate: t.codec.ClockRate,
|
||||||
|
Channels: t.codec.Channels,
|
||||||
|
SDPFmtpLine: t.codec.SDPFmtpLine,
|
||||||
|
RTCPFeedback: feedbacks,
|
||||||
|
}
|
||||||
|
t.interceptorRTPReader = t.receiver.api.interceptor.BindRemoteStream(info, interceptor.RTPReaderFunc(t.readRTP))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID is the unique identifier for this Track. This should be unique for the
|
// ID is the unique identifier for this Track. This should be unique for the
|
||||||
@@ -125,19 +165,25 @@ func (t *TrackRemote) peek(b []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRTP is a convenience method that wraps Read and unmarshals for you
|
// 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) {
|
func (t *TrackRemote) ReadRTP() (*rtp.Packet, error) {
|
||||||
|
p, _, err := t.interceptorRTPReader.Read()
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TrackRemote) readRTP() (*rtp.Packet, interceptor.Attributes, error) {
|
||||||
b := make([]byte, receiveMTU)
|
b := make([]byte, receiveMTU)
|
||||||
i, err := t.Read(b)
|
i, err := t.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &rtp.Packet{}
|
r := &rtp.Packet{}
|
||||||
if err := r.Unmarshal(b[:i]); err != nil {
|
if err := r.Unmarshal(b[:i]); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return r, nil
|
return r, interceptor.Attributes{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// determinePayloadType blocks and reads a single packet to determine the PayloadType for this Track
|
// determinePayloadType blocks and reads a single packet to determine the PayloadType for this Track
|
||||||
|
Reference in New Issue
Block a user