Commit 816b7a54 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Restrict negotiation of downstream codecs, allow multiple profiles.

We now restrict the allowable codecs in the downstream direction,
which leads to a clean failure instead of a silent track.  We also
allow multiple profiles for a single codec.
parent 845e7984
...@@ -142,71 +142,49 @@ func (g *Group) API() (*webrtc.API, error) { ...@@ -142,71 +142,49 @@ func (g *Group) API() (*webrtc.API, error) {
return APIFromNames(codecs) return APIFromNames(codecs)
} }
func codecFromName(name string) (webrtc.RTPCodecCapability, error) { func fmtpValue(fmtp, key string) string {
switch name { fields := strings.Split(fmtp, ";")
case "vp8": for _, f := range fields {
return webrtc.RTPCodecCapability{ kv := strings.SplitN(f, "=", 2)
"video/VP8", 90000, 0, if len(kv) != 2 {
"", continue
nil, }
}, nil if kv[0] == key {
case "vp9": return kv[1]
return webrtc.RTPCodecCapability{ }
"video/VP9", 90000, 0,
"profile-id=0",
nil,
}, nil
case "av1":
return webrtc.RTPCodecCapability{
"video/AV1X", 90000, 0,
"",
nil,
}, nil
case "h264":
return webrtc.RTPCodecCapability{
"video/H264", 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
nil,
}, nil
case "opus":
return webrtc.RTPCodecCapability{
"audio/opus", 48000, 2,
"minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1",
nil,
}, nil
case "g722":
return webrtc.RTPCodecCapability{
"audio/G722", 8000, 1,
"",
nil,
}, nil
case "pcmu":
return webrtc.RTPCodecCapability{
"audio/PCMU", 8000, 1,
"",
nil,
}, nil
case "pcma":
return webrtc.RTPCodecCapability{
"audio/PCMA", 8000, 1,
"",
nil,
}, nil
default:
return webrtc.RTPCodecCapability{}, errors.New("unknown codec")
} }
return ""
} }
func payloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) { func CodecPayloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) {
switch strings.ToLower(codec.MimeType) { switch strings.ToLower(codec.MimeType) {
case "video/vp8": case "video/vp8":
return 96, nil return 96, nil
case "video/vp9": case "video/vp9":
return 98, nil profile := fmtpValue(codec.SDPFmtpLine, "profile-id")
switch(profile) {
case "0":
return 98, nil
case "2":
return 100, nil
default:
return 0, errors.New("unknown VP9 profile")
}
case "video/av1x": case "video/av1x":
return 104, nil return 35, nil
case "video/h264": case "video/h264":
return 102, nil profile := fmtpValue(codec.SDPFmtpLine, "profile-level-id")
if len(profile) < 4 {
return 0, errors.New("malforned H.264 profile")
}
switch(strings.ToLower(profile[:4])) {
case "4200":
return 102, nil
case "42e0":
return 108, nil
default:
return 0, errors.New("unknown H.264 profile")
}
case "audio/opus": case "audio/opus":
return 111, nil return 111, nil
case "audio/g722": case "audio/g722":
...@@ -220,7 +198,112 @@ func payloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) { ...@@ -220,7 +198,112 @@ func payloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) {
} }
} }
func APIFromCodecs(codecs []webrtc.RTPCodecCapability) (*webrtc.API, error) { func codecsFromName(name string) ([]webrtc.RTPCodecParameters, error) {
fb := []webrtc.RTCPFeedback{
{"goog-remb", ""},
{"nack", ""},
{"nack", "pli"},
{"ccm", "fir"},
}
var codecs []webrtc.RTPCodecCapability
switch name {
case "vp8":
codecs = []webrtc.RTPCodecCapability{
{
"video/VP8", 90000, 0,
"",
fb,
},
}
case "vp9":
codecs = []webrtc.RTPCodecCapability{
{
"video/VP9", 90000, 0,
"profile-id=0",
fb,
},
{
"video/VP9", 90000, 0,
"profile-id=2",
fb,
},
}
case "av1":
codecs = []webrtc.RTPCodecCapability{
{
"video/AV1X", 90000, 0,
"",
fb,
},
}
case "h264":
codecs = []webrtc.RTPCodecCapability{
{
"video/H264", 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
fb,
},
{
"video/H264", 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
fb,
},
}
case "opus":
codecs = []webrtc.RTPCodecCapability{
{
"audio/opus", 48000, 2,
"minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1",
nil,
},
}
case "g722":
codecs = []webrtc.RTPCodecCapability{
{
"audio/G722", 8000, 1,
"",
nil,
},
}
case "pcmu":
codecs = []webrtc.RTPCodecCapability{
{
"audio/PCMU", 8000, 1,
"",
nil,
},
}
case "pcma":
codecs = []webrtc.RTPCodecCapability{
{
"audio/PCMU", 8000, 1,
"",
nil,
},
}
default:
return nil, errors.New("unknown codec")
}
parms := make([]webrtc.RTPCodecParameters, 0, len(codecs))
for _, c := range codecs {
ptype, err := CodecPayloadType(c)
if err != nil {
log.Printf("Couldn't determine ptype for codec %v: %v",
c.MimeType, err)
continue
}
parms = append(parms, webrtc.RTPCodecParameters{
RTPCodecCapability: c,
PayloadType: ptype,
})
}
return parms, nil
}
func APIFromCodecs(codecs []webrtc.RTPCodecParameters) (*webrtc.API, error) {
s := webrtc.SettingEngine{} s := webrtc.SettingEngine{}
s.SetSRTPReplayProtectionWindow(512) s.SetSRTPReplayProtectionWindow(512)
if !UseMDNS { if !UseMDNS {
...@@ -229,41 +312,11 @@ func APIFromCodecs(codecs []webrtc.RTPCodecCapability) (*webrtc.API, error) { ...@@ -229,41 +312,11 @@ func APIFromCodecs(codecs []webrtc.RTPCodecCapability) (*webrtc.API, error) {
m := webrtc.MediaEngine{} m := webrtc.MediaEngine{}
for _, codec := range codecs { for _, codec := range codecs {
var tpe webrtc.RTPCodecType tpe := webrtc.RTPCodecTypeVideo
var fb []webrtc.RTCPFeedback if strings.HasPrefix(strings.ToLower(codec.MimeType), "audio/") {
if strings.HasPrefix(strings.ToLower(codec.MimeType), "video/") {
tpe = webrtc.RTPCodecTypeVideo
fb = []webrtc.RTCPFeedback{
{"goog-remb", ""},
{"nack", ""},
{"nack", "pli"},
{"ccm", "fir"},
}
} else if strings.HasPrefix(strings.ToLower(codec.MimeType), "audio/") {
tpe = webrtc.RTPCodecTypeAudio tpe = webrtc.RTPCodecTypeAudio
fb = []webrtc.RTCPFeedback{}
} else {
continue
}
ptpe, err := payloadType(codec)
if err != nil {
log.Printf("%v", err)
continue
} }
err = m.RegisterCodec( err := m.RegisterCodec(codec, tpe)
webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: codec.MimeType,
ClockRate: codec.ClockRate,
Channels: codec.Channels,
SDPFmtpLine: codec.SDPFmtpLine,
RTCPFeedback: fb,
},
PayloadType: ptpe,
},
tpe,
)
if err != nil { if err != nil {
log.Printf("%v", err) log.Printf("%v", err)
continue continue
...@@ -290,14 +343,14 @@ func APIFromNames(names []string) (*webrtc.API, error) { ...@@ -290,14 +343,14 @@ func APIFromNames(names []string) (*webrtc.API, error) {
if len(names) == 0 { if len(names) == 0 {
names = []string{"vp8", "opus"} names = []string{"vp8", "opus"}
} }
codecs := make([]webrtc.RTPCodecCapability, 0, len(names)) var codecs []webrtc.RTPCodecParameters
for _, n := range names { for _, n := range names {
codec, err := codecFromName(n) cs, err := codecsFromName(n)
if err != nil { if err != nil {
log.Printf("Codec %v: %v", n, err) log.Printf("Codec %v: %v", n, err)
continue continue
} }
codecs = append(codecs, codec) codecs = append(codecs, cs...)
} }
return APIFromCodecs(codecs) return APIFromCodecs(codecs)
......
...@@ -237,3 +237,29 @@ func TestPermissions(t *testing.T) { ...@@ -237,3 +237,29 @@ func TestPermissions(t *testing.T) {
} }
} }
func TestFmtpValue(t *testing.T) {
type fmtpTest struct {
fmtp string
key string
value string
}
fmtpTests := []fmtpTest{
{"", "foo", ""},
{"profile-id=0", "profile-id", "0"},
{"profile-id=0", "foo", ""},
{"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", "profile-level-id", "42001f"},
{"foo=1;bar=2;quux=3", "foo", "1"},
{"foo=1;bar=2;quux=3", "bar", "2"},
{"foo=1;bar=2;quux=3", "fu", ""},
}
for _, test := range fmtpTests {
v := fmtpValue(test.fmtp, test.key)
if v != test.value {
t.Errorf("fmtpValue(%v, %v) = %v, expected %v",
test.fmtp, test.key, v, test.value,
)
}
}
}
...@@ -389,6 +389,26 @@ func addDownTrackUnlocked(conn *rtpDownConnection, remoteTrack *rtpUpTrack, remo ...@@ -389,6 +389,26 @@ func addDownTrackUnlocked(conn *rtpDownConnection, remoteTrack *rtpUpTrack, remo
return err return err
} }
codec := local.Codec()
ptype, err := group.CodecPayloadType(local.Codec())
if err != nil {
log.Printf("Couldn't determine ptype for codec %v: %v",
codec.MimeType, err)
} else {
err := transceiver.SetCodecPreferences(
[]webrtc.RTPCodecParameters{
{
RTPCodecCapability: codec,
PayloadType: ptype,
},
},
)
if err != nil {
log.Printf("Couldn't set ptype for codec %v: %v",
codec.MimeType, err)
}
}
parms := transceiver.Sender().GetParameters() parms := transceiver.Sender().GetParameters()
if len(parms.Encodings) != 1 { if len(parms.Encodings) != 1 {
return errors.New("got multiple encodings") return errors.New("got multiple encodings")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment