This commit is contained in:
Alessandro Ros
2024-05-19 14:37:02 +02:00
committed by GitHub
parent ef60c8c755
commit fd4a9137cc
7 changed files with 161 additions and 38 deletions

View File

@@ -153,6 +153,7 @@ In RTSP, media streams are routed between server and clients by using RTP packet
|[RFC2250, RTP Payload Format for MPEG1/MPEG2 Video](https://datatracker.ietf.org/doc/html/rfc2250)|MPEG-1 video, MPEG-2 audio, MPEG-TS payload formats| |[RFC2250, RTP Payload Format for MPEG1/MPEG2 Video](https://datatracker.ietf.org/doc/html/rfc2250)|MPEG-1 video, MPEG-2 audio, MPEG-TS payload formats|
|[RFC2435, RTP Payload Format for JPEG-compressed Video](https://datatracker.ietf.org/doc/html/rfc2435)|M-JPEG payload format| |[RFC2435, RTP Payload Format for JPEG-compressed Video](https://datatracker.ietf.org/doc/html/rfc2435)|M-JPEG payload format|
|[RFC7587, RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587)|Opus payload format| |[RFC7587, RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587)|Opus payload format|
|[Multiopus in libwebrtc](https://webrtc-review.googlesource.com/c/src/+/129768)|Opus payload format|
|[RFC5215, RTP Payload Format for Vorbis Encoded Audio](https://datatracker.ietf.org/doc/html/rfc5215)|Vorbis payload format| |[RFC5215, RTP Payload Format for Vorbis Encoded Audio](https://datatracker.ietf.org/doc/html/rfc5215)|Vorbis payload format|
|[RFC4184, RTP Payload Format for AC-3 Audio](https://datatracker.ietf.org/doc/html/rfc4184)|AC-3 payload format| |[RFC4184, RTP Payload Format for AC-3 Audio](https://datatracker.ietf.org/doc/html/rfc4184)|AC-3 payload format|
|[RFC6416, RTP Payload Format for MPEG-4 Audio/Visual Streams](https://datatracker.ietf.org/doc/html/rfc6416)|MPEG-4 audio payload format| |[RFC6416, RTP Payload Format for MPEG-4 Audio/Visual Streams](https://datatracker.ietf.org/doc/html/rfc6416)|MPEG-4 audio payload format|

View File

@@ -310,8 +310,9 @@ var casesSession = []struct {
IsBackChannel: true, IsBackChannel: true,
Formats: []format.Format{ Formats: []format.Format{
&format.Opus{ &format.Opus{
PayloadTyp: 111, PayloadTyp: 111,
IsStereo: false, IsStereo: false,
ChannelCount: 1,
}, },
&format.Generic{ &format.Generic{
PayloadTyp: 103, PayloadTyp: 103,
@@ -820,8 +821,9 @@ func TestSessionFindFormat(t *testing.T) {
Type: MediaTypeAudio, Type: MediaTypeAudio,
Formats: []format.Format{ Formats: []format.Format{
&format.Opus{ &format.Opus{
PayloadTyp: 111, PayloadTyp: 111,
IsStereo: true, IsStereo: true,
ChannelCount: 2,
}, },
}, },
}, },

View File

@@ -112,7 +112,7 @@ func Unmarshal(mediaType string, payloadType uint8, rtpMap string, fmtp map[stri
// audio // audio
case codec == "opus": case codec == "opus", codec == "multiopus":
return &Opus{} return &Opus{}
case codec == "vorbis": case codec == "vorbis":

View File

@@ -645,14 +645,37 @@ var casesFormat = []struct {
"sprop-stereo": "1", "sprop-stereo": "1",
}, },
&Opus{ &Opus{
PayloadTyp: 96, PayloadTyp: 96,
IsStereo: true, IsStereo: true,
ChannelCount: 2,
}, },
"opus/48000/2", "opus/48000/2",
map[string]string{ map[string]string{
"sprop-stereo": "1", "sprop-stereo": "1",
}, },
}, },
{
"audio opus 5.1",
"audio",
96,
"multiopus/48000/6",
map[string]string{
"num_streams": "4",
"coupled_streams": "2",
"channel_mapping": "0,4,1,2,3,5",
},
&Opus{
PayloadTyp: 96,
ChannelCount: 6,
},
"multiopus/48000/6",
map[string]string{
"channel_mapping": "0,4,1,2,3,5",
"coupled_streams": "2",
"num_streams": "4",
"sprop-maxcapturerate": "48000",
},
},
{ {
"audio ac3", "audio ac3",
"audio", "audio",
@@ -1250,6 +1273,14 @@ func FuzzUnmarshalOpus(f *testing.F) {
}) })
} }
func FuzzUnmarshalOpusMulti(f *testing.F) {
f.Add("48000/a")
f.Fuzz(func(_ *testing.T, a string) {
Unmarshal("audio", 96, "multiopus/"+a, nil) //nolint:errcheck
})
}
func FuzzUnmarshalVorbis(f *testing.F) { func FuzzUnmarshalVorbis(f *testing.F) {
f.Fuzz(func(_ *testing.T, a, b string) { f.Fuzz(func(_ *testing.T, a, b string) {
Unmarshal("audio", 96, "Vorbis/"+a, map[string]string{ //nolint:errcheck Unmarshal("audio", 96, "Vorbis/"+a, map[string]string{ //nolint:errcheck

View File

@@ -12,33 +12,63 @@ import (
// Opus is the RTP format for the Opus codec. // Opus is the RTP format for the Opus codec.
// Specification: https://datatracker.ietf.org/doc/html/rfc7587 // Specification: https://datatracker.ietf.org/doc/html/rfc7587
// Specification: https://webrtc-review.googlesource.com/c/src/+/129768
type Opus struct { type Opus struct {
PayloadTyp uint8 PayloadTyp uint8
IsStereo bool ChannelCount int
// Deprecated: replaced by ChannelCount.
IsStereo bool
} }
func (f *Opus) unmarshal(ctx *unmarshalContext) error { func (f *Opus) unmarshal(ctx *unmarshalContext) error {
f.PayloadTyp = ctx.payloadType f.PayloadTyp = ctx.payloadType
tmp := strings.SplitN(ctx.clock, "/", 2) if ctx.codec == "opus" {
if len(tmp) != 2 { tmp := strings.SplitN(ctx.clock, "/", 2)
return fmt.Errorf("invalid clock (%v)", ctx.clock) if len(tmp) != 2 {
} return fmt.Errorf("invalid clock (%v)", ctx.clock)
sampleRate, err := strconv.ParseUint(tmp[0], 10, 31)
if err != nil || sampleRate != 48000 {
return fmt.Errorf("invalid sample rate: %d", sampleRate)
}
channelCount, err := strconv.ParseUint(tmp[1], 10, 31)
if err != nil || channelCount != 2 {
return fmt.Errorf("invalid channel count: %d", channelCount)
}
for key, val := range ctx.fmtp {
if key == "sprop-stereo" {
f.IsStereo = (val == "1")
} }
sampleRate, err := strconv.ParseUint(tmp[0], 10, 31)
if err != nil || sampleRate != 48000 {
return fmt.Errorf("invalid sample rate: '%s", tmp[0])
}
channelCount, err := strconv.ParseUint(tmp[1], 10, 31)
if err != nil || channelCount != 2 {
return fmt.Errorf("invalid channel count: '%s'", tmp[1])
}
// assume mono
f.ChannelCount = 1
f.IsStereo = false
for key, val := range ctx.fmtp {
if key == "sprop-stereo" {
if val == "1" {
f.ChannelCount = 2
f.IsStereo = true
}
}
}
} else {
tmp := strings.SplitN(ctx.clock, "/", 2)
if len(tmp) != 2 {
return fmt.Errorf("invalid clock (%v)", ctx.clock)
}
sampleRate, err := strconv.ParseUint(tmp[0], 10, 31)
if err != nil || sampleRate != 48000 {
return fmt.Errorf("invalid sample rate: '%s'", tmp[0])
}
channelCount, err := strconv.ParseUint(tmp[1], 10, 31)
if err != nil {
return fmt.Errorf("invalid channel count: '%s'", tmp[1])
}
f.ChannelCount = int(channelCount)
} }
return nil return nil
@@ -63,22 +93,77 @@ func (f *Opus) PayloadType() uint8 {
// RTPMap implements Format. // RTPMap implements Format.
func (f *Opus) RTPMap() string { func (f *Opus) RTPMap() string {
// RFC7587: The RTP clock rate in "a=rtpmap" MUST be 48000, and the if f.ChannelCount <= 2 {
// number of channels MUST be 2. // RFC7587: The RTP clock rate in "a=rtpmap" MUST be 48000, and the
return "opus/48000/2" // number of channels MUST be 2.
return "opus/48000/2"
}
return "multiopus/48000/" + strconv.FormatUint(uint64(f.ChannelCount), 10)
} }
// FMTP implements Format. // FMTP implements Format.
func (f *Opus) FMTP() map[string]string { func (f *Opus) FMTP() map[string]string {
fmtp := map[string]string{ if f.ChannelCount <= 2 {
"sprop-stereo": func() string { return map[string]string{
if f.IsStereo { "sprop-stereo": func() string {
return "1" if f.ChannelCount == 2 || (f.ChannelCount == 0 && f.IsStereo) {
} return "1"
return "0" }
}(), return "0"
}(),
}
}
switch f.ChannelCount {
case 3:
return map[string]string{
"num_streams": "2",
"coupled_streams": "1",
"channel_mapping": "0,2,1",
"sprop-maxcapturerate": "48000",
}
case 4:
return map[string]string{
"num_streams": "2",
"coupled_streams": "2",
"channel_mapping": "0,1,2,3",
"sprop-maxcapturerate": "48000",
}
case 5:
return map[string]string{
"num_streams": "3",
"coupled_streams": "2",
"channel_mapping": "0,4,1,2,3",
"sprop-maxcapturerate": "48000",
}
case 6:
return map[string]string{
"num_streams": "4",
"coupled_streams": "2",
"channel_mapping": "0,4,1,2,3,5",
"sprop-maxcapturerate": "48000",
}
case 7:
return map[string]string{
"num_streams": "4",
"coupled_streams": "3",
"channel_mapping": "0,4,1,2,3,5,6",
"sprop-maxcapturerate": "48000",
}
default: // assume 8
return map[string]string{
"num_streams": "5",
"coupled_streams": "3",
"channel_mapping": "0,6,1,4,5,2,3,7",
"sprop-maxcapturerate": "48000",
}
} }
return fmtp
} }
// PTSEqualsDTS implements Format. // PTSEqualsDTS implements Format.

View File

@@ -0,0 +1,2 @@
go test fuzz v1
string("0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
string("/")