Skip to content

Commit 842a58b

Browse files
committed
Automatically configure RTX codecs
1 parent 2de1ac4 commit 842a58b

5 files changed

+263
-2
lines changed

mediaengine.go

+38
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,44 @@ func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType)
273273
return err
274274
}
275275

276+
func (m *MediaEngine) autoConfigRTXCodecs() error {
277+
additionalRTXCodecs := []RTPCodecParameters{}
278+
for _, codec := range m.videoCodecs {
279+
// ignore FEC & RTX
280+
if strings.Contains(codec.MimeType, MimeTypeFlexFEC) || codec.MimeType == MimeTypeRTX {
281+
continue
282+
}
283+
haveNACK := false
284+
for _, fb := range codec.RTCPFeedback {
285+
if fb.Type == "nack" {
286+
haveNACK = true
287+
288+
break
289+
}
290+
}
291+
if haveNACK {
292+
additionalRTXCodecs = append(additionalRTXCodecs, RTPCodecParameters{
293+
RTPCodecCapability: RTPCodecCapability{
294+
MimeType: MimeTypeRTX,
295+
ClockRate: 90000,
296+
Channels: 0,
297+
SDPFmtpLine: fmt.Sprintf("apt=%d", codec.PayloadType),
298+
RTCPFeedback: nil,
299+
},
300+
PayloadType: codec.PayloadType + 1,
301+
})
302+
}
303+
}
304+
for i := range additionalRTXCodecs {
305+
err := m.RegisterCodec(additionalRTXCodecs[i], RTPCodecTypeVideo)
306+
if err != nil {
307+
return err
308+
}
309+
}
310+
311+
return nil
312+
}
313+
276314
// RegisterHeaderExtension adds a header extension to the MediaEngine
277315
// To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete.
278316
//

mediaengine_test.go

+155
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,158 @@ a=ssrc:4281768245 msid:6ff05509-be96-4ef1-a74f-425e14720983 16d5d7fe-d076-4718-9
961961
assert.Len(t, mediaEngine.negotiatedVideoCodecs, 2)
962962
})
963963
}
964+
965+
func TestAutoConfigRTXCodecs(t *testing.T) {
966+
for _, test := range []struct {
967+
Original []RTPCodecParameters
968+
ExpectedResult []RTPCodecParameters
969+
ExpectedError error
970+
}{
971+
{
972+
// no video codec
973+
Original: []RTPCodecParameters{
974+
{
975+
PayloadType: 1,
976+
RTPCodecCapability: RTPCodecCapability{
977+
MimeType: MimeTypeFlexFEC03,
978+
ClockRate: 90000,
979+
Channels: 0,
980+
SDPFmtpLine: "repair-window=10000000",
981+
RTCPFeedback: nil,
982+
},
983+
},
984+
},
985+
ExpectedResult: []RTPCodecParameters{
986+
{
987+
PayloadType: 1,
988+
RTPCodecCapability: RTPCodecCapability{
989+
MimeType: MimeTypeFlexFEC03,
990+
ClockRate: 90000,
991+
Channels: 0,
992+
SDPFmtpLine: "repair-window=10000000",
993+
RTCPFeedback: nil,
994+
},
995+
},
996+
},
997+
ExpectedError: nil,
998+
},
999+
{
1000+
// one video codec with no nack rtcp feedback
1001+
Original: []RTPCodecParameters{
1002+
{
1003+
PayloadType: 1,
1004+
RTPCodecCapability: RTPCodecCapability{
1005+
MimeType: MimeTypeH265,
1006+
ClockRate: 90000,
1007+
Channels: 0,
1008+
SDPFmtpLine: "",
1009+
RTCPFeedback: nil,
1010+
},
1011+
},
1012+
},
1013+
ExpectedResult: []RTPCodecParameters{
1014+
{
1015+
PayloadType: 1,
1016+
RTPCodecCapability: RTPCodecCapability{
1017+
MimeType: MimeTypeH265,
1018+
ClockRate: 90000,
1019+
Channels: 0,
1020+
SDPFmtpLine: "",
1021+
RTCPFeedback: nil,
1022+
},
1023+
},
1024+
},
1025+
ExpectedError: nil,
1026+
},
1027+
{
1028+
// one video codec with nack and pli rtcp feedback
1029+
Original: []RTPCodecParameters{
1030+
{
1031+
PayloadType: 1,
1032+
RTPCodecCapability: RTPCodecCapability{
1033+
MimeType: MimeTypeH265,
1034+
ClockRate: 90000,
1035+
Channels: 0,
1036+
SDPFmtpLine: "",
1037+
RTCPFeedback: []RTCPFeedback{
1038+
{Type: "nack", Parameter: ""},
1039+
{Type: "nack", Parameter: "pli"},
1040+
},
1041+
},
1042+
},
1043+
},
1044+
ExpectedResult: []RTPCodecParameters{
1045+
{
1046+
PayloadType: 1,
1047+
RTPCodecCapability: RTPCodecCapability{
1048+
MimeType: MimeTypeH265,
1049+
ClockRate: 90000,
1050+
Channels: 0,
1051+
SDPFmtpLine: "",
1052+
RTCPFeedback: []RTCPFeedback{
1053+
{Type: "nack", Parameter: ""},
1054+
{Type: "nack", Parameter: "pli"},
1055+
},
1056+
},
1057+
},
1058+
{
1059+
PayloadType: 2,
1060+
RTPCodecCapability: RTPCodecCapability{
1061+
MimeType: MimeTypeRTX,
1062+
ClockRate: 90000,
1063+
Channels: 0,
1064+
SDPFmtpLine: "apt=1",
1065+
RTCPFeedback: nil,
1066+
},
1067+
},
1068+
},
1069+
ExpectedError: nil,
1070+
},
1071+
{
1072+
// multiple video codec, expect error because of PayloadType collision
1073+
Original: []RTPCodecParameters{
1074+
{
1075+
PayloadType: 1,
1076+
RTPCodecCapability: RTPCodecCapability{
1077+
MimeType: MimeTypeH265,
1078+
ClockRate: 90000,
1079+
Channels: 0,
1080+
SDPFmtpLine: "",
1081+
RTCPFeedback: []RTCPFeedback{
1082+
{Type: "nack", Parameter: ""},
1083+
{Type: "nack", Parameter: "pli"},
1084+
},
1085+
},
1086+
},
1087+
{
1088+
PayloadType: 2,
1089+
RTPCodecCapability: RTPCodecCapability{
1090+
MimeType: MimeTypeVP8,
1091+
ClockRate: 90000,
1092+
Channels: 0,
1093+
SDPFmtpLine: "",
1094+
RTCPFeedback: []RTCPFeedback{
1095+
{Type: "nack", Parameter: ""},
1096+
{Type: "nack", Parameter: "pli"},
1097+
},
1098+
},
1099+
},
1100+
},
1101+
ExpectedResult: nil,
1102+
ExpectedError: ErrCodecAlreadyRegistered,
1103+
},
1104+
} {
1105+
m := &MediaEngine{
1106+
videoCodecs: test.Original,
1107+
}
1108+
err := m.autoConfigRTXCodecs()
1109+
assert.Equal(t, err, test.ExpectedError)
1110+
if err == nil {
1111+
for i := range m.videoCodecs {
1112+
// ignore for following assert
1113+
m.videoCodecs[i].statsID = ""
1114+
}
1115+
assert.Equal(t, m.videoCodecs, test.ExpectedResult)
1116+
}
1117+
}
1118+
}

peerconnection.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
142142
pc.iceConnectionState.Store(ICEConnectionStateNew)
143143
pc.connectionState.Store(PeerConnectionStateNew)
144144

145-
i, err := api.interceptorRegistry.Build("")
146-
if err != nil {
145+
var i interceptor.Interceptor
146+
var err error
147+
if i, err = api.interceptorRegistry.Build(""); err != nil {
147148
return nil, err
148149
}
149150

@@ -152,6 +153,12 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
152153
interceptor: i,
153154
}
154155

156+
if api.settingEngine.autoConfigRTXCodec {
157+
if err = api.mediaEngine.autoConfigRTXCodecs(); err != nil {
158+
return nil, err
159+
}
160+
}
161+
155162
if api.settingEngine.disableMediaEngineCopy {
156163
pc.api.mediaEngine = api.mediaEngine
157164
} else {

settingengine.go

+6
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ type SettingEngine struct {
106106
fireOnTrackBeforeFirstRTP bool
107107
disableCloseByDTLS bool
108108
dataChannelBlockWrite bool
109+
autoConfigRTXCodec bool
109110
}
110111

111112
func (e *SettingEngine) getSCTPMaxMessageSize() uint32 {
@@ -551,3 +552,8 @@ func (e *SettingEngine) SetFireOnTrackBeforeFirstRTP(fireOnTrackBeforeFirstRTP b
551552
func (e *SettingEngine) DisableCloseByDTLS(isEnabled bool) {
552553
e.disableCloseByDTLS = isEnabled
553554
}
555+
556+
// AutoConfigRTXCodec sets if the RTX codec should be automatically configured.
557+
func (e *SettingEngine) AutoConfigRTXCodec(autoConfigRTXCodec bool) {
558+
e.autoConfigRTXCodec = autoConfigRTXCodec
559+
}

settingengine_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -464,3 +464,58 @@ func TestEnableDataChannelBlockWrite(t *testing.T) {
464464
assert.ErrorIs(t, err, context.DeadlineExceeded)
465465
closePairNow(t, offer, answer)
466466
}
467+
468+
func TestAutoConfigRTXCodec(t *testing.T) {
469+
lim := test.TimeOut(time.Second * 30)
470+
defer lim.Stop()
471+
472+
report := test.CheckRoutines(t)
473+
defer report()
474+
475+
settingEngine := SettingEngine{}
476+
settingEngine.DisableMediaEngineCopy(true)
477+
settingEngine.AutoConfigRTXCodec(true)
478+
mediaEngine := &MediaEngine{}
479+
err := mediaEngine.RegisterCodec(
480+
RTPCodecParameters{
481+
PayloadType: 96,
482+
RTPCodecCapability: RTPCodecCapability{
483+
MimeType: MimeTypeVP8,
484+
ClockRate: 90000,
485+
},
486+
},
487+
RTPCodecTypeVideo,
488+
)
489+
assert.Equal(t, err, nil)
490+
api := NewAPI(
491+
WithMediaEngine(mediaEngine),
492+
WithSettingEngine(settingEngine),
493+
)
494+
config := Configuration{
495+
ICEServers: []ICEServer{
496+
{
497+
URLs: []string{"stun:stun.l.google.com:19302"},
498+
},
499+
},
500+
}
501+
var pc *PeerConnection
502+
pc, err = api.NewPeerConnection(config)
503+
assert.Equal(t, err, nil)
504+
for i := range mediaEngine.videoCodecs {
505+
mediaEngine.videoCodecs[i].statsID = ""
506+
}
507+
assert.Equal(t, len(mediaEngine.videoCodecs), 2)
508+
assert.Equal(t, mediaEngine.videoCodecs[1],
509+
RTPCodecParameters{
510+
PayloadType: 97,
511+
RTPCodecCapability: RTPCodecCapability{
512+
MimeType: MimeTypeRTX,
513+
ClockRate: 90000,
514+
Channels: 0,
515+
SDPFmtpLine: "apt=96",
516+
RTCPFeedback: nil,
517+
},
518+
},
519+
)
520+
assert.NoError(t, pc.close(true))
521+
}

0 commit comments

Comments
 (0)