diff --git a/pkg/format/format_test.go b/pkg/format/format_test.go index 45111fad..f0266c1f 100644 --- a/pkg/format/format_test.go +++ b/pkg/format/format_test.go @@ -610,7 +610,46 @@ func TestNewFromMediaDescription(t *testing.T) { }, &Generic{ PayloadTyp: 107, - ClockRat: 0, + }, + }, + { + "generic invalid rtpmap", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "application", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"98"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "98 custom", + }, + }, + }, + &Generic{ + PayloadTyp: 98, + RTPMap: "custom", + }, + }, + { + "generic invalid rtpmap 2", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "application", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"98"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "98 custom/aaa", + }, + }, + }, + &Generic{ + PayloadTyp: 98, + RTPMap: "custom/aaa", }, }, } { @@ -662,44 +701,6 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) { }, "strconv.ParseInt: parsing \"\": invalid syntax", }, - { - "audio aac missing fmtp", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "audio", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"96"}, - }, - Attributes: []psdp.Attribute{ - { - Key: "rtpmap", - Value: "96 mpeg4-generic/48000/2", - }, - }, - }, - "fmtp attribute is missing", - }, - { - "audio aac invalid fmtp", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "audio", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"96"}, - }, - Attributes: []psdp.Attribute{ - { - Key: "rtpmap", - Value: "96 mpeg4-generic/48000/2", - }, - { - Key: "fmtp", - Value: "96", - }, - }, - }, - "fmtp attribute is missing", - }, { "audio aac fmtp without key", &psdp.MediaDescription{ @@ -868,6 +869,27 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) { }, "invalid AAC IndexDeltaLength (aaa)", }, + { + "audio vorbis missing configuration", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "audio", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 VORBIS/44100/2", + }, + { + Key: "fmtp", + Value: "96 aa=bb", + }, + }, + }, + "config is missing (aa=bb)", + }, { "audio opus invalid 1", &psdp.MediaDescription{ @@ -919,6 +941,90 @@ func TestNewFromMediaDescriptionErrors(t *testing.T) { }, "strconv.ParseInt: parsing \"aa\": invalid syntax", }, + { + "video h264 invalid fmtp", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 H264/90000", + }, + { + Key: "fmtp", + Value: "96 aaa", + }, + }, + }, + "invalid fmtp attribute (aaa)", + }, + { + "video h264 invalid sps", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 H264/90000", + }, + { + Key: "fmtp", + Value: "96 sprop-parameter-sets=kkk,vvv", + }, + }, + }, + "invalid sprop-parameter-sets (kkk,vvv)", + }, + { + "video h264 invalid pps", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 H264/90000", + }, + { + Key: "fmtp", + Value: "96 sprop-parameter-sets=Z2QADKw7ULBLQgAAAwACAAADAD0I,vvv", + }, + }, + }, + "invalid sprop-parameter-sets (Z2QADKw7ULBLQgAAAwACAAADAD0I,vvv)", + }, + { + "video h264 invalid packetization-mode", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 H264/90000", + }, + { + Key: "fmtp", + Value: "96 packetization-mode=aaa", + }, + }, + }, + "invalid packetization-mode (aaa)", + }, } { t.Run(ca.name, func(t *testing.T) { _, err := Unmarshal(ca.md, ca.md.MediaName.Formats[0]) diff --git a/pkg/format/h264.go b/pkg/format/h264.go index fcadf85e..324afa33 100644 --- a/pkg/format/h264.go +++ b/pkg/format/h264.go @@ -98,47 +98,45 @@ func (t *H264) PayloadType() uint8 { func (t *H264) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error { t.PayloadTyp = payloadType - if fmtp == "" { - return nil // do not return any error - } + if fmtp != "" { + for _, kv := range strings.Split(fmtp, ";") { + kv = strings.Trim(kv, " ") - for _, kv := range strings.Split(fmtp, ";") { - kv = strings.Trim(kv, " ") - - if len(kv) == 0 { - continue - } - - tmp := strings.SplitN(kv, "=", 2) - if len(tmp) != 2 { - return fmt.Errorf("invalid fmtp attribute (%v)", fmtp) - } - - switch tmp[0] { - case "sprop-parameter-sets": - tmp := strings.Split(tmp[1], ",") - if len(tmp) >= 2 { - sps, err := base64.StdEncoding.DecodeString(tmp[0]) - if err != nil { - return fmt.Errorf("invalid sprop-parameter-sets (%v)", fmtp) - } - - pps, err := base64.StdEncoding.DecodeString(tmp[1]) - if err != nil { - return fmt.Errorf("invalid sprop-parameter-sets (%v)", fmtp) - } - - t.SPS = sps - t.PPS = pps + if len(kv) == 0 { + continue } - case "packetization-mode": - tmp, err := strconv.ParseInt(tmp[1], 10, 64) - if err != nil { - return fmt.Errorf("invalid packetization-mode (%v)", fmtp) + tmp := strings.SplitN(kv, "=", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid fmtp attribute (%v)", fmtp) } - t.PacketizationMode = int(tmp) + switch tmp[0] { + case "sprop-parameter-sets": + tmp2 := strings.Split(tmp[1], ",") + if len(tmp2) >= 2 { + sps, err := base64.StdEncoding.DecodeString(tmp2[0]) + if err != nil { + return fmt.Errorf("invalid sprop-parameter-sets (%v)", tmp[1]) + } + + pps, err := base64.StdEncoding.DecodeString(tmp2[1]) + if err != nil { + return fmt.Errorf("invalid sprop-parameter-sets (%v)", tmp[1]) + } + + t.SPS = sps + t.PPS = pps + } + + case "packetization-mode": + tmp2, err := strconv.ParseInt(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid packetization-mode (%v)", tmp[1]) + } + + t.PacketizationMode = int(tmp2) + } } } diff --git a/pkg/format/h265.go b/pkg/format/h265.go index c17bd56e..19e6840d 100644 --- a/pkg/format/h265.go +++ b/pkg/format/h265.go @@ -41,50 +41,48 @@ func (t *H265) PayloadType() uint8 { func (t *H265) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error { t.PayloadTyp = payloadType - if fmtp == "" { - return nil // do not return any error - } + if fmtp != "" { + for _, kv := range strings.Split(fmtp, ";") { + kv = strings.Trim(kv, " ") - for _, kv := range strings.Split(fmtp, ";") { - kv = strings.Trim(kv, " ") - - if len(kv) == 0 { - continue - } - - tmp := strings.SplitN(kv, "=", 2) - if len(tmp) != 2 { - return fmt.Errorf("invalid fmtp attribute (%v)", fmtp) - } - - switch tmp[0] { - case "sprop-vps": - var err error - t.VPS, err = base64.StdEncoding.DecodeString(tmp[1]) - if err != nil { - return fmt.Errorf("invalid sprop-vps (%v)", fmtp) + if len(kv) == 0 { + continue } - case "sprop-sps": - var err error - t.SPS, err = base64.StdEncoding.DecodeString(tmp[1]) - if err != nil { - return fmt.Errorf("invalid sprop-sps (%v)", fmtp) + tmp := strings.SplitN(kv, "=", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid fmtp attribute (%v)", fmtp) } - case "sprop-pps": - var err error - t.PPS, err = base64.StdEncoding.DecodeString(tmp[1]) - if err != nil { - return fmt.Errorf("invalid sprop-pps (%v)", fmtp) - } + switch tmp[0] { + case "sprop-vps": + var err error + t.VPS, err = base64.StdEncoding.DecodeString(tmp[1]) + if err != nil { + return fmt.Errorf("invalid sprop-vps (%v)", fmtp) + } - case "sprop-max-don-diff": - tmp, err := strconv.ParseInt(tmp[1], 10, 64) - if err != nil { - return fmt.Errorf("invalid sprop-max-don-diff (%v)", fmtp) + case "sprop-sps": + var err error + t.SPS, err = base64.StdEncoding.DecodeString(tmp[1]) + if err != nil { + return fmt.Errorf("invalid sprop-sps (%v)", fmtp) + } + + case "sprop-pps": + var err error + t.PPS, err = base64.StdEncoding.DecodeString(tmp[1]) + if err != nil { + return fmt.Errorf("invalid sprop-pps (%v)", fmtp) + } + + case "sprop-max-don-diff": + tmp, err := strconv.ParseInt(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid sprop-max-don-diff (%v)", fmtp) + } + t.MaxDONDiff = int(tmp) } - t.MaxDONDiff = int(tmp) } } diff --git a/pkg/format/mpeg4audio.go b/pkg/format/mpeg4audio.go index a91695d5..00d89e38 100644 --- a/pkg/format/mpeg4audio.go +++ b/pkg/format/mpeg4audio.go @@ -39,55 +39,53 @@ func (t *MPEG4Audio) PayloadType() uint8 { func (t *MPEG4Audio) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error { t.PayloadTyp = payloadType - if fmtp == "" { - return fmt.Errorf("fmtp attribute is missing") - } + if fmtp != "" { + for _, kv := range strings.Split(fmtp, ";") { + kv = strings.Trim(kv, " ") - for _, kv := range strings.Split(fmtp, ";") { - kv = strings.Trim(kv, " ") - - if len(kv) == 0 { - continue - } - - tmp := strings.SplitN(kv, "=", 2) - if len(tmp) != 2 { - return fmt.Errorf("invalid fmtp (%v)", fmtp) - } - - switch strings.ToLower(tmp[0]) { - case "config": - enc, err := hex.DecodeString(tmp[1]) - if err != nil { - return fmt.Errorf("invalid AAC config (%v)", tmp[1]) + if len(kv) == 0 { + continue } - t.Config = &mpeg4audio.Config{} - err = t.Config.Unmarshal(enc) - if err != nil { - return fmt.Errorf("invalid AAC config (%v)", tmp[1]) + tmp := strings.SplitN(kv, "=", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid fmtp (%v)", fmtp) } - case "sizelength": - val, err := strconv.ParseUint(tmp[1], 10, 64) - if err != nil { - return fmt.Errorf("invalid AAC SizeLength (%v)", tmp[1]) - } - t.SizeLength = int(val) + switch strings.ToLower(tmp[0]) { + case "config": + enc, err := hex.DecodeString(tmp[1]) + if err != nil { + return fmt.Errorf("invalid AAC config (%v)", tmp[1]) + } - case "indexlength": - val, err := strconv.ParseUint(tmp[1], 10, 64) - if err != nil { - return fmt.Errorf("invalid AAC IndexLength (%v)", tmp[1]) - } - t.IndexLength = int(val) + t.Config = &mpeg4audio.Config{} + err = t.Config.Unmarshal(enc) + if err != nil { + return fmt.Errorf("invalid AAC config (%v)", tmp[1]) + } - case "indexdeltalength": - val, err := strconv.ParseUint(tmp[1], 10, 64) - if err != nil { - return fmt.Errorf("invalid AAC IndexDeltaLength (%v)", tmp[1]) + case "sizelength": + val, err := strconv.ParseUint(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid AAC SizeLength (%v)", tmp[1]) + } + t.SizeLength = int(val) + + case "indexlength": + val, err := strconv.ParseUint(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid AAC IndexLength (%v)", tmp[1]) + } + t.IndexLength = int(val) + + case "indexdeltalength": + val, err := strconv.ParseUint(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid AAC IndexDeltaLength (%v)", tmp[1]) + } + t.IndexDeltaLength = int(val) } - t.IndexDeltaLength = int(val) } } diff --git a/pkg/format/mpeg4audio_test.go b/pkg/format/mpeg4audio_test.go index 1e401009..2d02f184 100644 --- a/pkg/format/mpeg4audio_test.go +++ b/pkg/format/mpeg4audio_test.go @@ -45,3 +45,27 @@ func TestMPEG4AudioMediaDescription(t *testing.T) { require.Equal(t, "profile-level-id=1; mode=AAC-hbr; sizelength=13;"+ " indexlength=3; indexdeltalength=3; config=1190", fmtp) } + +func TestMPEG4AudioDecEncoder(t *testing.T) { + format := &MPEG4Audio{ + PayloadTyp: 96, + Config: &mpeg4audio.Config{ + Type: mpeg4audio.ObjectTypeAACLC, + SampleRate: 48000, + ChannelCount: 2, + }, + SizeLength: 13, + IndexLength: 3, + IndexDeltaLength: 3, + } + + enc := format.CreateEncoder() + pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0) + require.NoError(t, err) + require.Equal(t, format.PayloadType(), pkts[0].PayloadType) + + dec := format.CreateDecoder() + byts, _, err := dec.Decode(pkts[0]) + require.NoError(t, err) + require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) +} diff --git a/pkg/format/vorbis.go b/pkg/format/vorbis.go index 63c16711..3f02a306 100644 --- a/pkg/format/vorbis.go +++ b/pkg/format/vorbis.go @@ -52,29 +52,27 @@ func (t *Vorbis) unmarshal(payloadType uint8, clock string, codec string, rtpmap } t.ChannelCount = int(channelCount) - if fmtp == "" { - return fmt.Errorf("fmtp attribute is missing") - } + if fmtp != "" { + for _, kv := range strings.Split(fmtp, ";") { + kv = strings.Trim(kv, " ") - for _, kv := range strings.Split(fmtp, ";") { - kv = strings.Trim(kv, " ") - - if len(kv) == 0 { - continue - } - - tmp := strings.SplitN(kv, "=", 2) - if len(tmp) != 2 { - return fmt.Errorf("invalid fmtp (%v)", fmtp) - } - - if tmp[0] == "configuration" { - conf, err := base64.StdEncoding.DecodeString(tmp[1]) - if err != nil { - return fmt.Errorf("invalid AAC config (%v)", tmp[1]) + if len(kv) == 0 { + continue } - t.Configuration = conf + tmp := strings.SplitN(kv, "=", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid fmtp (%v)", fmtp) + } + + if tmp[0] == "configuration" { + conf, err := base64.StdEncoding.DecodeString(tmp[1]) + if err != nil { + return fmt.Errorf("invalid AAC config (%v)", tmp[1]) + } + + t.Configuration = conf + } } } diff --git a/server_record_test.go b/server_record_test.go index 1e125e7a..12349f6a 100644 --- a/server_record_test.go +++ b/server_record_test.go @@ -1001,9 +1001,9 @@ func TestServerRecordRTCPReport(t *testing.T) { buf = make([]byte, 2048) n, _, err := l2.ReadFrom(buf) require.NoError(t, err) - pkt, err := rtcp.Unmarshal(buf[:n]) + pkts, err := rtcp.Unmarshal(buf[:n]) require.NoError(t, err) - rr, ok := pkt[0].(*rtcp.ReceiverReport) + rr, ok := pkts[0].(*rtcp.ReceiverReport) require.True(t, ok) require.Equal(t, &rtcp.ReceiverReport{ SSRC: rr.SSRC,