From 816b7a54df0af4caf8c151142c26dd63073be77b Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Mon, 2 Aug 2021 15:53:33 +0200 Subject: [PATCH] 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. --- group/group.go | 239 ++++++++++++++++++++++++++----------------- group/group_test.go | 26 +++++ rtpconn/webclient.go | 20 ++++ 3 files changed, 192 insertions(+), 93 deletions(-) diff --git a/group/group.go b/group/group.go index edb06d3..c900f23 100644 --- a/group/group.go +++ b/group/group.go @@ -142,71 +142,49 @@ func (g *Group) API() (*webrtc.API, error) { return APIFromNames(codecs) } -func codecFromName(name string) (webrtc.RTPCodecCapability, error) { - switch name { - case "vp8": - return webrtc.RTPCodecCapability{ - "video/VP8", 90000, 0, - "", - nil, - }, nil - case "vp9": - 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") +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 payloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) { +func CodecPayloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) { switch strings.ToLower(codec.MimeType) { case "video/vp8": return 96, nil 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": - return 104, nil + return 35, nil 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": return 111, nil case "audio/g722": @@ -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.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) diff --git a/group/group_test.go b/group/group_test.go index 90378ed..0c7b2f8 100644 --- a/group/group_test.go +++ b/group/group_test.go @@ -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, + ) + } + } +} diff --git a/rtpconn/webclient.go b/rtpconn/webclient.go index 9952de2..0efc525 100644 --- a/rtpconn/webclient.go +++ b/rtpconn/webclient.go @@ -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")