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,85 +142,168 @@ func (g *Group) API() (*webrtc.API, error) {
return APIFromNames(codecs)
}
func codecFromName(name string) (webrtc.RTPCodecCapability, error) {
func fmtpValue(fmtp, key string) string {
fields := strings.Split(fmtp, ";")
for _, f := range fields {
kv := strings.SplitN(f, "=", 2)
if len(kv) != 2 {
continue
}
if kv[0] == key {
return kv[1]
}
}
return ""
}
func CodecPayloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) {
switch strings.ToLower(codec.MimeType) {
case "video/vp8":
return 96, nil
case "video/vp9":
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":
return 35, nil
case "video/h264":
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":
return 111, nil
case "audio/g722":
return 9, nil
case "audio/pcmu":
return 0, nil
case "audio/pcma":
return 8, nil
default:
return 0, errors.New("unknown codec")
}
}
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":
return webrtc.RTPCodecCapability{
codecs = []webrtc.RTPCodecCapability{
{
"video/VP8", 90000, 0,
"",
nil,
}, nil
fb,
},
}
case "vp9":
return webrtc.RTPCodecCapability{
codecs = []webrtc.RTPCodecCapability{
{
"video/VP9", 90000, 0,
"profile-id=0",
nil,
}, nil
fb,
},
{
"video/VP9", 90000, 0,
"profile-id=2",
fb,
},
}
case "av1":
return webrtc.RTPCodecCapability{
codecs = []webrtc.RTPCodecCapability{
{
"video/AV1X", 90000, 0,
"",
nil,
}, nil
fb,
},
}
case "h264":
return webrtc.RTPCodecCapability{
codecs = []webrtc.RTPCodecCapability{
{
"video/H264", 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
nil,
}, nil
fb,
},
{
"video/H264", 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
fb,
},
}
case "opus":
return webrtc.RTPCodecCapability{
codecs = []webrtc.RTPCodecCapability{
{
"audio/opus", 48000, 2,
"minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1",
nil,
}, nil
},
}
case "g722":
return webrtc.RTPCodecCapability{
codecs = []webrtc.RTPCodecCapability{
{
"audio/G722", 8000, 1,
"",
nil,
}, nil
},
}
case "pcmu":
return webrtc.RTPCodecCapability{
codecs = []webrtc.RTPCodecCapability{
{
"audio/PCMU", 8000, 1,
"",
nil,
}, nil
},
}
case "pcma":
return webrtc.RTPCodecCapability{
"audio/PCMA", 8000, 1,
codecs = []webrtc.RTPCodecCapability{
{
"audio/PCMU", 8000, 1,
"",
nil,
}, nil
},
}
default:
return webrtc.RTPCodecCapability{}, errors.New("unknown codec")
return nil, errors.New("unknown codec")
}
}
func payloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) {
switch strings.ToLower(codec.MimeType) {
case "video/vp8":
return 96, nil
case "video/vp9":
return 98, nil
case "video/av1x":
return 104, nil
case "video/h264":
return 102, nil
case "audio/opus":
return 111, nil
case "audio/g722":
return 9, nil
case "audio/pcmu":
return 0, nil
case "audio/pcma":
return 8, nil
default:
return 0, 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.RTPCodecCapability) (*webrtc.API, error) {
func APIFromCodecs(codecs []webrtc.RTPCodecParameters) (*webrtc.API, error) {
s := webrtc.SettingEngine{}
s.SetSRTPReplayProtectionWindow(512)
if !UseMDNS {
......@@ -229,41 +312,11 @@ func APIFromCodecs(codecs []webrtc.RTPCodecCapability) (*webrtc.API, error) {
m := webrtc.MediaEngine{}
for _, codec := range codecs {
var tpe webrtc.RTPCodecType
var fb []webrtc.RTCPFeedback
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.RTPCodecTypeVideo
if strings.HasPrefix(strings.ToLower(codec.MimeType), "audio/") {
tpe = webrtc.RTPCodecTypeAudio
fb = []webrtc.RTCPFeedback{}
} else {
continue
}
ptpe, err := payloadType(codec)
if err != nil {
log.Printf("%v", err)
continue
}
err = m.RegisterCodec(
webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: codec.MimeType,
ClockRate: codec.ClockRate,
Channels: codec.Channels,
SDPFmtpLine: codec.SDPFmtpLine,
RTCPFeedback: fb,
},
PayloadType: ptpe,
},
tpe,
)
err := m.RegisterCodec(codec, tpe)
if err != nil {
log.Printf("%v", err)
continue
......@@ -290,14 +343,14 @@ func APIFromNames(names []string) (*webrtc.API, error) {
if len(names) == 0 {
names = []string{"vp8", "opus"}
}
codecs := make([]webrtc.RTPCodecCapability, 0, len(names))
var codecs []webrtc.RTPCodecParameters
for _, n := range names {
codec, err := codecFromName(n)
cs, err := codecsFromName(n)
if err != nil {
log.Printf("Codec %v: %v", n, err)
continue
}
codecs = append(codecs, codec)
codecs = append(codecs, cs...)
}
return APIFromCodecs(codecs)
......
......@@ -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
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()
if len(parms.Encodings) != 1 {
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