From 497594f53f2600d360b1399be72c70717e4439a9 Mon Sep 17 00:00:00 2001 From: Alexey Khit Date: Mon, 6 Feb 2023 11:46:00 +0300 Subject: [PATCH] Fix buggy SDP parsing --- pkg/rtsp/helpers.go | 35 ++++++++++++++++++++++++++++++----- pkg/rtsp/rtsp_test.go | 23 +++++++++++++++++++++++ pkg/streamer/media.go | 12 +++--------- pkg/webrtc/conn.go | 8 ++++++-- 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/pkg/rtsp/helpers.go b/pkg/rtsp/helpers.go index 893f3b87..ea9d13a6 100644 --- a/pkg/rtsp/helpers.go +++ b/pkg/rtsp/helpers.go @@ -4,8 +4,10 @@ import ( "bytes" "github.com/AlexxIT/go2rtc/pkg/streamer" "github.com/pion/rtcp" + "github.com/pion/sdp/v3" "net/url" "regexp" + "strconv" "strings" ) @@ -21,8 +23,8 @@ s=- t=0 0` func UnmarshalSDP(rawSDP []byte) ([]*streamer.Media, error) { - medias, err := streamer.UnmarshalSDP(rawSDP) - if err != nil { + sd := &sdp.SessionDescription{} + if err := sd.Unmarshal(rawSDP); err != nil { // fix multiple `s=` https://github.com/AlexxIT/WebRTC/issues/417 re, _ := regexp.Compile("\ns=[^\n]+") rawSDP = re.ReplaceAll(rawSDP, nil) @@ -30,16 +32,28 @@ func UnmarshalSDP(rawSDP []byte) ([]*streamer.Media, error) { // fix SDP header for some cameras if i := bytes.Index(rawSDP, []byte("\nm=")); i > 0 { rawSDP = append([]byte(sdpHeader), rawSDP[i:]...) - medias, err = streamer.UnmarshalSDP(rawSDP) + sd = &sdp.SessionDescription{} + err = sd.Unmarshal(rawSDP) } + if err != nil { return nil, err } } - // fix bug in ONVIF spec - // https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec-v241.pdf + medias := streamer.UnmarshalMedias(sd.MediaDescriptions) + for _, media := range medias { + // Check buggy SDP with fmtp for H264 on another track + // https://github.com/AlexxIT/WebRTC/issues/419 + for _, codec := range media.Codecs { + if codec.Name == streamer.CodecH264 && codec.FmtpLine == "" { + codec.FmtpLine = findFmtpLine(codec.PayloadType, sd.MediaDescriptions) + } + } + + // fix bug in ONVIF spec + // https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec-v241.pdf switch media.Direction { case streamer.DirectionRecvonly, "": media.Direction = streamer.DirectionSendonly @@ -51,6 +65,17 @@ func UnmarshalSDP(rawSDP []byte) ([]*streamer.Media, error) { return medias, nil } +func findFmtpLine(payloadType uint8, descriptions []*sdp.MediaDescription) string { + s := strconv.Itoa(int(payloadType)) + for _, md := range descriptions { + codec := streamer.UnmarshalCodec(md, s) + if codec.FmtpLine != "" { + return codec.FmtpLine + } + } + return "" +} + // urlParse fix bugs: // 1. Content-Base: rtsp://::ffff:192.168.1.123/onvif/profile.1/ // 2. Content-Base: rtsp://rtsp://turret2-cam.lan:554/stream1/ diff --git a/pkg/rtsp/rtsp_test.go b/pkg/rtsp/rtsp_test.go index e7024f4e..39d5cae4 100644 --- a/pkg/rtsp/rtsp_test.go +++ b/pkg/rtsp/rtsp_test.go @@ -20,6 +20,7 @@ func TestURLParse(t *testing.T) { } func TestMultipleSinSDP(t *testing.T) { + // https://github.com/AlexxIT/WebRTC/issues/417 s := `v=0 o=- 91674849066 1 IN IP4 192.168.1.123 s=RtspServer @@ -50,3 +51,25 @@ a=control:track1 assert.Nil(t, err) assert.NotNil(t, medias) } + +func TestFindFmtp(t *testing.T) { + // https://github.com/AlexxIT/WebRTC/issues/419 + s := `v=0 +o=- 1675628282 1675628283 IN IP4 192.168.1.123 +s=streamed by the RTSP server +t=0 0 +m=video 0 RTP/AVP 96 +a=rtpmap:96 H264/90000 +a=control:track0 +m=audio 0 RTP/AVP 8 +a=rtpmap:0 pcma/8000/1 +a=control:track1 +a=framerate:25 +a=range:npt=now- +a=fmtp:96 packetization-mode=1;profile-level-id=64001F;sprop-parameter-sets=Z0IAH5WoFAFuQA==,aM48gA== +` + medias, err := UnmarshalSDP([]byte(s)) + assert.Nil(t, err) + assert.NotNil(t, medias) + assert.NotEqual(t, "", medias[0].Codecs[0].FmtpLine) +} diff --git a/pkg/streamer/media.go b/pkg/streamer/media.go index 5765c75d..bcb5ae08 100644 --- a/pkg/streamer/media.go +++ b/pkg/streamer/media.go @@ -166,14 +166,8 @@ func (c *Codec) Match(codec *Codec) bool { (c.Channels == codec.Channels || codec.Channels == 0) } -func UnmarshalSDP(rawSDP []byte) ([]*Media, error) { - sd := &sdp.SessionDescription{} - if err := sd.Unmarshal(rawSDP); err != nil { - return nil, err - } - - var medias []*Media - for _, md := range sd.MediaDescriptions { +func UnmarshalMedias(descriptions []*sdp.MediaDescription) (medias []*Media) { + for _, md := range descriptions { media := UnmarshalMedia(md) if media.Direction == DirectionSendRecv { @@ -187,7 +181,7 @@ func UnmarshalSDP(rawSDP []byte) ([]*Media, error) { medias = append(medias, media) } - return medias, nil + return } func MarshalSDP(name string, medias []*Media) ([]byte, error) { diff --git a/pkg/webrtc/conn.go b/pkg/webrtc/conn.go index eb4d5974..059dd545 100644 --- a/pkg/webrtc/conn.go +++ b/pkg/webrtc/conn.go @@ -2,6 +2,7 @@ package webrtc import ( "github.com/AlexxIT/go2rtc/pkg/streamer" + "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" ) @@ -90,12 +91,15 @@ func (c *Conn) SetOffer(offer string) (err error) { if err = c.Conn.SetRemoteDescription(sdOffer); err != nil { return } + rawSDP := []byte(c.Conn.RemoteDescription().SDP) - medias, err := streamer.UnmarshalSDP(rawSDP) - if err != nil { + sd := &sdp.SessionDescription{} + if err = sd.Unmarshal(rawSDP); err != nil { return } + medias := streamer.UnmarshalMedias(sd.MediaDescriptions) + // sort medias, so video will always be before audio // and ignore application media from Hass default lovelace card for _, media := range medias {