mirror of
https://github.com/pion/webrtc.git
synced 2025-09-26 19:21:12 +08:00
Add Retransmission and FEC to TrackLocal
If the MediaEngine contains support for them a SSRC will be generated appropriately Co-authored-by: aggresss <aggresss@163.com> Co-authored-by: Kevin Wang <kevmo314@gmail.com> Resolves #1989 Resolves #1675
This commit is contained in:
2
go.mod
2
go.mod
@@ -6,7 +6,7 @@ require (
|
|||||||
github.com/pion/datachannel v1.5.9
|
github.com/pion/datachannel v1.5.9
|
||||||
github.com/pion/dtls/v3 v3.0.2
|
github.com/pion/dtls/v3 v3.0.2
|
||||||
github.com/pion/ice/v4 v4.0.1
|
github.com/pion/ice/v4 v4.0.1
|
||||||
github.com/pion/interceptor v0.1.31
|
github.com/pion/interceptor v0.1.32
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
github.com/pion/randutil v0.1.0
|
github.com/pion/randutil v0.1.0
|
||||||
github.com/pion/rtcp v1.2.14
|
github.com/pion/rtcp v1.2.14
|
||||||
|
4
go.sum
4
go.sum
@@ -41,8 +41,8 @@ github.com/pion/dtls/v3 v3.0.2 h1:425DEeJ/jfuTTghhUDW0GtYZYIwwMtnKKJNMcWccTX0=
|
|||||||
github.com/pion/dtls/v3 v3.0.2/go.mod h1:dfIXcFkKoujDQ+jtd8M6RgqKK3DuaUilm3YatAbGp5k=
|
github.com/pion/dtls/v3 v3.0.2/go.mod h1:dfIXcFkKoujDQ+jtd8M6RgqKK3DuaUilm3YatAbGp5k=
|
||||||
github.com/pion/ice/v4 v4.0.1 h1:2d3tPoTR90F3TcGYeXUwucGlXI3hds96cwv4kjZmb9s=
|
github.com/pion/ice/v4 v4.0.1 h1:2d3tPoTR90F3TcGYeXUwucGlXI3hds96cwv4kjZmb9s=
|
||||||
github.com/pion/ice/v4 v4.0.1/go.mod h1:2dpakjpd7+74L5j3TAe6gvkbI5UIzOgAnkimm9SuHvA=
|
github.com/pion/ice/v4 v4.0.1/go.mod h1:2dpakjpd7+74L5j3TAe6gvkbI5UIzOgAnkimm9SuHvA=
|
||||||
github.com/pion/interceptor v0.1.31 h1:9enhHjP1fDfMI8sqvpO5c/9QuTQnCf2dzPHwwIH4x5w=
|
github.com/pion/interceptor v0.1.32 h1:DYbusOBhWKjPMiA5ifyczW03Tnh12gCaYn4VOvLMGk4=
|
||||||
github.com/pion/interceptor v0.1.31/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
github.com/pion/interceptor v0.1.32/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
||||||
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/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||||
|
@@ -159,7 +159,7 @@ func (i *interceptorToTrackLocalWriter) Write(b []byte) (int, error) {
|
|||||||
return i.WriteRTP(&packet.Header, packet.Payload)
|
return i.WriteRTP(&packet.Header, packet.Payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStreamInfo(id string, ssrc SSRC, payloadType PayloadType, codec RTPCodecCapability, webrtcHeaderExtensions []RTPHeaderExtensionParameter) *interceptor.StreamInfo {
|
func createStreamInfo(id string, ssrc, ssrcFEC, ssrcRTX SSRC, payloadType PayloadType, codec RTPCodecCapability, webrtcHeaderExtensions []RTPHeaderExtensionParameter) *interceptor.StreamInfo {
|
||||||
headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(webrtcHeaderExtensions))
|
headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(webrtcHeaderExtensions))
|
||||||
for _, h := range webrtcHeaderExtensions {
|
for _, h := range webrtcHeaderExtensions {
|
||||||
headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI})
|
headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI})
|
||||||
@@ -171,15 +171,17 @@ func createStreamInfo(id string, ssrc SSRC, payloadType PayloadType, codec RTPCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &interceptor.StreamInfo{
|
return &interceptor.StreamInfo{
|
||||||
ID: id,
|
ID: id,
|
||||||
Attributes: interceptor.Attributes{},
|
Attributes: interceptor.Attributes{},
|
||||||
SSRC: uint32(ssrc),
|
SSRC: uint32(ssrc),
|
||||||
PayloadType: uint8(payloadType),
|
SSRCRetransmission: uint32(ssrcRTX),
|
||||||
RTPHeaderExtensions: headerExtensions,
|
SSRCForwardErrorCorrection: uint32(ssrcFEC),
|
||||||
MimeType: codec.MimeType,
|
PayloadType: uint8(payloadType),
|
||||||
ClockRate: codec.ClockRate,
|
RTPHeaderExtensions: headerExtensions,
|
||||||
Channels: codec.Channels,
|
MimeType: codec.MimeType,
|
||||||
SDPFmtpLine: codec.SDPFmtpLine,
|
ClockRate: codec.ClockRate,
|
||||||
RTCPFeedback: feedbacks,
|
Channels: codec.Channels,
|
||||||
|
SDPFmtpLine: codec.SDPFmtpLine,
|
||||||
|
RTCPFeedback: feedbacks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -196,10 +196,10 @@ func Test_Interceptor_BindUnbind(t *testing.T) {
|
|||||||
if cnt := atomic.LoadUint32(&cntUnbindLocalStream); cnt != 1 {
|
if cnt := atomic.LoadUint32(&cntUnbindLocalStream); cnt != 1 {
|
||||||
t.Errorf("UnbindLocalStreamFn is expected to be called once, but called %d times", cnt)
|
t.Errorf("UnbindLocalStreamFn is expected to be called once, but called %d times", cnt)
|
||||||
}
|
}
|
||||||
if cnt := atomic.LoadUint32(&cntBindRemoteStream); cnt != 1 {
|
if cnt := atomic.LoadUint32(&cntBindRemoteStream); cnt != 2 {
|
||||||
t.Errorf("BindRemoteStreamFn is expected to be called once, but called %d times", cnt)
|
t.Errorf("BindRemoteStreamFn is expected to be called once, but called %d times", cnt)
|
||||||
}
|
}
|
||||||
if cnt := atomic.LoadUint32(&cntUnbindRemoteStream); cnt != 1 {
|
if cnt := atomic.LoadUint32(&cntUnbindRemoteStream); cnt != 2 {
|
||||||
t.Errorf("UnbindRemoteStreamFn is expected to be called once, but called %d times", cnt)
|
t.Errorf("UnbindRemoteStreamFn is expected to be called once, but called %d times", cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ func Test_Interceptor_BindUnbind(t *testing.T) {
|
|||||||
if cnt := atomic.LoadUint32(&cntBindRTCPWriter); cnt != 2 {
|
if cnt := atomic.LoadUint32(&cntBindRTCPWriter); cnt != 2 {
|
||||||
t.Errorf("BindRTCPWriterFn is expected to be called twice, but called %d times", cnt)
|
t.Errorf("BindRTCPWriterFn is expected to be called twice, but called %d times", cnt)
|
||||||
}
|
}
|
||||||
if cnt := atomic.LoadUint32(&cntBindRTCPReader); cnt != 2 {
|
if cnt := atomic.LoadUint32(&cntBindRTCPReader); cnt != 3 {
|
||||||
t.Errorf("BindRTCPReaderFn is expected to be called twice, but called %d times", cnt)
|
t.Errorf("BindRTCPReaderFn is expected to be called twice, but called %d times", cnt)
|
||||||
}
|
}
|
||||||
if cnt := atomic.LoadUint32(&cntClose); cnt != 2 {
|
if cnt := atomic.LoadUint32(&cntClose); cnt != 2 {
|
||||||
|
@@ -47,6 +47,12 @@ const (
|
|||||||
// MimeTypePCMA PCMA MIME type
|
// MimeTypePCMA PCMA MIME type
|
||||||
// Note: Matching should be case insensitive.
|
// Note: Matching should be case insensitive.
|
||||||
MimeTypePCMA = "audio/PCMA"
|
MimeTypePCMA = "audio/PCMA"
|
||||||
|
// MimeTypeRTX RTX MIME type
|
||||||
|
// Note: Matching should be case insensitive.
|
||||||
|
MimeTypeRTX = "video/rtx"
|
||||||
|
// MimeTypeFlexFEC FEC MIME Type
|
||||||
|
// Note: Matching should be case insensitive.
|
||||||
|
MimeTypeFlexFEC = "video/flexfec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mediaEngineHeaderExtension struct {
|
type mediaEngineHeaderExtension struct {
|
||||||
@@ -106,7 +112,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
|
||||||
PayloadType: 97,
|
PayloadType: 97,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -115,7 +121,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 102,
|
PayloadType: 102,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=102", nil},
|
||||||
PayloadType: 103,
|
PayloadType: 103,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -124,7 +130,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 104,
|
PayloadType: 104,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=104", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=104", nil},
|
||||||
PayloadType: 105,
|
PayloadType: 105,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -133,7 +139,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 106,
|
PayloadType: 106,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=106", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=106", nil},
|
||||||
PayloadType: 107,
|
PayloadType: 107,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -142,7 +148,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 108,
|
PayloadType: 108,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=108", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=108", nil},
|
||||||
PayloadType: 109,
|
PayloadType: 109,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -151,7 +157,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 127,
|
PayloadType: 127,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=127", nil},
|
||||||
PayloadType: 125,
|
PayloadType: 125,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -160,7 +166,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 39,
|
PayloadType: 39,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=39", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=39", nil},
|
||||||
PayloadType: 40,
|
PayloadType: 40,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -169,7 +175,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 45,
|
PayloadType: 45,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=45", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=45", nil},
|
||||||
PayloadType: 46,
|
PayloadType: 46,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -178,7 +184,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 98,
|
PayloadType: 98,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=98", nil},
|
||||||
PayloadType: 99,
|
PayloadType: 99,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -187,7 +193,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 100,
|
PayloadType: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=100", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=100", nil},
|
||||||
PayloadType: 101,
|
PayloadType: 101,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -196,7 +202,7 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
|
|||||||
PayloadType: 112,
|
PayloadType: 112,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=112", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=112", nil},
|
||||||
PayloadType: 113,
|
PayloadType: 113,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
@@ -702,3 +708,23 @@ func payloaderForCodec(codec RTPCodecCapability) (rtp.Payloader, error) {
|
|||||||
return nil, ErrNoPayloaderForCodec
|
return nil, ErrNoPayloaderForCodec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MediaEngine) isRTXEnabled(typ RTPCodecType, directions []RTPTransceiverDirection) bool {
|
||||||
|
for _, p := range m.getRTPParametersByKind(typ, directions).Codecs {
|
||||||
|
if p.MimeType == MimeTypeRTX {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MediaEngine) isFECEnabled(typ RTPCodecType, directions []RTPTransceiverDirection) bool {
|
||||||
|
for _, p := range m.getRTPParametersByKind(typ, directions).Codecs {
|
||||||
|
if strings.Contains(p.MimeType, MimeTypeFlexFEC) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@@ -364,7 +364,7 @@ a=fmtp:97 apt=96
|
|||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
|
||||||
PayloadType: 97,
|
PayloadType: 97,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
@@ -372,7 +372,7 @@ a=fmtp:97 apt=96
|
|||||||
PayloadType: 102,
|
PayloadType: 102,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=102", nil},
|
||||||
PayloadType: 103,
|
PayloadType: 103,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
@@ -380,7 +380,7 @@ a=fmtp:97 apt=96
|
|||||||
PayloadType: 104,
|
PayloadType: 104,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=104", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=104", nil},
|
||||||
PayloadType: 105,
|
PayloadType: 105,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
@@ -388,7 +388,7 @@ a=fmtp:97 apt=96
|
|||||||
PayloadType: 98,
|
PayloadType: 98,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=98", nil},
|
||||||
PayloadType: 99,
|
PayloadType: 99,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
|
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
|
||||||
@@ -400,7 +400,7 @@ a=fmtp:97 apt=96
|
|||||||
assert.Equal(t, vp9Codec.MimeType, MimeTypeVP9)
|
assert.Equal(t, vp9Codec.MimeType, MimeTypeVP9)
|
||||||
vp9RTX, _, err := m.getCodecByPayload(97)
|
vp9RTX, _, err := m.getCodecByPayload(97)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, vp9RTX.MimeType, "video/rtx")
|
assert.Equal(t, vp9RTX.MimeType, MimeTypeRTX)
|
||||||
|
|
||||||
h264P1Codec, _, err := m.getCodecByPayload(106)
|
h264P1Codec, _, err := m.getCodecByPayload(106)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -408,7 +408,7 @@ a=fmtp:97 apt=96
|
|||||||
assert.Equal(t, h264P1Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f")
|
assert.Equal(t, h264P1Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f")
|
||||||
h264P1RTX, _, err := m.getCodecByPayload(107)
|
h264P1RTX, _, err := m.getCodecByPayload(107)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, h264P1RTX.MimeType, "video/rtx")
|
assert.Equal(t, h264P1RTX.MimeType, MimeTypeRTX)
|
||||||
assert.Equal(t, h264P1RTX.SDPFmtpLine, "apt=106")
|
assert.Equal(t, h264P1RTX.SDPFmtpLine, "apt=106")
|
||||||
|
|
||||||
h264P0Codec, _, err := m.getCodecByPayload(108)
|
h264P0Codec, _, err := m.getCodecByPayload(108)
|
||||||
@@ -417,7 +417,7 @@ a=fmtp:97 apt=96
|
|||||||
assert.Equal(t, h264P0Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f")
|
assert.Equal(t, h264P0Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f")
|
||||||
h264P0RTX, _, err := m.getCodecByPayload(109)
|
h264P0RTX, _, err := m.getCodecByPayload(109)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, h264P0RTX.MimeType, "video/rtx")
|
assert.Equal(t, h264P0RTX.MimeType, MimeTypeRTX)
|
||||||
assert.Equal(t, h264P0RTX.SDPFmtpLine, "apt=108")
|
assert.Equal(t, h264P0RTX.SDPFmtpLine, "apt=108")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -443,7 +443,7 @@ a=fmtp:97 apt=96
|
|||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
|
||||||
PayloadType: 97,
|
PayloadType: 97,
|
||||||
}, RTPCodecTypeVideo))
|
}, RTPCodecTypeVideo))
|
||||||
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
|
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
|
||||||
|
@@ -1048,6 +1048,11 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable RTX/FEC on RTPSenders if the remote didn't support it
|
||||||
|
for _, sender := range pc.GetSenders() {
|
||||||
|
sender.configureRTXAndFEC()
|
||||||
|
}
|
||||||
|
|
||||||
var t *RTPTransceiver
|
var t *RTPTransceiver
|
||||||
localTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...)
|
localTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...)
|
||||||
detectedPlanB := descriptionIsPlanB(pc.RemoteDescription(), pc.log)
|
detectedPlanB := descriptionIsPlanB(pc.RemoteDescription(), pc.log)
|
||||||
@@ -1616,7 +1621,7 @@ func (pc *PeerConnection) handleIncomingSSRC(rtpStream io.Reader, ssrc SSRC) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
streamInfo := createStreamInfo("", ssrc, params.Codecs[0].PayloadType, params.Codecs[0].RTPCodecCapability, params.HeaderExtensions)
|
streamInfo := createStreamInfo("", ssrc, 0, 0, params.Codecs[0].PayloadType, params.Codecs[0].RTPCodecCapability, params.HeaderExtensions)
|
||||||
readStream, interceptor, rtcpReadStream, rtcpInterceptor, err := pc.dtlsTransport.streamsForSSRC(ssrc, *streamInfo)
|
readStream, interceptor, rtcpReadStream, rtcpInterceptor, err := pc.dtlsTransport.streamsForSSRC(ssrc, *streamInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -21,7 +21,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/logging"
|
"github.com/pion/logging"
|
||||||
"github.com/pion/randutil"
|
|
||||||
"github.com/pion/rtcp"
|
"github.com/pion/rtcp"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/sdp/v3"
|
"github.com/pion/sdp/v3"
|
||||||
@@ -778,7 +777,7 @@ func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) {
|
|||||||
func TestPlanBMediaExchange(t *testing.T) {
|
func TestPlanBMediaExchange(t *testing.T) {
|
||||||
runTest := func(trackCount int, t *testing.T) {
|
runTest := func(trackCount int, t *testing.T) {
|
||||||
addSingleTrack := func(p *PeerConnection) *TrackLocalStaticSample {
|
addSingleTrack := func(p *PeerConnection) *TrackLocalStaticSample {
|
||||||
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()), fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()))
|
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, fmt.Sprintf("video-%d", util.RandUint32()), fmt.Sprintf("video-%d", util.RandUint32()))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = p.AddTrack(track)
|
_, err = p.AddTrack(track)
|
||||||
@@ -1020,7 +1019,7 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) {
|
|||||||
if len(track.bindings) == 1 {
|
if len(track.bindings) == 1 {
|
||||||
_, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{
|
_, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
SSRC: randutil.NewMathRandomGenerator().Uint32(),
|
SSRC: util.RandUint32(),
|
||||||
}, []byte{0, 1, 2, 3, 4, 5})
|
}, []byte{0, 1, 2, 3, 4, 5})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
@@ -10,9 +10,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/pion/randutil"
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/rtp/codecs"
|
"github.com/pion/rtp/codecs"
|
||||||
|
"github.com/pion/webrtc/v4/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -68,7 +68,7 @@ func NewWith(out io.Writer, sampleRate uint32, channelCount uint16) (*OggWriter,
|
|||||||
stream: out,
|
stream: out,
|
||||||
sampleRate: sampleRate,
|
sampleRate: sampleRate,
|
||||||
channelCount: channelCount,
|
channelCount: channelCount,
|
||||||
serial: randutil.NewMathRandomGenerator().Uint32(),
|
serial: util.RandUint32(),
|
||||||
checksumTable: generateChecksumTable(),
|
checksumTable: generateChecksumTable(),
|
||||||
|
|
||||||
// Timestamp and Granule MUST start from 1
|
// Timestamp and Granule MUST start from 1
|
||||||
|
13
rtpcodec.go
13
rtpcodec.go
@@ -4,6 +4,7 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v4/internal/fmtp"
|
"github.com/pion/webrtc/v4/internal/fmtp"
|
||||||
@@ -123,3 +124,15 @@ func codecParametersFuzzySearch(needle RTPCodecParameters, haystack []RTPCodecPa
|
|||||||
|
|
||||||
return RTPCodecParameters{}, codecMatchNone
|
return RTPCodecParameters{}, codecMatchNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given a CodecParameters find the RTX CodecParameters if one exists
|
||||||
|
func findRTXCodecParameters(needle PayloadType, haystack []RTPCodecParameters) (RTPCodecParameters, bool) {
|
||||||
|
aptStr := fmt.Sprintf("apt=%d", needle)
|
||||||
|
for _, c := range haystack {
|
||||||
|
if aptStr == c.SDPFmtpLine {
|
||||||
|
return c, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RTPCodecParameters{}, false
|
||||||
|
}
|
||||||
|
@@ -9,6 +9,12 @@ type RTPRtxParameters struct {
|
|||||||
SSRC SSRC `json:"ssrc"`
|
SSRC SSRC `json:"ssrc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RTPFecParameters dictionary contains information relating to forward error correction (FEC) settings.
|
||||||
|
// https://draft.ortc.org/#dom-rtcrtpfecparameters
|
||||||
|
type RTPFecParameters struct {
|
||||||
|
SSRC SSRC `json:"ssrc"`
|
||||||
|
}
|
||||||
|
|
||||||
// RTPCodingParameters provides information relating to both encoding and decoding.
|
// RTPCodingParameters provides information relating to both encoding and decoding.
|
||||||
// This is a subset of the RFC since Pion WebRTC doesn't implement encoding/decoding itself
|
// This is a subset of the RFC since Pion WebRTC doesn't implement encoding/decoding itself
|
||||||
// http://draft.ortc.org/#dom-rtcrtpcodingparameters
|
// http://draft.ortc.org/#dom-rtcrtpcodingparameters
|
||||||
@@ -17,4 +23,5 @@ type RTPCodingParameters struct {
|
|||||||
SSRC SSRC `json:"ssrc"`
|
SSRC SSRC `json:"ssrc"`
|
||||||
PayloadType PayloadType `json:"payloadType"`
|
PayloadType PayloadType `json:"payloadType"`
|
||||||
RTX RTPRtxParameters `json:"rtx"`
|
RTX RTPRtxParameters `json:"rtx"`
|
||||||
|
FEC RTPFecParameters `json:"fec"`
|
||||||
}
|
}
|
||||||
|
@@ -210,14 +210,14 @@ func (r *RTPReceiver) startReceive(parameters RTPReceiveParameters) error {
|
|||||||
return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, parameters.Encodings[i].SSRC)
|
return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, parameters.Encodings[i].SSRC)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.streamInfo = createStreamInfo("", parameters.Encodings[i].SSRC, 0, codec, globalParams.HeaderExtensions)
|
t.streamInfo = createStreamInfo("", parameters.Encodings[i].SSRC, 0, 0, 0, codec, globalParams.HeaderExtensions)
|
||||||
var err error
|
var err error
|
||||||
if t.rtpReadStream, t.rtpInterceptor, t.rtcpReadStream, t.rtcpInterceptor, err = r.transport.streamsForSSRC(parameters.Encodings[i].SSRC, *t.streamInfo); err != nil {
|
if t.rtpReadStream, t.rtpInterceptor, t.rtcpReadStream, t.rtcpInterceptor, err = r.transport.streamsForSSRC(parameters.Encodings[i].SSRC, *t.streamInfo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if rtxSsrc := parameters.Encodings[i].RTX.SSRC; rtxSsrc != 0 {
|
if rtxSsrc := parameters.Encodings[i].RTX.SSRC; rtxSsrc != 0 {
|
||||||
streamInfo := createStreamInfo("", rtxSsrc, 0, codec, globalParams.HeaderExtensions)
|
streamInfo := createStreamInfo("", rtxSsrc, 0, 0, 0, codec, globalParams.HeaderExtensions)
|
||||||
rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor, err := r.transport.streamsForSSRC(rtxSsrc, *streamInfo)
|
rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor, err := r.transport.streamsForSSRC(rtxSsrc, *streamInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -7,18 +7,13 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/randutil"
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/sdp/v3"
|
"github.com/pion/sdp/v3"
|
||||||
"github.com/pion/transport/v3/test"
|
"github.com/pion/transport/v3/test"
|
||||||
@@ -86,19 +81,18 @@ func TestSetRTPParameters(t *testing.T) {
|
|||||||
func Test_RTX_Read(t *testing.T) {
|
func Test_RTX_Read(t *testing.T) {
|
||||||
defer test.TimeOut(time.Second * 30).Stop()
|
defer test.TimeOut(time.Second * 30).Stop()
|
||||||
|
|
||||||
var ssrc *uint32
|
|
||||||
ssrcLines := ""
|
|
||||||
rtxSsrc := randutil.NewMathRandomGenerator().Uint32()
|
|
||||||
|
|
||||||
pcOffer, pcAnswer, err := newPair()
|
pcOffer, pcAnswer, err := newPair()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "track-id", "stream-id")
|
track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "track-id", "stream-id")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = pcOffer.AddTrack(track)
|
rtpSender, err := pcOffer.AddTrack(track)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
rtxSsrc := rtpSender.GetParameters().Encodings[0].RTX.SSRC
|
||||||
|
ssrc := rtpSender.GetParameters().Encodings[0].SSRC
|
||||||
|
|
||||||
rtxRead, rtxReadCancel := context.WithCancel(context.Background())
|
rtxRead, rtxReadCancel := context.WithCancel(context.Background())
|
||||||
pcAnswer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) {
|
pcAnswer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) {
|
||||||
for {
|
for {
|
||||||
@@ -111,7 +105,7 @@ func Test_RTX_Read(t *testing.T) {
|
|||||||
|
|
||||||
assert.NoError(t, readRTPErr)
|
assert.NoError(t, readRTPErr)
|
||||||
assert.NotNil(t, pkt)
|
assert.NotNil(t, pkt)
|
||||||
assert.Equal(t, pkt.SSRC, *ssrc)
|
assert.Equal(t, pkt.SSRC, uint32(ssrc))
|
||||||
assert.Equal(t, pkt.PayloadType, uint8(96))
|
assert.Equal(t, pkt.PayloadType, uint8(96))
|
||||||
assert.Equal(t, pkt.Payload, []byte{0xB, 0xA, 0xD})
|
assert.Equal(t, pkt.Payload, []byte{0xB, 0xA, 0xD})
|
||||||
|
|
||||||
@@ -120,7 +114,7 @@ func Test_RTX_Read(t *testing.T) {
|
|||||||
rtxSSRC := attributes.Get(AttributeRtxSsrc)
|
rtxSSRC := attributes.Get(AttributeRtxSsrc)
|
||||||
if rtxPayloadType != nil && rtxSequenceNumber != nil && rtxSSRC != nil {
|
if rtxPayloadType != nil && rtxSequenceNumber != nil && rtxSSRC != nil {
|
||||||
assert.Equal(t, rtxPayloadType, uint8(97))
|
assert.Equal(t, rtxPayloadType, uint8(97))
|
||||||
assert.Equal(t, rtxSSRC, rtxSsrc)
|
assert.Equal(t, rtxSSRC, uint32(rtxSsrc))
|
||||||
assert.Equal(t, rtxSequenceNumber, pkt.SequenceNumber+500)
|
assert.Equal(t, rtxSequenceNumber, pkt.SequenceNumber+500)
|
||||||
|
|
||||||
rtxReadCancel()
|
rtxReadCancel()
|
||||||
@@ -128,42 +122,14 @@ func Test_RTX_Read(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(offer string) (modified string) {
|
assert.NoError(t, signalPair(pcOffer, pcAnswer))
|
||||||
scanner := bufio.NewScanner(strings.NewReader(offer))
|
|
||||||
for scanner.Scan() {
|
|
||||||
l := scanner.Text()
|
|
||||||
|
|
||||||
if strings.HasPrefix(l, "a=ssrc") {
|
|
||||||
if ssrc == nil {
|
|
||||||
lineSplit := strings.Split(l, " ")[0]
|
|
||||||
parsed, atoiErr := strconv.ParseUint(strings.TrimPrefix(lineSplit, "a=ssrc:"), 10, 32)
|
|
||||||
assert.NoError(t, atoiErr)
|
|
||||||
|
|
||||||
parsedSsrc := uint32(parsed)
|
|
||||||
ssrc = &parsedSsrc
|
|
||||||
|
|
||||||
modified += fmt.Sprintf("a=ssrc-group:FID %d %d\r\n", *ssrc, rtxSsrc)
|
|
||||||
}
|
|
||||||
|
|
||||||
ssrcLines += l + "\n"
|
|
||||||
} else if ssrcLines != "" {
|
|
||||||
ssrcLines = strings.ReplaceAll(ssrcLines, fmt.Sprintf("%d", *ssrc), fmt.Sprintf("%d", rtxSsrc))
|
|
||||||
modified += ssrcLines
|
|
||||||
ssrcLines = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
modified += l + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
return modified
|
|
||||||
}))
|
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
for i := uint16(0); ; i++ {
|
for i := uint16(0); ; i++ {
|
||||||
pkt := rtp.Packet{
|
pkt := rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
SSRC: *ssrc,
|
SSRC: uint32(ssrc),
|
||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
SequenceNumber: i,
|
SequenceNumber: i,
|
||||||
},
|
},
|
||||||
@@ -182,7 +148,7 @@ func Test_RTX_Read(t *testing.T) {
|
|||||||
// Send the RTX
|
// Send the RTX
|
||||||
_, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{
|
_, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
SSRC: rtxSsrc,
|
SSRC: uint32(rtxSsrc),
|
||||||
PayloadType: 97,
|
PayloadType: 97,
|
||||||
SequenceNumber: i + 500,
|
SequenceNumber: i + 500,
|
||||||
}, rtxPayload)
|
}, rtxPayload)
|
||||||
|
51
rtpsender.go
51
rtpsender.go
@@ -29,7 +29,7 @@ type trackEncoding struct {
|
|||||||
|
|
||||||
context *baseTrackLocalContext
|
context *baseTrackLocalContext
|
||||||
|
|
||||||
ssrc SSRC
|
ssrc, ssrcRTX, ssrcFEC SSRC
|
||||||
}
|
}
|
||||||
|
|
||||||
// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
|
// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
|
||||||
@@ -110,7 +110,9 @@ func (r *RTPSender) Transport() *DTLSTransport {
|
|||||||
return r.transport
|
return r.transport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RTPSender) getParameters() RTPSendParameters {
|
// GetParameters describes the current configuration for the encoding and
|
||||||
|
// transmission of media on the sender's track.
|
||||||
|
func (r *RTPSender) GetParameters() RTPSendParameters {
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
@@ -124,6 +126,8 @@ func (r *RTPSender) getParameters() RTPSendParameters {
|
|||||||
RTPCodingParameters: RTPCodingParameters{
|
RTPCodingParameters: RTPCodingParameters{
|
||||||
RID: rid,
|
RID: rid,
|
||||||
SSRC: trackEncoding.ssrc,
|
SSRC: trackEncoding.ssrc,
|
||||||
|
RTX: RTPRtxParameters{SSRC: trackEncoding.ssrcRTX},
|
||||||
|
FEC: RTPFecParameters{SSRC: trackEncoding.ssrcFEC},
|
||||||
PayloadType: r.payloadType,
|
PayloadType: r.payloadType,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -143,14 +147,6 @@ func (r *RTPSender) getParameters() RTPSendParameters {
|
|||||||
return sendParameters
|
return sendParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetParameters describes the current configuration for the encoding and
|
|
||||||
// transmission of media on the sender's track.
|
|
||||||
func (r *RTPSender) GetParameters() RTPSendParameters {
|
|
||||||
r.mu.RLock()
|
|
||||||
defer r.mu.RUnlock()
|
|
||||||
return r.getParameters()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddEncoding adds an encoding to RTPSender. Used by simulcast senders.
|
// AddEncoding adds an encoding to RTPSender. Used by simulcast senders.
|
||||||
func (r *RTPSender) AddEncoding(track TrackLocal) error {
|
func (r *RTPSender) AddEncoding(track TrackLocal) error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
@@ -201,7 +197,15 @@ func (r *RTPSender) AddEncoding(track TrackLocal) error {
|
|||||||
func (r *RTPSender) addEncoding(track TrackLocal) {
|
func (r *RTPSender) addEncoding(track TrackLocal) {
|
||||||
trackEncoding := &trackEncoding{
|
trackEncoding := &trackEncoding{
|
||||||
track: track,
|
track: track,
|
||||||
ssrc: SSRC(randutil.NewMathRandomGenerator().Uint32()),
|
ssrc: SSRC(util.RandUint32()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.api.mediaEngine.isRTXEnabled(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
|
||||||
|
trackEncoding.ssrcRTX = SSRC(util.RandUint32())
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.api.mediaEngine.isFECEnabled(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
|
||||||
|
trackEncoding.ssrcFEC = SSRC(util.RandUint32())
|
||||||
}
|
}
|
||||||
|
|
||||||
r.trackEncodings = append(r.trackEncodings, trackEncoding)
|
r.trackEncodings = append(r.trackEncodings, trackEncoding)
|
||||||
@@ -261,6 +265,8 @@ func (r *RTPSender) ReplaceTrack(track TrackLocal) error {
|
|||||||
id: context.ID(),
|
id: context.ID(),
|
||||||
params: r.api.mediaEngine.getRTPParametersByKind(track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
params: r.api.mediaEngine.getRTPParametersByKind(track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
||||||
ssrc: context.SSRC(),
|
ssrc: context.SSRC(),
|
||||||
|
ssrcRTX: context.SSRCRetransmission(),
|
||||||
|
ssrcFEC: context.SSRCForwardErrorCorrection(),
|
||||||
writeStream: context.WriteStream(),
|
writeStream: context.WriteStream(),
|
||||||
rtcpInterceptor: context.RTCPReader(),
|
rtcpInterceptor: context.RTCPReader(),
|
||||||
})
|
})
|
||||||
@@ -302,10 +308,14 @@ func (r *RTPSender) Send(parameters RTPSendParameters) error {
|
|||||||
|
|
||||||
trackEncoding.srtpStream = srtpStream
|
trackEncoding.srtpStream = srtpStream
|
||||||
trackEncoding.ssrc = parameters.Encodings[idx].SSRC
|
trackEncoding.ssrc = parameters.Encodings[idx].SSRC
|
||||||
|
trackEncoding.ssrcRTX = parameters.Encodings[idx].RTX.SSRC
|
||||||
|
trackEncoding.ssrcFEC = parameters.Encodings[idx].FEC.SSRC
|
||||||
trackEncoding.context = &baseTrackLocalContext{
|
trackEncoding.context = &baseTrackLocalContext{
|
||||||
id: r.id,
|
id: r.id,
|
||||||
params: r.api.mediaEngine.getRTPParametersByKind(trackEncoding.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
params: r.api.mediaEngine.getRTPParametersByKind(trackEncoding.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
||||||
ssrc: parameters.Encodings[idx].SSRC,
|
ssrc: parameters.Encodings[idx].SSRC,
|
||||||
|
ssrcFEC: parameters.Encodings[idx].FEC.SSRC,
|
||||||
|
ssrcRTX: parameters.Encodings[idx].RTX.SSRC,
|
||||||
writeStream: writeStream,
|
writeStream: writeStream,
|
||||||
rtcpInterceptor: trackEncoding.rtcpInterceptor,
|
rtcpInterceptor: trackEncoding.rtcpInterceptor,
|
||||||
}
|
}
|
||||||
@@ -319,6 +329,8 @@ func (r *RTPSender) Send(parameters RTPSendParameters) error {
|
|||||||
trackEncoding.streamInfo = *createStreamInfo(
|
trackEncoding.streamInfo = *createStreamInfo(
|
||||||
r.id,
|
r.id,
|
||||||
parameters.Encodings[idx].SSRC,
|
parameters.Encodings[idx].SSRC,
|
||||||
|
parameters.Encodings[idx].RTX.SSRC,
|
||||||
|
parameters.Encodings[idx].FEC.SSRC,
|
||||||
codec.PayloadType,
|
codec.PayloadType,
|
||||||
codec.RTPCodecCapability,
|
codec.RTPCodecCapability,
|
||||||
parameters.HeaderExtensions,
|
parameters.HeaderExtensions,
|
||||||
@@ -467,3 +479,20 @@ func (r *RTPSender) hasStopped() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set a SSRC for FEC and RTX if MediaEngine has them enabled
|
||||||
|
// If the remote doesn't support FEC or RTX we disable locally
|
||||||
|
func (r *RTPSender) configureRTXAndFEC() {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, trackEncoding := range r.trackEncodings {
|
||||||
|
if !r.api.mediaEngine.isRTXEnabled(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
|
||||||
|
trackEncoding.ssrcRTX = SSRC(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.api.mediaEngine.isFECEnabled(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
|
||||||
|
trackEncoding.ssrcFEC = SSRC(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -401,3 +401,85 @@ func Test_RTPSender_Add_Encoding(t *testing.T) {
|
|||||||
|
|
||||||
assert.NoError(t, peerConnection.Close())
|
assert.NoError(t, peerConnection.Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint: dupl
|
||||||
|
func Test_RTPSender_FEC_Support(t *testing.T) {
|
||||||
|
t.Run("FEC disabled by default", func(t *testing.T) {
|
||||||
|
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
peerConnection, err := NewPeerConnection(Configuration{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
rtpSender, err := peerConnection.AddTrack(track)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Zero(t, rtpSender.GetParameters().Encodings[0].FEC.SSRC)
|
||||||
|
assert.NoError(t, peerConnection.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FEC can be enabled", func(t *testing.T) {
|
||||||
|
m := MediaEngine{}
|
||||||
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
|
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
|
||||||
|
PayloadType: 94,
|
||||||
|
}, RTPCodecTypeVideo))
|
||||||
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
|
RTPCodecCapability: RTPCodecCapability{MimeTypeFlexFEC, 90000, 0, "", nil},
|
||||||
|
PayloadType: 95,
|
||||||
|
}, RTPCodecTypeVideo))
|
||||||
|
|
||||||
|
api := NewAPI(WithMediaEngine(&m))
|
||||||
|
|
||||||
|
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
peerConnection, err := api.NewPeerConnection(Configuration{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
rtpSender, err := peerConnection.AddTrack(track)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotZero(t, rtpSender.GetParameters().Encodings[0].FEC.SSRC)
|
||||||
|
assert.NoError(t, peerConnection.Close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: dupl
|
||||||
|
func Test_RTPSender_RTX_Support(t *testing.T) {
|
||||||
|
t.Run("RTX SSRC by Default", func(t *testing.T) {
|
||||||
|
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
peerConnection, err := NewPeerConnection(Configuration{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
rtpSender, err := peerConnection.AddTrack(track)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotZero(t, rtpSender.GetParameters().Encodings[0].RTX.SSRC)
|
||||||
|
assert.NoError(t, peerConnection.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RTX can be disabled", func(t *testing.T) {
|
||||||
|
m := MediaEngine{}
|
||||||
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
|
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
|
||||||
|
PayloadType: 94,
|
||||||
|
}, RTPCodecTypeVideo))
|
||||||
|
api := NewAPI(WithMediaEngine(&m))
|
||||||
|
|
||||||
|
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
peerConnection, err := api.NewPeerConnection(Configuration{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
rtpSender, err := peerConnection.AddTrack(track)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Zero(t, rtpSender.GetParameters().Encodings[0].RTX.SSRC)
|
||||||
|
|
||||||
|
assert.NoError(t, peerConnection.Close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -60,7 +60,7 @@ func Test_RTPTransceiver_SetCodecPreferences(t *testing.T) {
|
|||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
|
||||||
PayloadType: 97,
|
PayloadType: 97,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ func Test_RTPTransceiver_SetCodecPreferences(t *testing.T) {
|
|||||||
PayloadType: 98,
|
PayloadType: 98,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil},
|
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=98", nil},
|
||||||
PayloadType: 99,
|
PayloadType: 99,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
9
sdp.go
9
sdp.go
@@ -392,7 +392,16 @@ func addSenderSDP(
|
|||||||
|
|
||||||
sendParameters := sender.GetParameters()
|
sendParameters := sender.GetParameters()
|
||||||
for _, encoding := range sendParameters.Encodings {
|
for _, encoding := range sendParameters.Encodings {
|
||||||
|
if encoding.RTX.SSRC != 0 {
|
||||||
|
media = media.WithValueAttribute("ssrc-group", fmt.Sprintf("FID %d %d", encoding.SSRC, encoding.RTX.SSRC))
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoding.FEC.SSRC != 0 {
|
||||||
|
media = media.WithValueAttribute("ssrc-group", fmt.Sprintf("FEC-FR %d %d", encoding.SSRC, encoding.FEC.SSRC))
|
||||||
|
}
|
||||||
|
|
||||||
media = media.WithMediaSource(uint32(encoding.SSRC), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
|
media = media.WithMediaSource(uint32(encoding.SSRC), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
|
||||||
|
|
||||||
if !isPlanB {
|
if !isPlanB {
|
||||||
media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
|
media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
|
||||||
}
|
}
|
||||||
|
164
sdp_test.go
164
sdp_test.go
@@ -14,6 +14,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pion/sdp/v3"
|
"github.com/pion/sdp/v3"
|
||||||
|
"github.com/pion/transport/v3/test"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -746,3 +747,166 @@ func TestRtpExtensionsFromMediaDescription(t *testing.T) {
|
|||||||
assert.Equal(t, extensions[sdp.ABSSendTimeURI], 1)
|
assert.Equal(t, extensions[sdp.ABSSendTimeURI], 1)
|
||||||
assert.Equal(t, extensions[sdp.SDESMidURI], 3)
|
assert.Equal(t, extensions[sdp.SDESMidURI], 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assert that FEC and RTX SSRCes are present if they are enabled in the MediaEngine
|
||||||
|
func Test_SSRC_Groups(t *testing.T) {
|
||||||
|
const offerWithRTX = `v=0
|
||||||
|
o=- 930222930247584370 1727933945 IN IP4 0.0.0.0
|
||||||
|
s=-
|
||||||
|
t=0 0
|
||||||
|
a=msid-semantic:WMS*
|
||||||
|
a=fingerprint:sha-256 11:3F:1C:8D:D4:1D:8D:E7:E1:3E:AF:38:06:0D:1D:40:22:DC:FE:C9:93:E4:80:D8:0B:17:9F:2E:C1:CA:C8:3D
|
||||||
|
a=extmap-allow-mixed
|
||||||
|
a=group:BUNDLE 0 1
|
||||||
|
m=audio 9 UDP/TLS/RTP/SAVPF 101
|
||||||
|
c=IN IP4 0.0.0.0
|
||||||
|
a=setup:actpass
|
||||||
|
a=mid:0
|
||||||
|
a=ice-ufrag:yIgpPUMarFReduuM
|
||||||
|
a=ice-pwd:VmnVaqCByWiOTatFoDBbMGhSFGlsxviz
|
||||||
|
a=rtcp-mux
|
||||||
|
a=rtcp-rsize
|
||||||
|
a=rtpmap:101 opus/90000
|
||||||
|
a=rtcp-fb:101 transport-cc
|
||||||
|
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||||
|
a=ssrc:3566446228 cname:stream-id
|
||||||
|
a=ssrc:3566446228 msid:stream-id audio-id
|
||||||
|
a=ssrc:3566446228 mslabel:stream-id
|
||||||
|
a=ssrc:3566446228 label:audio-id
|
||||||
|
a=msid:stream-id audio-id
|
||||||
|
a=sendrecv
|
||||||
|
m=video 9 UDP/TLS/RTP/SAVPF 96 97
|
||||||
|
c=IN IP4 0.0.0.0
|
||||||
|
a=setup:actpass
|
||||||
|
a=mid:1
|
||||||
|
a=ice-ufrag:yIgpPUMarFReduuM
|
||||||
|
a=ice-pwd:VmnVaqCByWiOTatFoDBbMGhSFGlsxviz
|
||||||
|
a=rtpmap:96 VP8/90000
|
||||||
|
a=rtcp-fb:96 nack
|
||||||
|
a=rtcp-fb:96 nack pli
|
||||||
|
a=rtcp-fb:96 transport-cc
|
||||||
|
a=rtpmap:97 rtx/90000
|
||||||
|
a=fmtp:97 apt=96
|
||||||
|
a=ssrc-group:FID 1701050765 2578535262
|
||||||
|
a=ssrc:1701050765 cname:stream-id
|
||||||
|
a=ssrc:1701050765 msid:stream-id track-id
|
||||||
|
a=ssrc:1701050765 mslabel:stream-id
|
||||||
|
a=ssrc:1701050765 label:track-id
|
||||||
|
a=msid:stream-id track-id
|
||||||
|
a=sendrecv
|
||||||
|
`
|
||||||
|
|
||||||
|
const offerNoRTX = `v=0
|
||||||
|
o=- 930222930247584370 1727933945 IN IP4 0.0.0.0
|
||||||
|
s=-
|
||||||
|
t=0 0
|
||||||
|
a=msid-semantic:WMS*
|
||||||
|
a=fingerprint:sha-256 11:3F:1C:8D:D4:1D:8D:E7:E1:3E:AF:38:06:0D:1D:40:22:DC:FE:C9:93:E4:80:D8:0B:17:9F:2E:C1:CA:C8:3D
|
||||||
|
a=extmap-allow-mixed
|
||||||
|
a=group:BUNDLE 0 1
|
||||||
|
m=audio 9 UDP/TLS/RTP/SAVPF 101
|
||||||
|
a=mid:0
|
||||||
|
a=ice-ufrag:yIgpPUMarFReduuM
|
||||||
|
a=ice-pwd:VmnVaqCByWiOTatFoDBbMGhSFGlsxviz
|
||||||
|
a=rtcp-mux
|
||||||
|
a=rtcp-rsize
|
||||||
|
a=rtpmap:101 opus/90000
|
||||||
|
a=rtcp-fb:101 transport-cc
|
||||||
|
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||||
|
a=ssrc:3566446228 cname:stream-id
|
||||||
|
a=ssrc:3566446228 msid:stream-id audio-id
|
||||||
|
a=ssrc:3566446228 mslabel:stream-id
|
||||||
|
a=ssrc:3566446228 label:audio-id
|
||||||
|
a=msid:stream-id audio-id
|
||||||
|
a=sendrecv
|
||||||
|
m=video 9 UDP/TLS/RTP/SAVPF 96
|
||||||
|
c=IN IP4 0.0.0.0
|
||||||
|
a=setup:actpass
|
||||||
|
a=mid:1
|
||||||
|
a=ice-ufrag:yIgpPUMarFReduuM
|
||||||
|
a=ice-pwd:VmnVaqCByWiOTatFoDBbMGhSFGlsxviz
|
||||||
|
a=rtpmap:96 VP8/90000
|
||||||
|
a=rtcp-fb:96 nack
|
||||||
|
a=rtcp-fb:96 nack pli
|
||||||
|
a=rtcp-fb:96 transport-cc
|
||||||
|
a=ssrc-group:FID 1701050765 2578535262
|
||||||
|
a=ssrc:1701050765 cname:stream-id
|
||||||
|
a=ssrc:1701050765 msid:stream-id track-id
|
||||||
|
a=ssrc:1701050765 mslabel:stream-id
|
||||||
|
a=ssrc:1701050765 label:track-id
|
||||||
|
a=msid:stream-id track-id
|
||||||
|
a=sendrecv
|
||||||
|
`
|
||||||
|
defer test.CheckRoutines(t)()
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
name string
|
||||||
|
enableRTXInMediaEngine bool
|
||||||
|
rtxExpected bool
|
||||||
|
remoteOffer string
|
||||||
|
}{
|
||||||
|
{"Offer", true, true, ""},
|
||||||
|
{"Offer no Local Groups", false, false, ""},
|
||||||
|
{"Answer", true, true, offerWithRTX},
|
||||||
|
{"Answer No Local Groups", false, false, offerWithRTX},
|
||||||
|
{"Answer No Remote Groups", true, false, offerNoRTX},
|
||||||
|
} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
checkRTXSupport := func(s *sdp.SessionDescription) {
|
||||||
|
// RTX is never enabled for audio
|
||||||
|
assert.Nil(t, trackDetailsFromSDP(nil, s)[0].repairSsrc)
|
||||||
|
|
||||||
|
// RTX is conditionally enabled for video
|
||||||
|
if testCase.rtxExpected {
|
||||||
|
assert.NotNil(t, trackDetailsFromSDP(nil, s)[1].repairSsrc)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, trackDetailsFromSDP(nil, s)[1].repairSsrc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &MediaEngine{}
|
||||||
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
|
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeOpus, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
|
||||||
|
PayloadType: 101,
|
||||||
|
}, RTPCodecTypeAudio))
|
||||||
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
|
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
|
||||||
|
PayloadType: 96,
|
||||||
|
}, RTPCodecTypeVideo))
|
||||||
|
if testCase.enableRTXInMediaEngine {
|
||||||
|
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
|
||||||
|
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeRTX, ClockRate: 90000, Channels: 0, SDPFmtpLine: "apt=96", RTCPFeedback: nil},
|
||||||
|
PayloadType: 97,
|
||||||
|
}, RTPCodecTypeVideo))
|
||||||
|
}
|
||||||
|
|
||||||
|
peerConnection, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
audioTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeOpus}, "audio-id", "stream-id")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = peerConnection.AddTrack(audioTrack)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
videoTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video-id", "stream-id")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = peerConnection.AddTrack(videoTrack)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if testCase.remoteOffer == "" {
|
||||||
|
offer, err := peerConnection.CreateOffer(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkRTXSupport(offer.parsed)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, peerConnection.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: testCase.remoteOffer}))
|
||||||
|
answer, err := peerConnection.CreateAnswer(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkRTXSupport(answer.parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, peerConnection.Close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -21,17 +21,22 @@ type TrackLocalWriter interface {
|
|||||||
// in Interceptors.
|
// in Interceptors.
|
||||||
type TrackLocalContext interface {
|
type TrackLocalContext interface {
|
||||||
// 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 PayloadTypes
|
||||||
CodecParameters() []RTPCodecParameters
|
CodecParameters() []RTPCodecParameters
|
||||||
|
|
||||||
// HeaderExtensions returns the negotiated RTPHeaderExtensionParameters. These are the header extensions supported by
|
// HeaderExtensions returns the negotiated RTPHeaderExtensionParameters. These are the header extensions supported by
|
||||||
// both PeerConnections and the SSRC/PayloadTypes
|
// both PeerConnections and the URI/IDs
|
||||||
HeaderExtensions() []RTPHeaderExtensionParameter
|
HeaderExtensions() []RTPHeaderExtensionParameter
|
||||||
|
|
||||||
// SSRC requires the negotiated SSRC of this track
|
// SSRC returns the negotiated SSRC of this track
|
||||||
// This track may have multiple if RTX is enabled
|
|
||||||
SSRC() SSRC
|
SSRC() SSRC
|
||||||
|
|
||||||
|
// SSRCRetransmission returns the negotiated SSRC used to send retransmissions for this track
|
||||||
|
SSRCRetransmission() SSRC
|
||||||
|
|
||||||
|
// SSRCForwardErrorCorrection returns the negotiated SSRC to send forward error correction for this track
|
||||||
|
SSRCForwardErrorCorrection() SSRC
|
||||||
|
|
||||||
// WriteStream returns the WriteStream for this TrackLocal. The implementer writes the outbound
|
// WriteStream returns the WriteStream for this TrackLocal. The implementer writes the outbound
|
||||||
// media packets to it
|
// media packets to it
|
||||||
WriteStream() TrackLocalWriter
|
WriteStream() TrackLocalWriter
|
||||||
@@ -44,11 +49,11 @@ type TrackLocalContext interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type baseTrackLocalContext struct {
|
type baseTrackLocalContext struct {
|
||||||
id string
|
id string
|
||||||
params RTPParameters
|
params RTPParameters
|
||||||
ssrc SSRC
|
ssrc, ssrcRTX, ssrcFEC SSRC
|
||||||
writeStream TrackLocalWriter
|
writeStream TrackLocalWriter
|
||||||
rtcpInterceptor interceptor.RTCPReader
|
rtcpInterceptor interceptor.RTCPReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both
|
// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both
|
||||||
@@ -64,11 +69,20 @@ func (t *baseTrackLocalContext) HeaderExtensions() []RTPHeaderExtensionParameter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SSRC requires the negotiated SSRC of this track
|
// SSRC requires the negotiated SSRC of this track
|
||||||
// This track may have multiple if RTX is enabled
|
|
||||||
func (t *baseTrackLocalContext) SSRC() SSRC {
|
func (t *baseTrackLocalContext) SSRC() SSRC {
|
||||||
return t.ssrc
|
return t.ssrc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSRCRetransmission returns the negotiated SSRC used to send retransmissions for this track
|
||||||
|
func (t *baseTrackLocalContext) SSRCRetransmission() SSRC {
|
||||||
|
return t.ssrcRTX
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSRCForwardErrorCorrection returns the negotiated SSRC to send forward error correction for this track
|
||||||
|
func (t *baseTrackLocalContext) SSRCForwardErrorCorrection() SSRC {
|
||||||
|
return t.ssrcFEC
|
||||||
|
}
|
||||||
|
|
||||||
// WriteStream returns the WriteStream for this TrackLocal. The implementer writes the outbound
|
// WriteStream returns the WriteStream for this TrackLocal. The implementer writes the outbound
|
||||||
// media packets to it
|
// media packets to it
|
||||||
func (t *baseTrackLocalContext) WriteStream() TrackLocalWriter {
|
func (t *baseTrackLocalContext) WriteStream() TrackLocalWriter {
|
||||||
|
@@ -19,10 +19,10 @@ import (
|
|||||||
// Bind can be called multiple times, this stores the
|
// Bind can be called multiple times, this stores the
|
||||||
// result for a single bind call so that it can be used when writing
|
// result for a single bind call so that it can be used when writing
|
||||||
type trackBinding struct {
|
type trackBinding struct {
|
||||||
id string
|
id string
|
||||||
ssrc SSRC
|
ssrc, ssrcRTX, ssrcFEC SSRC
|
||||||
payloadType PayloadType
|
payloadType, payloadTypeRTX PayloadType
|
||||||
writeStream TrackLocalWriter
|
writeStream TrackLocalWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets.
|
// TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets.
|
||||||
@@ -59,19 +59,28 @@ func WithRTPStreamID(rid string) func(*TrackLocalStaticRTP) {
|
|||||||
|
|
||||||
// Bind is called by the PeerConnection after negotiation is complete
|
// Bind is called by the PeerConnection after negotiation is complete
|
||||||
// This asserts that the code requested is supported by the remote peer.
|
// This asserts that the code requested is supported by the remote peer.
|
||||||
// If so it setups all the state (SSRC and PayloadType) to have a call
|
// If so it sets up all the state (SSRC and PayloadType) to have a call
|
||||||
func (s *TrackLocalStaticRTP) Bind(t TrackLocalContext) (RTPCodecParameters, error) {
|
func (s *TrackLocalStaticRTP) Bind(t TrackLocalContext) (RTPCodecParameters, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
parameters := RTPCodecParameters{RTPCodecCapability: s.codec}
|
parameters := RTPCodecParameters{RTPCodecCapability: s.codec}
|
||||||
if codec, matchType := codecParametersFuzzySearch(parameters, t.CodecParameters()); matchType != codecMatchNone {
|
if codec, matchType := codecParametersFuzzySearch(parameters, t.CodecParameters()); matchType != codecMatchNone {
|
||||||
|
var payloadTypeRTX PayloadType
|
||||||
|
if rtxParameters, ok := findRTXCodecParameters(codec.PayloadType, t.CodecParameters()); ok {
|
||||||
|
payloadTypeRTX = rtxParameters.PayloadType
|
||||||
|
}
|
||||||
|
|
||||||
s.bindings = append(s.bindings, trackBinding{
|
s.bindings = append(s.bindings, trackBinding{
|
||||||
ssrc: t.SSRC(),
|
ssrc: t.SSRC(),
|
||||||
payloadType: codec.PayloadType,
|
ssrcRTX: t.SSRCRetransmission(),
|
||||||
writeStream: t.WriteStream(),
|
ssrcFEC: t.SSRCForwardErrorCorrection(),
|
||||||
id: t.ID(),
|
payloadType: codec.PayloadType,
|
||||||
|
payloadTypeRTX: payloadTypeRTX,
|
||||||
|
writeStream: t.WriteStream(),
|
||||||
|
id: t.ID(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return codec, nil
|
return codec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -313,3 +313,26 @@ func Test_TrackLocalStatic_Padding(t *testing.T) {
|
|||||||
|
|
||||||
closePairNow(t, offerer, answerer)
|
closePairNow(t, offerer, answerer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_TrackLocalStatic_RTX(t *testing.T) {
|
||||||
|
defer test.TimeOut(time.Second * 30).Stop()
|
||||||
|
defer test.CheckRoutines(t)()
|
||||||
|
|
||||||
|
offerer, answerer, err := newPair()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = offerer.AddTrack(track)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, signalPair(offerer, answerer))
|
||||||
|
|
||||||
|
track.mu.Lock()
|
||||||
|
assert.NotZero(t, track.bindings[0].ssrcRTX)
|
||||||
|
assert.NotZero(t, track.bindings[0].payloadTypeRTX)
|
||||||
|
track.mu.Unlock()
|
||||||
|
|
||||||
|
closePairNow(t, offerer, answerer)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user