Automatically configure RTX codecs

This commit is contained in:
3DRX
2025-04-13 18:18:43 +08:00
parent 2de1ac4285
commit 842a58bf06
5 changed files with 263 additions and 2 deletions

View File

@@ -273,6 +273,44 @@ func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType)
return err return err
} }
func (m *MediaEngine) autoConfigRTXCodecs() error {
additionalRTXCodecs := []RTPCodecParameters{}
for _, codec := range m.videoCodecs {
// ignore FEC & RTX
if strings.Contains(codec.MimeType, MimeTypeFlexFEC) || codec.MimeType == MimeTypeRTX {
continue
}
haveNACK := false
for _, fb := range codec.RTCPFeedback {
if fb.Type == "nack" {
haveNACK = true
break
}
}
if haveNACK {
additionalRTXCodecs = append(additionalRTXCodecs, RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeRTX,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: fmt.Sprintf("apt=%d", codec.PayloadType),
RTCPFeedback: nil,
},
PayloadType: codec.PayloadType + 1,
})
}
}
for i := range additionalRTXCodecs {
err := m.RegisterCodec(additionalRTXCodecs[i], RTPCodecTypeVideo)
if err != nil {
return err
}
}
return nil
}
// RegisterHeaderExtension adds a header extension to the MediaEngine // RegisterHeaderExtension adds a header extension to the MediaEngine
// To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete. // To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete.
// //

View File

@@ -961,3 +961,158 @@ a=ssrc:4281768245 msid:6ff05509-be96-4ef1-a74f-425e14720983 16d5d7fe-d076-4718-9
assert.Len(t, mediaEngine.negotiatedVideoCodecs, 2) assert.Len(t, mediaEngine.negotiatedVideoCodecs, 2)
}) })
} }
func TestAutoConfigRTXCodecs(t *testing.T) {
for _, test := range []struct {
Original []RTPCodecParameters
ExpectedResult []RTPCodecParameters
ExpectedError error
}{
{
// no video codec
Original: []RTPCodecParameters{
{
PayloadType: 1,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeFlexFEC03,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "repair-window=10000000",
RTCPFeedback: nil,
},
},
},
ExpectedResult: []RTPCodecParameters{
{
PayloadType: 1,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeFlexFEC03,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "repair-window=10000000",
RTCPFeedback: nil,
},
},
},
ExpectedError: nil,
},
{
// one video codec with no nack rtcp feedback
Original: []RTPCodecParameters{
{
PayloadType: 1,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeH265,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
},
},
},
ExpectedResult: []RTPCodecParameters{
{
PayloadType: 1,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeH265,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
},
},
},
ExpectedError: nil,
},
{
// one video codec with nack and pli rtcp feedback
Original: []RTPCodecParameters{
{
PayloadType: 1,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeH265,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: []RTCPFeedback{
{Type: "nack", Parameter: ""},
{Type: "nack", Parameter: "pli"},
},
},
},
},
ExpectedResult: []RTPCodecParameters{
{
PayloadType: 1,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeH265,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: []RTCPFeedback{
{Type: "nack", Parameter: ""},
{Type: "nack", Parameter: "pli"},
},
},
},
{
PayloadType: 2,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeRTX,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "apt=1",
RTCPFeedback: nil,
},
},
},
ExpectedError: nil,
},
{
// multiple video codec, expect error because of PayloadType collision
Original: []RTPCodecParameters{
{
PayloadType: 1,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeH265,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: []RTCPFeedback{
{Type: "nack", Parameter: ""},
{Type: "nack", Parameter: "pli"},
},
},
},
{
PayloadType: 2,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeVP8,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: []RTCPFeedback{
{Type: "nack", Parameter: ""},
{Type: "nack", Parameter: "pli"},
},
},
},
},
ExpectedResult: nil,
ExpectedError: ErrCodecAlreadyRegistered,
},
} {
m := &MediaEngine{
videoCodecs: test.Original,
}
err := m.autoConfigRTXCodecs()
assert.Equal(t, err, test.ExpectedError)
if err == nil {
for i := range m.videoCodecs {
// ignore for following assert
m.videoCodecs[i].statsID = ""
}
assert.Equal(t, m.videoCodecs, test.ExpectedResult)
}
}
}

View File

@@ -142,8 +142,9 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
pc.iceConnectionState.Store(ICEConnectionStateNew) pc.iceConnectionState.Store(ICEConnectionStateNew)
pc.connectionState.Store(PeerConnectionStateNew) pc.connectionState.Store(PeerConnectionStateNew)
i, err := api.interceptorRegistry.Build("") var i interceptor.Interceptor
if err != nil { var err error
if i, err = api.interceptorRegistry.Build(""); err != nil {
return nil, err return nil, err
} }
@@ -152,6 +153,12 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
interceptor: i, interceptor: i,
} }
if api.settingEngine.autoConfigRTXCodec {
if err = api.mediaEngine.autoConfigRTXCodecs(); err != nil {
return nil, err
}
}
if api.settingEngine.disableMediaEngineCopy { if api.settingEngine.disableMediaEngineCopy {
pc.api.mediaEngine = api.mediaEngine pc.api.mediaEngine = api.mediaEngine
} else { } else {

View File

@@ -106,6 +106,7 @@ type SettingEngine struct {
fireOnTrackBeforeFirstRTP bool fireOnTrackBeforeFirstRTP bool
disableCloseByDTLS bool disableCloseByDTLS bool
dataChannelBlockWrite bool dataChannelBlockWrite bool
autoConfigRTXCodec bool
} }
func (e *SettingEngine) getSCTPMaxMessageSize() uint32 { func (e *SettingEngine) getSCTPMaxMessageSize() uint32 {
@@ -551,3 +552,8 @@ func (e *SettingEngine) SetFireOnTrackBeforeFirstRTP(fireOnTrackBeforeFirstRTP b
func (e *SettingEngine) DisableCloseByDTLS(isEnabled bool) { func (e *SettingEngine) DisableCloseByDTLS(isEnabled bool) {
e.disableCloseByDTLS = isEnabled e.disableCloseByDTLS = isEnabled
} }
// AutoConfigRTXCodec sets if the RTX codec should be automatically configured.
func (e *SettingEngine) AutoConfigRTXCodec(autoConfigRTXCodec bool) {
e.autoConfigRTXCodec = autoConfigRTXCodec
}

View File

@@ -464,3 +464,58 @@ func TestEnableDataChannelBlockWrite(t *testing.T) {
assert.ErrorIs(t, err, context.DeadlineExceeded) assert.ErrorIs(t, err, context.DeadlineExceeded)
closePairNow(t, offer, answer) closePairNow(t, offer, answer)
} }
func TestAutoConfigRTXCodec(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
report := test.CheckRoutines(t)
defer report()
settingEngine := SettingEngine{}
settingEngine.DisableMediaEngineCopy(true)
settingEngine.AutoConfigRTXCodec(true)
mediaEngine := &MediaEngine{}
err := mediaEngine.RegisterCodec(
RTPCodecParameters{
PayloadType: 96,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeVP8,
ClockRate: 90000,
},
},
RTPCodecTypeVideo,
)
assert.Equal(t, err, nil)
api := NewAPI(
WithMediaEngine(mediaEngine),
WithSettingEngine(settingEngine),
)
config := Configuration{
ICEServers: []ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
var pc *PeerConnection
pc, err = api.NewPeerConnection(config)
assert.Equal(t, err, nil)
for i := range mediaEngine.videoCodecs {
mediaEngine.videoCodecs[i].statsID = ""
}
assert.Equal(t, len(mediaEngine.videoCodecs), 2)
assert.Equal(t, mediaEngine.videoCodecs[1],
RTPCodecParameters{
PayloadType: 97,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeRTX,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "apt=96",
RTCPFeedback: nil,
},
},
)
assert.NoError(t, pc.close(true))
}