Skip to content

Commit 969ab68

Browse files
authored
Fix matching codecs with different rate or channels
Currently codecs are matched regardless of the clock rate and the channel count, and this makes impossible to fully support codecs that might have a clock rate or channel count different than the default one, in particular LPCM, PCMU, PCMA and multiopus (the last one is a custom Opus variant present in the Chrome source code to support multichannel Opus). For instance, let's suppose a peer (receiver) wants to receive an audio track encoded with LPCM, 48khz sample rate and 2 channels. This receiver doesn't know the audio codec yet, therefore it advertises all supported sample rates in the SDP: ``` LPCM/44100 LPCM/48000 LPCM/44100/2 LPCM/48000/2 ``` The other peer (sender) receives the SDP, but since the clock rate and channel count are not taken into consideration when matching codecs, the sender codec `LPCM/48000/2` is wrongly associated with the receiver codec `LPCM/44100`. The result is that the audio track cannot be decoded correctly from the receiver side. This patch fixes the issue and has been running smoothly in MediaMTX for almost a year. Unfortunately, in lots of examples and tests, clock rate and/or channels are not present (and in fact they are producing horrible SDPs that contain `VP8/0` instead of `VP8/90000` and are incompatible with lots of servers) therefore this new check causes troubles in existing code. In order to maintain compatibility, default clock rates and channels are provided for most codecs. In the future, it might be better to update examples (i can do it in a future patch) and remove the exception.
1 parent 70d06fd commit 969ab68

File tree

4 files changed

+330
-32
lines changed

4 files changed

+330
-32
lines changed

internal/fmtp/fmtp.go

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,32 @@ import (
88
"strings"
99
)
1010

11+
func defaultClockRate(mimeType string) uint32 {
12+
defaults := map[string]uint32{
13+
"audio/opus": 48000,
14+
"audio/pcmu": 8000,
15+
"audio/pcma": 8000,
16+
}
17+
18+
if def, ok := defaults[strings.ToLower(mimeType)]; ok {
19+
return def
20+
}
21+
22+
return 90000
23+
}
24+
25+
func defaultChannels(mimeType string) uint16 {
26+
defaults := map[string]uint16{
27+
"audio/opus": 2,
28+
}
29+
30+
if def, ok := defaults[strings.ToLower(mimeType)]; ok {
31+
return def
32+
}
33+
34+
return 0
35+
}
36+
1137
func parseParameters(line string) map[string]string {
1238
parameters := make(map[string]string)
1339

@@ -24,6 +50,61 @@ func parseParameters(line string) map[string]string {
2450
return parameters
2551
}
2652

53+
// ClockRateEqual checks whether two clock rates are equal.
54+
func ClockRateEqual(mimeType string, valA, valB uint32) bool {
55+
// Lots of users use formats without setting clock rate or channels.
56+
// In this case, use default values.
57+
// It would be better to remove this exception in a future major release.
58+
if valA == 0 {
59+
valA = defaultClockRate(mimeType)
60+
}
61+
if valB == 0 {
62+
valB = defaultClockRate(mimeType)
63+
}
64+
65+
return valA == valB
66+
}
67+
68+
// ChannelsEqual checks whether two channels are equal.
69+
func ChannelsEqual(mimeType string, valA, valB uint16) bool {
70+
// Lots of users use formats without setting clock rate or channels.
71+
// In this case, use default values.
72+
// It would be better to remove this exception in a future major release.
73+
if valA == 0 {
74+
valA = defaultChannels(mimeType)
75+
}
76+
if valB == 0 {
77+
valB = defaultChannels(mimeType)
78+
}
79+
80+
// RFC8866: channel count "is OPTIONAL and may be omitted
81+
// if the number of channels is one".
82+
if valA == 0 {
83+
valA = 1
84+
}
85+
if valB == 0 {
86+
valB = 1
87+
}
88+
89+
return valA == valB
90+
}
91+
92+
func paramsEqual(valA, valB map[string]string) bool {
93+
for k, v := range valA {
94+
if vb, ok := valB[k]; ok && !strings.EqualFold(vb, v) {
95+
return false
96+
}
97+
}
98+
99+
for k, v := range valB {
100+
if va, ok := valA[k]; ok && !strings.EqualFold(va, v) {
101+
return false
102+
}
103+
}
104+
105+
return true
106+
}
107+
27108
// FMTP interface for implementing custom
28109
// FMTP parsers based on MimeType.
29110
type FMTP interface {
@@ -39,7 +120,7 @@ type FMTP interface {
39120
}
40121

41122
// Parse parses an fmtp string based on the MimeType.
42-
func Parse(mimeType, line string) FMTP {
123+
func Parse(mimeType string, clockRate uint32, channels uint16, line string) FMTP {
43124
var fmtp FMTP
44125

45126
parameters := parseParameters(line)
@@ -63,6 +144,8 @@ func Parse(mimeType, line string) FMTP {
63144
default:
64145
fmtp = &genericFMTP{
65146
mimeType: mimeType,
147+
clockRate: clockRate,
148+
channels: channels,
66149
parameters: parameters,
67150
}
68151
}
@@ -72,6 +155,8 @@ func Parse(mimeType, line string) FMTP {
72155

73156
type genericFMTP struct {
74157
mimeType string
158+
clockRate uint32
159+
channels uint16
75160
parameters map[string]string
76161
}
77162

@@ -87,23 +172,10 @@ func (g *genericFMTP) Match(b FMTP) bool {
87172
return false
88173
}
89174

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
175+
return strings.EqualFold(g.mimeType, fmtp.MimeType()) &&
176+
ClockRateEqual(g.mimeType, g.clockRate, fmtp.clockRate) &&
177+
ChannelsEqual(g.mimeType, g.channels, fmtp.channels) &&
178+
paramsEqual(g.parameters, fmtp.parameters)
107179
}
108180

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

0 commit comments

Comments
 (0)