Skip to content

Commit 2aef1d7

Browse files
committed
Match codecs with different rate or channels
Consider clock rate and channels when matching codecs. This allows to support codecs with the same MIME type but sample rate or channel count that might be different from the default ones, like PCMU, PCMA, LPCM and multiopus.
1 parent 59c7270 commit 2aef1d7

File tree

4 files changed

+167
-32
lines changed

4 files changed

+167
-32
lines changed

internal/fmtp/fmtp.go

+49-18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,46 @@ func parseParameters(line string) map[string]string {
2424
return parameters
2525
}
2626

27+
// ClockRateEqual checks whether two clock rates are equal.
28+
func ClockRateEqual(valA, valB uint32) bool {
29+
// Lots of users use formats without setting clock rate or channels.
30+
// In this case, skip the check to keep backward compatibility.
31+
// It would be better to remove this exception in a future major release.
32+
if valA == 0 || valB == 0 {
33+
return true
34+
}
35+
36+
return valA == valB
37+
}
38+
39+
// ChannelsEqual checks whether two channels are equal.
40+
func ChannelsEqual(valA, valB uint16) bool {
41+
// Lots of users use formats without setting clock rate or channels.
42+
// In this case, skip the check to keep backward compatibility.
43+
// It would be better to remove this exception in a future major release.
44+
if valA == 0 || valB == 0 {
45+
return true
46+
}
47+
48+
return valA == valB
49+
}
50+
51+
func paramsEqual(valA, valB map[string]string) bool {
52+
for k, v := range valA {
53+
if vb, ok := valB[k]; ok && !strings.EqualFold(vb, v) {
54+
return false
55+
}
56+
}
57+
58+
for k, v := range valB {
59+
if va, ok := valA[k]; ok && !strings.EqualFold(va, v) {
60+
return false
61+
}
62+
}
63+
64+
return true
65+
}
66+
2767
// FMTP interface for implementing custom
2868
// FMTP parsers based on MimeType.
2969
type FMTP interface {
@@ -39,7 +79,7 @@ type FMTP interface {
3979
}
4080

4181
// Parse parses an fmtp string based on the MimeType.
42-
func Parse(mimeType, line string) FMTP {
82+
func Parse(mimeType string, clockRate uint32, channels uint16, line string) FMTP {
4383
var fmtp FMTP
4484

4585
parameters := parseParameters(line)
@@ -63,6 +103,8 @@ func Parse(mimeType, line string) FMTP {
63103
default:
64104
fmtp = &genericFMTP{
65105
mimeType: mimeType,
106+
clockRate: clockRate,
107+
channels: channels,
66108
parameters: parameters,
67109
}
68110
}
@@ -72,6 +114,8 @@ func Parse(mimeType, line string) FMTP {
72114

73115
type genericFMTP struct {
74116
mimeType string
117+
clockRate uint32
118+
channels uint16
75119
parameters map[string]string
76120
}
77121

@@ -87,23 +131,10 @@ func (g *genericFMTP) Match(b FMTP) bool {
87131
return false
88132
}
89133

90-
if !strings.EqualFold(g.mimeType, fmtp.MimeType()) {
91-
return false
92-
}
93-
94-
for k, v := range g.parameters {
95-
if vb, ok := fmtp.parameters[k]; ok && !strings.EqualFold(vb, v) {
96-
return false
97-
}
98-
}
99-
100-
for k, v := range fmtp.parameters {
101-
if va, ok := g.parameters[k]; ok && !strings.EqualFold(va, v) {
102-
return false
103-
}
104-
}
105-
106-
return true
134+
return strings.EqualFold(g.mimeType, fmtp.MimeType()) &&
135+
ClockRateEqual(g.clockRate, fmtp.clockRate) &&
136+
ChannelsEqual(g.channels, fmtp.channels) &&
137+
paramsEqual(g.parameters, fmtp.parameters)
107138
}
108139

109140
func (g *genericFMTP) Parameter(key string) (string, bool) {

internal/fmtp/fmtp_test.go

+90-7
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,23 @@ func TestParseParameters(t *testing.T) {
5656

5757
func TestParse(t *testing.T) {
5858
for _, ca := range []struct {
59-
name string
60-
mimeType string
61-
line string
62-
expected FMTP
59+
name string
60+
mimeType string
61+
clockRate uint32
62+
channels uint16
63+
line string
64+
expected FMTP
6365
}{
6466
{
6567
"generic",
6668
"generic",
69+
90000,
70+
2,
6771
"key-name=value",
6872
&genericFMTP{
69-
mimeType: "generic",
73+
mimeType: "generic",
74+
clockRate: 90000,
75+
channels: 2,
7076
parameters: map[string]string{
7177
"key-name": "value",
7278
},
@@ -75,9 +81,13 @@ func TestParse(t *testing.T) {
7581
{
7682
"generic case normalization",
7783
"generic",
84+
90000,
85+
2,
7886
"Key=value",
7987
&genericFMTP{
80-
mimeType: "generic",
88+
mimeType: "generic",
89+
clockRate: 90000,
90+
channels: 2,
8191
parameters: map[string]string{
8292
"key": "value",
8393
},
@@ -86,6 +96,8 @@ func TestParse(t *testing.T) {
8696
{
8797
"h264",
8898
"video/h264",
99+
90000,
100+
0,
89101
"key-name=value",
90102
&h264FMTP{
91103
parameters: map[string]string{
@@ -96,6 +108,8 @@ func TestParse(t *testing.T) {
96108
{
97109
"vp9",
98110
"video/vp9",
111+
90000,
112+
0,
99113
"key-name=value",
100114
&vp9FMTP{
101115
parameters: map[string]string{
@@ -106,6 +120,8 @@ func TestParse(t *testing.T) {
106120
{
107121
"av1",
108122
"video/av1",
123+
90000,
124+
0,
109125
"key-name=value",
110126
&av1FMTP{
111127
parameters: map[string]string{
@@ -115,7 +131,7 @@ func TestParse(t *testing.T) {
115131
},
116132
} {
117133
t.Run(ca.name, func(t *testing.T) {
118-
f := Parse(ca.mimeType, ca.line)
134+
f := Parse(ca.mimeType, ca.clockRate, ca.channels, ca.line)
119135
if !reflect.DeepEqual(ca.expected, f) {
120136
t.Errorf("expected '%v', got '%v'", ca.expected, f)
121137
}
@@ -177,6 +193,27 @@ func TestMatch(t *testing.T) { //nolint:maintidx
177193
},
178194
true,
179195
},
196+
{
197+
"generic inferred channels",
198+
&genericFMTP{
199+
mimeType: "generic",
200+
channels: 1,
201+
parameters: map[string]string{
202+
"key1": "value1",
203+
"key2": "value2",
204+
"key3": "value3",
205+
},
206+
},
207+
&genericFMTP{
208+
mimeType: "generic",
209+
parameters: map[string]string{
210+
"key1": "value1",
211+
"key2": "value2",
212+
"key3": "value3",
213+
},
214+
},
215+
true,
216+
},
180217
{
181218
"generic inconsistent different kind",
182219
&genericFMTP{
@@ -210,6 +247,52 @@ func TestMatch(t *testing.T) { //nolint:maintidx
210247
},
211248
false,
212249
},
250+
{
251+
"generic inconsistent different clock rate",
252+
&genericFMTP{
253+
mimeType: "generic",
254+
clockRate: 90000,
255+
parameters: map[string]string{
256+
"key1": "value1",
257+
"key2": "value2",
258+
"key3": "value3",
259+
},
260+
},
261+
&genericFMTP{
262+
mimeType: "generic",
263+
clockRate: 48000,
264+
parameters: map[string]string{
265+
"key1": "value1",
266+
"key2": "value2",
267+
"key3": "value3",
268+
},
269+
},
270+
false,
271+
},
272+
{
273+
"generic inconsistent different channels",
274+
&genericFMTP{
275+
mimeType: "generic",
276+
clockRate: 90000,
277+
channels: 2,
278+
parameters: map[string]string{
279+
"key1": "value1",
280+
"key2": "value2",
281+
"key3": "value3",
282+
},
283+
},
284+
&genericFMTP{
285+
mimeType: "generic",
286+
clockRate: 90000,
287+
channels: 1,
288+
parameters: map[string]string{
289+
"key1": "value1",
290+
"key2": "value2",
291+
"key3": "value3",
292+
},
293+
},
294+
false,
295+
},
213296
{
214297
"generic inconsistent different parameters",
215298
&genericFMTP{

mediaengine.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,10 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
246246
// addCodec will append codec if it not exists.
247247
func (m *MediaEngine) addCodec(codecs []RTPCodecParameters, codec RTPCodecParameters) []RTPCodecParameters {
248248
for _, c := range codecs {
249-
if c.MimeType == codec.MimeType && c.PayloadType == codec.PayloadType {
249+
if c.MimeType == codec.MimeType &&
250+
fmtp.ClockRateEqual(c.ClockRate, codec.ClockRate) &&
251+
fmtp.ChannelsEqual(c.Channels, codec.Channels) &&
252+
c.PayloadType == codec.PayloadType {
250253
return codecs
251254
}
252255
}
@@ -459,7 +462,12 @@ func (m *MediaEngine) matchRemoteCodec(
459462
codecs = m.audioCodecs
460463
}
461464

462-
remoteFmtp := fmtp.Parse(remoteCodec.RTPCodecCapability.MimeType, remoteCodec.RTPCodecCapability.SDPFmtpLine)
465+
remoteFmtp := fmtp.Parse(
466+
remoteCodec.RTPCodecCapability.MimeType,
467+
remoteCodec.RTPCodecCapability.ClockRate,
468+
remoteCodec.RTPCodecCapability.Channels,
469+
remoteCodec.RTPCodecCapability.SDPFmtpLine)
470+
463471
if apt, hasApt := remoteFmtp.Parameter("apt"); hasApt { //nolint:nestif
464472
payloadType, err := strconv.ParseUint(apt, 10, 8)
465473
if err != nil {

rtpcodec.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,32 @@ func codecParametersFuzzySearch(
108108
needle RTPCodecParameters,
109109
haystack []RTPCodecParameters,
110110
) (RTPCodecParameters, codecMatchType) {
111-
needleFmtp := fmtp.Parse(needle.RTPCodecCapability.MimeType, needle.RTPCodecCapability.SDPFmtpLine)
111+
needleFmtp := fmtp.Parse(
112+
needle.RTPCodecCapability.MimeType,
113+
needle.RTPCodecCapability.ClockRate,
114+
needle.RTPCodecCapability.Channels,
115+
needle.RTPCodecCapability.SDPFmtpLine)
112116

113-
// First attempt to match on MimeType + SDPFmtpLine
117+
// First attempt to match on MimeType + ClockRate + Channels + SDPFmtpLine
114118
for _, c := range haystack {
115-
cfmtp := fmtp.Parse(c.RTPCodecCapability.MimeType, c.RTPCodecCapability.SDPFmtpLine)
119+
cfmtp := fmtp.Parse(
120+
c.RTPCodecCapability.MimeType,
121+
c.RTPCodecCapability.ClockRate,
122+
c.RTPCodecCapability.Channels,
123+
c.RTPCodecCapability.SDPFmtpLine)
124+
116125
if needleFmtp.Match(cfmtp) {
117126
return c, codecMatchExact
118127
}
119128
}
120129

121-
// Fallback to just MimeType
130+
// Fallback to just MimeType + ClockRate + Channels
122131
for _, c := range haystack {
123-
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) {
132+
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
133+
fmtp.ClockRateEqual(c.RTPCodecCapability.ClockRate,
134+
needle.RTPCodecCapability.ClockRate) &&
135+
fmtp.ChannelsEqual(c.RTPCodecCapability.Channels,
136+
needle.RTPCodecCapability.Channels) {
124137
return c, codecMatchPartial
125138
}
126139
}

0 commit comments

Comments
 (0)