From fee147222e2d9971de4d49c38fd25fbaab7a00e7 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Sat, 15 Apr 2023 13:10:56 +0200 Subject: [PATCH] add AV1 format (#252) --- README.md | 2 + pkg/formats/av1.go | 91 ++++++++++++++++++++++++++++++++++++++ pkg/formats/av1_test.go | 18 ++++++++ pkg/formats/format.go | 3 ++ pkg/formats/format_test.go | 40 +++++++++++++++++ pkg/formats/vp9.go | 1 + 6 files changed, 155 insertions(+) create mode 100644 pkg/formats/av1.go create mode 100644 pkg/formats/av1_test.go diff --git a/README.md b/README.md index b1721f12..63999865 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ https://pkg.go.dev/github.com/bluenviron/gortsplib/v3#pkg-index * [RFC2326, RTSP 1.0](https://datatracker.ietf.org/doc/html/rfc2326) * [RFC7826, RTSP 2.0](https://datatracker.ietf.org/doc/html/rfc7826) +* [RFC8866, SDP: Session Description Protocol](https://datatracker.ietf.org/doc/html/rfc8866) * [RFC3551, RTP Profile for Audio and Video Conferences with Minimal Control](https://datatracker.ietf.org/doc/html/rfc3551) * [RFC2250, RTP Payload Format for MPEG1/MPEG2 Video](https://datatracker.ietf.org/doc/html/rfc2250) * [RFC2435, RTP Payload Format for JPEG-compressed Video](https://datatracker.ietf.org/doc/html/rfc2435) @@ -111,6 +112,7 @@ https://pkg.go.dev/github.com/bluenviron/gortsplib/v3#pkg-index * [RFC5215, RTP Payload Format for Vorbis Encoded Audio](https://datatracker.ietf.org/doc/html/rfc5215) * [RFC7587, RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587) * [RFC3640, RTP Payload Format for Transport of MPEG-4 Elementary Streams](https://datatracker.ietf.org/doc/html/rfc3640) +* [RTP Payload Format For AV1 (v1.0)](https://aomediacodec.github.io/av1-rtp-spec/) * [ITU-T Rec. H.264 (08/2021)](https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.264-202108-I!!PDF-E&type=items) * [ITU-T Rec. H.265 (08/2021)](https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.265-202108-I!!PDF-E&type=items) * ISO 14496-3, Coding of audio-visual objects, part 3, Audio diff --git a/pkg/formats/av1.go b/pkg/formats/av1.go new file mode 100644 index 00000000..1bad66bf --- /dev/null +++ b/pkg/formats/av1.go @@ -0,0 +1,91 @@ +package formats + +import ( + "fmt" + "strconv" + + "github.com/pion/rtp" +) + +// AV1 is a RTP format that uses the AV1 codec. +// Specification: https://aomediacodec.github.io/av1-rtp-spec/ +type AV1 struct { + PayloadTyp uint8 + LevelIdx *int + Profile *int + Tier *int +} + +// String implements Format. +func (f *AV1) String() string { + return "AV1" +} + +// ClockRate implements Format. +func (f *AV1) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *AV1) PayloadType() uint8 { + return f.PayloadTyp +} + +func (f *AV1) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp map[string]string) error { + f.PayloadTyp = payloadType + + for key, val := range fmtp { + switch key { + case "level-idx": + n, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid level-idx: %v", val) + } + + v2 := int(n) + f.LevelIdx = &v2 + + case "profile": + n, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid profile: %v", val) + } + + v2 := int(n) + f.Profile = &v2 + + case "tier": + n, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid tier: %v", val) + } + + v2 := int(n) + f.Tier = &v2 + } + } + + return nil +} + +// Marshal implements Format. +func (f *AV1) Marshal() (string, map[string]string) { + fmtp := make(map[string]string) + + if f.LevelIdx != nil { + fmtp["level-idx"] = strconv.FormatInt(int64(*f.LevelIdx), 10) + } + if f.Profile != nil { + fmtp["profile"] = strconv.FormatInt(int64(*f.Profile), 10) + } + if f.Tier != nil { + fmtp["tier"] = strconv.FormatInt(int64(*f.Tier), 10) + } + + return "AV1/90000", fmtp +} + +// PTSEqualsDTS implements Format. +func (f *AV1) PTSEqualsDTS(*rtp.Packet) bool { + return true +} diff --git a/pkg/formats/av1_test.go b/pkg/formats/av1_test.go new file mode 100644 index 00000000..68e69d5e --- /dev/null +++ b/pkg/formats/av1_test.go @@ -0,0 +1,18 @@ +package formats //nolint:dupl + +import ( + "testing" + + "github.com/pion/rtp" + "github.com/stretchr/testify/require" +) + +func TestAV1Attributes(t *testing.T) { + format := &AV1{ + PayloadTyp: 100, + } + require.Equal(t, "AV1", format.String()) + require.Equal(t, 90000, format.ClockRate()) + require.Equal(t, uint8(100), format.PayloadType()) + require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) +} diff --git a/pkg/formats/format.go b/pkg/formats/format.go index e9f9f594..2d6853c5 100644 --- a/pkg/formats/format.go +++ b/pkg/formats/format.go @@ -68,6 +68,9 @@ func Unmarshal(mediaType string, payloadType uint8, rtpMap string, fmtp map[stri case codec == "vp9" && clock == "90000": return &VP9{} + + case codec == "av1" && clock == "90000": + return &AV1{} } case mediaType == "audio": diff --git a/pkg/formats/format_test.go b/pkg/formats/format_test.go index 2354627d..f8da6f59 100644 --- a/pkg/formats/format_test.go +++ b/pkg/formats/format_test.go @@ -578,6 +578,29 @@ var casesFormat = []struct { "profile-id": "789", }, }, + { + "video av1", + "video", + 96, + "AV1/90000", + map[string]string{ + "profile": "2", + "level-idx": "8", + "tier": "1", + }, + &AV1{ + PayloadTyp: 96, + Profile: intPtr(2), + LevelIdx: intPtr(8), + Tier: intPtr(1), + }, + "AV1/90000", + map[string]string{ + "profile": "2", + "level-idx": "8", + "tier": "1", + }, + }, { "application", "application", @@ -764,6 +787,23 @@ func TestUnmarshalMPEG4AudioLATMErrors(t *testing.T) { require.Error(t, err) } +func TestUnmarshalAV1Errors(t *testing.T) { + _, err := Unmarshal("video", 96, "AV1/90000", map[string]string{ + "level-idx": "aaa", + }) + require.Error(t, err) + + _, err = Unmarshal("video", 96, "AV1/90000", map[string]string{ + "profile": "aaa", + }) + require.Error(t, err) + + _, err = Unmarshal("video", 96, "AV1/90000", map[string]string{ + "tier": "aaa", + }) + require.Error(t, err) +} + func FuzzUnmarshalH264(f *testing.F) { f.Fuzz(func(t *testing.T, sps string, pktMode string) { Unmarshal("video", 96, "H264/90000", map[string]string{ diff --git a/pkg/formats/vp9.go b/pkg/formats/vp9.go index 4ed8cb97..e3ab2da4 100644 --- a/pkg/formats/vp9.go +++ b/pkg/formats/vp9.go @@ -73,6 +73,7 @@ func (f *VP9) unmarshal(payloadType uint8, clock string, codec string, rtpmap st // Marshal implements Format. func (f *VP9) Marshal() (string, map[string]string) { fmtp := make(map[string]string) + if f.MaxFR != nil { fmtp["max-fr"] = strconv.FormatInt(int64(*f.MaxFR), 10) }