From 13fab2962eb2eaaf9db550f2568a946b28bd9b08 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Mon, 13 Mar 2023 22:03:49 +0100 Subject: [PATCH] make Opus SDP always return 48khz and 2 channels (#204) RFC7587 mandates 48khz as sample rate and 2 channels inside the SDP. These values can be dynamically adjusted by the stream, but they must not be touched inside the SDP. --- examples/client-publish-format-opus/main.go | 5 +-- pkg/format/format_test.go | 5 +-- pkg/format/opus.go | 47 ++++++++++++++++----- pkg/format/opus_test.go | 10 ++--- pkg/media/medias_test.go | 12 +++--- 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/examples/client-publish-format-opus/main.go b/examples/client-publish-format-opus/main.go index 99caef55..ebc8d2f3 100644 --- a/examples/client-publish-format-opus/main.go +++ b/examples/client-publish-format-opus/main.go @@ -39,9 +39,8 @@ func main() { medi := &media.Media{ Type: media.TypeAudio, Formats: []format.Format{&format.Opus{ - PayloadTyp: 96, - SampleRate: 48000, - ChannelCount: 2, + PayloadTyp: 96, + IsStereo: false, }}, } diff --git a/pkg/format/format_test.go b/pkg/format/format_test.go index 455480e2..b62176f2 100644 --- a/pkg/format/format_test.go +++ b/pkg/format/format_test.go @@ -296,9 +296,8 @@ func TestNewFromMediaDescription(t *testing.T) { }, }, &Opus{ - PayloadTyp: 96, - SampleRate: 48000, - ChannelCount: 2, + PayloadTyp: 96, + IsStereo: true, }, }, { diff --git a/pkg/format/opus.go b/pkg/format/opus.go index 1e65573e..7c28254f 100644 --- a/pkg/format/opus.go +++ b/pkg/format/opus.go @@ -12,9 +12,8 @@ import ( // Opus is a format that uses the Opus codec. type Opus struct { - PayloadTyp uint8 - SampleRate int - ChannelCount int + PayloadTyp uint8 + IsStereo bool } // String implements Format. @@ -24,7 +23,9 @@ func (t *Opus) String() string { // ClockRate implements Format. func (t *Opus) ClockRate() int { - return t.SampleRate + // RFC7587: the RTP timestamp is incremented with a 48000 Hz + // clock rate for all modes of Opus and all sampling rates. + return 48000 } // PayloadType implements Format. @@ -44,13 +45,36 @@ func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap s if err != nil { return err } - t.SampleRate = int(sampleRate) + if sampleRate != 48000 { + return fmt.Errorf("invalid sample rate: %d", sampleRate) + } channelCount, err := strconv.ParseInt(tmp[1], 10, 64) if err != nil { return err } - t.ChannelCount = int(channelCount) + if channelCount != 2 { + return fmt.Errorf("invalid channel count: %d", channelCount) + } + + if fmtp != "" { + 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 strings.ToLower(tmp[0]) == "sprop-stereo" { + t.IsStereo = (tmp[1] == "1") + } + } + } return nil } @@ -58,14 +82,15 @@ func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap s // Marshal implements Format. func (t *Opus) Marshal() (string, string) { fmtp := "sprop-stereo=" + func() string { - if t.ChannelCount == 2 { + if t.IsStereo { return "1" } return "0" }() - return "opus/" + strconv.FormatInt(int64(t.SampleRate), 10) + - "/" + strconv.FormatInt(int64(t.ChannelCount), 10), fmtp + // RFC7587: The RTP clock rate in "a=rtpmap" MUST be 48000, and the + // number of channels MUST be 2. + return "opus/48000/2", fmtp } // PTSEqualsDTS implements Format. @@ -76,7 +101,7 @@ func (t *Opus) PTSEqualsDTS(*rtp.Packet) bool { // CreateDecoder creates a decoder able to decode the content of the format. func (t *Opus) CreateDecoder() *rtpsimpleaudio.Decoder { d := &rtpsimpleaudio.Decoder{ - SampleRate: t.SampleRate, + SampleRate: 48000, } d.Init() return d @@ -86,7 +111,7 @@ func (t *Opus) CreateDecoder() *rtpsimpleaudio.Decoder { func (t *Opus) CreateEncoder() *rtpsimpleaudio.Encoder { e := &rtpsimpleaudio.Encoder{ PayloadType: t.PayloadTyp, - SampleRate: 8000, + SampleRate: 48000, } e.Init() return e diff --git a/pkg/format/opus_test.go b/pkg/format/opus_test.go index 2535dd7f..bdcf78f6 100644 --- a/pkg/format/opus_test.go +++ b/pkg/format/opus_test.go @@ -9,9 +9,8 @@ import ( func TestOpusAttributes(t *testing.T) { format := &Opus{ - PayloadTyp: 96, - SampleRate: 48000, - ChannelCount: 2, + PayloadTyp: 96, + IsStereo: true, } require.Equal(t, "Opus", format.String()) require.Equal(t, 48000, format.ClockRate()) @@ -21,9 +20,8 @@ func TestOpusAttributes(t *testing.T) { func TestOpusMediaDescription(t *testing.T) { format := &Opus{ - PayloadTyp: 96, - SampleRate: 48000, - ChannelCount: 2, + PayloadTyp: 96, + IsStereo: true, } rtpmap, fmtp := format.Marshal() diff --git a/pkg/media/medias_test.go b/pkg/media/medias_test.go index 6df6652b..98d94b82 100644 --- a/pkg/media/medias_test.go +++ b/pkg/media/medias_test.go @@ -194,7 +194,7 @@ var casesMedias = []struct { "a=control\r\n" + "a=sendonly\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + - "a=fmtp:111 sprop-stereo=1\r\n" + + "a=fmtp:111 sprop-stereo=0\r\n" + "a=rtpmap:103 ISAC/16000\r\n" + "a=rtpmap:104 ISAC/32000\r\n" + "a=rtpmap:9 G722/8000\r\n" + @@ -230,9 +230,8 @@ var casesMedias = []struct { Direction: DirectionSendonly, Formats: []format.Format{ &format.Opus{ - PayloadTyp: 111, - SampleRate: 48000, - ChannelCount: 2, + PayloadTyp: 111, + IsStereo: false, }, &format.Generic{ PayloadTyp: 103, @@ -534,9 +533,8 @@ func TestMediasFindFormat(t *testing.T) { Type: TypeAudio, Formats: []format.Format{ &format.Opus{ - PayloadTyp: 111, - SampleRate: 48000, - ChannelCount: 2, + PayloadTyp: 111, + IsStereo: true, }, }, },