diff --git a/README.md b/README.md index 47be56e4..19925f90 100644 --- a/README.md +++ b/README.md @@ -101,14 +101,15 @@ https://pkg.go.dev/github.com/bluenviron/gortsplib/v3#pkg-index * RTP Profile for Audio and Video Conferences with Minimal Control https://www.rfc-editor.org/rfc/rfc3551 * RTP Payload Format for MPEG1/MPEG2 Video https://www.rfc-editor.org/rfc/rfc2250 * RTP Payload Format for JPEG-compressed Video https://www.rfc-editor.org/rfc/rfc2435 +* RTP Payload Format for MPEG-4 Audio/Visual Streams https://www.rfc-editor.org/rfc/rfc6416 * RTP Payload Format for H.264 Video https://www.rfc-editor.org/rfc/rfc6184 -* RTP Payload Format for High Efficiency Video Coding (HEVC) https://www.rfc-editor.org/rfc/rfc7798.html -* RTP Payload Format for VP8 Video https://www.rfc-editor.org/rfc/rfc7741.html +* RTP Payload Format for High Efficiency Video Coding (HEVC) https://www.rfc-editor.org/rfc/rfc7798 +* RTP Payload Format for VP8 Video https://www.rfc-editor.org/rfc/rfc7741 * RTP Payload Format for VP9 Video https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16 -* RTP Payload Format for 12-bit DAT Audio and 20- and 24-bit Linear Sampled Audio https://www.rfc-editor.org/rfc/rfc3190.html -* RTP Payload Format for the Opus Speech and Audio Codec https://www.rfc-editor.org/rfc/rfc7587.html -* RTP Payload Format for MPEG-4 Audio/Visual Streams https://www.rfc-editor.org/rfc/rfc6416 -* RTP Payload Format for Transport of MPEG-4 Elementary Streams https://www.rfc-editor.org/rfc/rfc3640.html +* RTP Payload Format for 12-bit DAT Audio and 20- and 24-bit Linear Sampled Audio https://www.rfc-editor.org/rfc/rfc3190 +* RTP Payload Format for Vorbis Encoded Audio https://datatracker.ietf.org/doc/html/rfc5215 +* RTP Payload Format for the Opus Speech and Audio Codec https://www.rfc-editor.org/rfc/rfc7587 +* RTP Payload Format for Transport of MPEG-4 Elementary Streams https://www.rfc-editor.org/rfc/rfc3640 * 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/format.go b/pkg/formats/format.go index d20a40bb..e55e261e 100644 --- a/pkg/formats/format.go +++ b/pkg/formats/format.go @@ -57,8 +57,8 @@ func decodeFMTP(enc string) map[string]string { return ret } -// Format is a format of a media. -// It defines a codec and a payload type used to ship the media. +// Format is a RTP format of a media. +// It defines a codec and a payload type used to transmit the media. type Format interface { // String returns a description of the format. String() string @@ -111,6 +111,9 @@ func Unmarshal(md *psdp.MediaDescription, payloadTypeStr string) (Format, error) case payloadType == 32: return &MPEG2Video{} + case codec == "mp4v-es" && clock == "90000": + return &MPEG4Video{} + case codec == "h264" && clock == "90000": return &H264{} diff --git a/pkg/formats/format_test.go b/pkg/formats/format_test.go index 79d8d5bf..dc501106 100644 --- a/pkg/formats/format_test.go +++ b/pkg/formats/format_test.go @@ -322,6 +322,39 @@ func TestNewFromMediaDescription(t *testing.T) { }, &MPEG2Video{}, }, + { + "video mpeg4 video", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 MP4V-ES/90000", + }, + { + Key: "fmtp", + Value: "96 profile-level-id=1; " + + "config=000001B001000001B58913000001000000012000C48D8AEE053C04641443000001B24C61766335382E3133342E313030", + }, + }, + }, + &MPEG4Video{ + PayloadTyp: 96, + ProfileLevelID: 1, + Config: []byte{ + 0x00, 0x00, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x01, + 0xb5, 0x89, 0x13, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x20, 0x00, 0xc4, 0x8d, 0x8a, 0xee, + 0x05, 0x3c, 0x04, 0x64, 0x14, 0x43, 0x00, 0x00, + 0x01, 0xb2, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38, + 0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30, + }, + }, + }, { "video h264", &psdp.MediaDescription{ @@ -443,7 +476,7 @@ func TestNewFromMediaDescription(t *testing.T) { }, }, { - "h264 empty sprop-parameter-sets", + "video h264 empty sprop-parameter-sets", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", diff --git a/pkg/formats/g711.go b/pkg/formats/g711.go index 4879e5ab..62773d8e 100644 --- a/pkg/formats/g711.go +++ b/pkg/formats/g711.go @@ -6,7 +6,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpsimpleaudio" ) -// G711 is a format that uses the G711 codec, encoded with mu-law or A-law. +// G711 is a RTP format that uses the G711 codec, encoded with mu-law or A-law. +// Specification: https://www.rfc-editor.org/rfc/rfc3551 type G711 struct { // whether to use mu-law. Otherwise, A-law is used. MULaw bool diff --git a/pkg/formats/g722.go b/pkg/formats/g722.go index 230195dd..f1ca1d38 100644 --- a/pkg/formats/g722.go +++ b/pkg/formats/g722.go @@ -6,7 +6,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpsimpleaudio" ) -// G722 is a format that uses the G722 codec. +// G722 is a RTP format that uses the G722 codec. +// Specification: https://www.rfc-editor.org/rfc/rfc3551 type G722 struct{} // String implements Format. diff --git a/pkg/formats/generic.go b/pkg/formats/generic.go index c29b34c6..e7f913ee 100644 --- a/pkg/formats/generic.go +++ b/pkg/formats/generic.go @@ -51,7 +51,7 @@ func findClockRate(payloadType uint8, rtpMap string) (int, error) { return int(v), nil } -// Generic is a generic format. +// Generic is a generic RTP format. type Generic struct { PayloadTyp uint8 RTPMap string diff --git a/pkg/formats/h264.go b/pkg/formats/h264.go index aa2ae3b5..b97171bf 100644 --- a/pkg/formats/h264.go +++ b/pkg/formats/h264.go @@ -70,7 +70,8 @@ func rtpH264ContainsIDR(pkt *rtp.Packet) bool { } } -// H264 is a format that uses the H264 codef. +// H264 is a RTP format that uses the H264 codec, defined in MPEG-4 part 10. +// Specification: https://www.rfc-editor.org/rfc/rfc6184 type H264 struct { PayloadTyp uint8 SPS []byte @@ -101,14 +102,14 @@ func (f *H264) unmarshal(payloadType uint8, clock string, codec string, rtpmap s for key, val := range fmtp { switch key { case "sprop-parameter-sets": - tmp2 := strings.Split(val, ",") - if len(tmp2) >= 2 { - sps, err := base64.StdEncoding.DecodeString(tmp2[0]) + tmp := strings.Split(val, ",") + if len(tmp) >= 2 { + sps, err := base64.StdEncoding.DecodeString(tmp[0]) if err != nil { return fmt.Errorf("invalid sprop-parameter-sets (%v)", val) } - pps, err := base64.StdEncoding.DecodeString(tmp2[1]) + pps, err := base64.StdEncoding.DecodeString(tmp[1]) if err != nil { return fmt.Errorf("invalid sprop-parameter-sets (%v)", val) } @@ -118,12 +119,12 @@ func (f *H264) unmarshal(payloadType uint8, clock string, codec string, rtpmap s } case "packetization-mode": - tmp2, err := strconv.ParseInt(val, 10, 64) + tmp, err := strconv.ParseInt(val, 10, 64) if err != nil { return fmt.Errorf("invalid packetization-mode (%v)", val) } - f.PacketizationMode = int(tmp2) + f.PacketizationMode = int(tmp) } } @@ -141,15 +142,15 @@ func (f *H264) Marshal() (string, map[string]string) { fmtp["packetization-mode"] = strconv.FormatInt(int64(f.PacketizationMode), 10) } - var tmp2 []string + var tmp []string if f.SPS != nil { - tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(f.SPS)) + tmp = append(tmp, base64.StdEncoding.EncodeToString(f.SPS)) } if f.PPS != nil { - tmp2 = append(tmp2, base64.StdEncoding.EncodeToString(f.PPS)) + tmp = append(tmp, base64.StdEncoding.EncodeToString(f.PPS)) } - if tmp2 != nil { - fmtp["sprop-parameter-sets"] = strings.Join(tmp2, ",") + if tmp != nil { + fmtp["sprop-parameter-sets"] = strings.Join(tmp, ",") } if len(f.SPS) >= 4 { fmtp["profile-level-id"] = strings.ToUpper(hex.EncodeToString(f.SPS[1:4])) @@ -163,7 +164,7 @@ func (f *H264) PTSEqualsDTS(pkt *rtp.Packet) bool { return rtpH264ContainsIDR(pkt) } -// CreateDecoder creates a decoder able to decode the content of the formaf. +// CreateDecoder creates a decoder able to decode the content of the format. func (f *H264) CreateDecoder() *rtph264.Decoder { d := &rtph264.Decoder{ PacketizationMode: f.PacketizationMode, @@ -172,7 +173,7 @@ func (f *H264) CreateDecoder() *rtph264.Decoder { return d } -// CreateEncoder creates an encoder able to encode the content of the formaf. +// CreateEncoder creates an encoder able to encode the content of the format. func (f *H264) CreateEncoder() *rtph264.Encoder { e := &rtph264.Encoder{ PayloadType: f.PayloadTyp, diff --git a/pkg/formats/h265.go b/pkg/formats/h265.go index 271c43e0..19fe3f91 100644 --- a/pkg/formats/h265.go +++ b/pkg/formats/h265.go @@ -11,7 +11,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtph265" ) -// H265 is a format that uses the H265 codef. +// H265 is a RTP format that uses the H265 codec. +// Specification: https://www.rfc-editor.org/rfc/rfc7798 type H265 struct { PayloadTyp uint8 VPS []byte @@ -102,7 +103,7 @@ func (f *H265) PTSEqualsDTS(*rtp.Packet) bool { return true } -// CreateDecoder creates a decoder able to decode the content of the formaf. +// CreateDecoder creates a decoder able to decode the content of the format. func (f *H265) CreateDecoder() *rtph265.Decoder { d := &rtph265.Decoder{ MaxDONDiff: f.MaxDONDiff, @@ -111,7 +112,7 @@ func (f *H265) CreateDecoder() *rtph265.Decoder { return d } -// CreateEncoder creates an encoder able to encode the content of the formaf. +// CreateEncoder creates an encoder able to encode the content of the format. func (f *H265) CreateEncoder() *rtph265.Encoder { e := &rtph265.Encoder{ PayloadType: f.PayloadTyp, diff --git a/pkg/formats/lpcm.go b/pkg/formats/lpcm.go index f791eb7d..19d3d0f5 100644 --- a/pkg/formats/lpcm.go +++ b/pkg/formats/lpcm.go @@ -9,7 +9,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtplpcm" ) -// LPCM is a format that uses the uncompressed, Linear PCM codec. +// LPCM is a RTP format that uses the uncompressed, Linear PCM codec. +// Specification: https://www.rfc-editor.org/rfc/rfc3190 type LPCM struct { PayloadTyp uint8 BitDepth int diff --git a/pkg/formats/mjpeg.go b/pkg/formats/mjpeg.go index 2f607210..55399731 100644 --- a/pkg/formats/mjpeg.go +++ b/pkg/formats/mjpeg.go @@ -6,7 +6,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmjpeg" ) -// MJPEG is a format that uses the Motion-JPEG codec. +// MJPEG is a RTP format that uses the Motion-JPEG codec. +// Specification: https://www.rfc-editor.org/rfc/rfc2435 type MJPEG struct{} // String implements Format. diff --git a/pkg/formats/mpeg2_audio.go b/pkg/formats/mpeg2_audio.go index 3a651696..f7610d0b 100644 --- a/pkg/formats/mpeg2_audio.go +++ b/pkg/formats/mpeg2_audio.go @@ -4,7 +4,8 @@ import ( "github.com/pion/rtp" ) -// MPEG2Audio is a format that uses a MPEG-1 or MPEG-2 audio codec. +// MPEG2Audio is a RTP format that uses a MPEG-1 or MPEG-2 audio codec. +// Specification: https://www.rfc-editor.org/rfc/rfc2250 type MPEG2Audio struct{} // String implements Format. diff --git a/pkg/formats/mpeg2_video.go b/pkg/formats/mpeg2_video.go index 90457836..5a6a847d 100644 --- a/pkg/formats/mpeg2_video.go +++ b/pkg/formats/mpeg2_video.go @@ -4,7 +4,8 @@ import ( "github.com/pion/rtp" ) -// MPEG2Video is a format that uses a MPEG-1 or MPEG-2 video codec. +// MPEG2Video is a RTP format that uses a MPEG-1 or MPEG-2 video codec. +// Specification: https://www.rfc-editor.org/rfc/rfc2250 type MPEG2Video struct{} // String implements Format. diff --git a/pkg/formats/mpeg4_audio.go b/pkg/formats/mpeg4_audio.go index 7bd216db..0fbbc042 100644 --- a/pkg/formats/mpeg4_audio.go +++ b/pkg/formats/mpeg4_audio.go @@ -11,7 +11,8 @@ import ( "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" ) -// MPEG4Audio is a format that uses a MPEG-4 audio codec. +// MPEG4Audio is a RTP format that uses a MPEG-4 audio codec. +// Specification: https://www.rfc-editor.org/rfc/rfc3640 type MPEG4Audio struct { PayloadTyp uint8 Config *mpeg4audio.Config diff --git a/pkg/formats/mpeg4_video.go b/pkg/formats/mpeg4_video.go new file mode 100644 index 00000000..be3b93b6 --- /dev/null +++ b/pkg/formats/mpeg4_video.go @@ -0,0 +1,81 @@ +package formats + +import ( + "encoding/hex" + "fmt" + "strconv" + "strings" + + "github.com/pion/rtp" +) + +// MPEG4Video is a RTP format that uses the video codec defined in MPEG-4 part 2. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.1 +type MPEG4Video struct { + PayloadTyp uint8 + ProfileLevelID int + Config []byte +} + +// String implements Format. +func (f *MPEG4Video) String() string { + return "MPEG4-video" +} + +// ClockRate implements Format. +func (f *MPEG4Video) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *MPEG4Video) PayloadType() uint8 { + return f.PayloadTyp +} + +func (f *MPEG4Video) unmarshal( + payloadType uint8, clock string, codec string, + rtpmap string, fmtp map[string]string, +) error { + f.PayloadTyp = payloadType + + // If this parameter is not specified by + // the procedure, its default value of 1 (Simple Profile/Level 1) is + // used. + f.ProfileLevelID = 1 + + for key, val := range fmtp { + switch key { + case "profile-level-id": + tmp, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid profile-level-id: %v", val) + } + + f.ProfileLevelID = int(tmp) + + case "config": + var err error + f.Config, err = hex.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid config: %v", val) + } + } + } + + return nil +} + +// Marshal implements Format. +func (f *MPEG4Video) Marshal() (string, map[string]string) { + fmtp := map[string]string{ + "profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10), + "config": strings.ToUpper(hex.EncodeToString(f.Config)), + } + + return "MP4V-ES/90000", fmtp +} + +// PTSEqualsDTS implements Format. +func (f *MPEG4Video) PTSEqualsDTS(*rtp.Packet) bool { + return true +} diff --git a/pkg/formats/mpeg4_video_test.go b/pkg/formats/mpeg4_video_test.go new file mode 100644 index 00000000..226c646f --- /dev/null +++ b/pkg/formats/mpeg4_video_test.go @@ -0,0 +1,35 @@ +package formats + +import ( + "testing" + + "github.com/pion/rtp" + "github.com/stretchr/testify/require" +) + +func TestMPEG4VideoAttributes(t *testing.T) { + format := &MPEG4Video{ + PayloadTyp: 96, + ProfileLevelID: 1, + Config: []byte{0x01, 0x02, 0x03}, + } + require.Equal(t, "MPEG4-video", format.String()) + require.Equal(t, 90000, format.ClockRate()) + require.Equal(t, uint8(96), format.PayloadType()) + require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) +} + +func TestMPEG4VideoMediaDescription(t *testing.T) { + format := &MPEG4Video{ + PayloadTyp: 96, + ProfileLevelID: 1, + Config: []byte{0x0a, 0x0b, 0x03}, + } + + rtpmap, fmtp := format.Marshal() + require.Equal(t, "MP4V-ES/90000", rtpmap) + require.Equal(t, map[string]string{ + "profile-level-id": "1", + "config": "0A0B03", + }, fmtp) +} diff --git a/pkg/formats/opus.go b/pkg/formats/opus.go index 583836a0..058e15c1 100644 --- a/pkg/formats/opus.go +++ b/pkg/formats/opus.go @@ -10,7 +10,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpsimpleaudio" ) -// Opus is a format that uses the Opus codec. +// Opus is a RTP format that uses the Opus codec. +// Specification: https://www.rfc-editor.org/rfc/rfc7587 type Opus struct { PayloadTyp uint8 IsStereo bool diff --git a/pkg/formats/vorbis.go b/pkg/formats/vorbis.go index 9ef97f94..d27b89f2 100644 --- a/pkg/formats/vorbis.go +++ b/pkg/formats/vorbis.go @@ -9,7 +9,8 @@ import ( "github.com/pion/rtp" ) -// Vorbis is a format that uses the Vorbis codec. +// Vorbis is a RTP format that uses the Vorbis codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc5215 type Vorbis struct { PayloadTyp uint8 SampleRate int diff --git a/pkg/formats/vp8.go b/pkg/formats/vp8.go index 880418ed..5ea29205 100644 --- a/pkg/formats/vp8.go +++ b/pkg/formats/vp8.go @@ -9,7 +9,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8" ) -// VP8 is a format that uses the VP8 codec. +// VP8 is a RTP format that uses the VP8 codec. +// Specification: https://www.rfc-editor.org/rfc/rfc7741 type VP8 struct { PayloadTyp uint8 MaxFR *int diff --git a/pkg/formats/vp9.go b/pkg/formats/vp9.go index 9860a2dc..7f9e047f 100644 --- a/pkg/formats/vp9.go +++ b/pkg/formats/vp9.go @@ -9,7 +9,8 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9" ) -// VP9 is a format that uses the VP9 codec. +// VP9 is a RTP format that uses the VP9 codec. +// Specification: https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16 type VP9 struct { PayloadTyp uint8 MaxFR *int