mirror of
https://github.com/aler9/gortsplib
synced 2025-10-04 23:02:45 +08:00
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.
This commit is contained in:
@@ -39,9 +39,8 @@ func main() {
|
|||||||
medi := &media.Media{
|
medi := &media.Media{
|
||||||
Type: media.TypeAudio,
|
Type: media.TypeAudio,
|
||||||
Formats: []format.Format{&format.Opus{
|
Formats: []format.Format{&format.Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SampleRate: 48000,
|
IsStereo: false,
|
||||||
ChannelCount: 2,
|
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -296,9 +296,8 @@ func TestNewFromMediaDescription(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
&Opus{
|
&Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SampleRate: 48000,
|
IsStereo: true,
|
||||||
ChannelCount: 2,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -12,9 +12,8 @@ import (
|
|||||||
|
|
||||||
// Opus is a format that uses the Opus codec.
|
// Opus is a format that uses the Opus codec.
|
||||||
type Opus struct {
|
type Opus struct {
|
||||||
PayloadTyp uint8
|
PayloadTyp uint8
|
||||||
SampleRate int
|
IsStereo bool
|
||||||
ChannelCount int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements Format.
|
// String implements Format.
|
||||||
@@ -24,7 +23,9 @@ func (t *Opus) String() string {
|
|||||||
|
|
||||||
// ClockRate implements Format.
|
// ClockRate implements Format.
|
||||||
func (t *Opus) ClockRate() int {
|
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.
|
// PayloadType implements Format.
|
||||||
@@ -44,13 +45,36 @@ func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
channelCount, err := strconv.ParseInt(tmp[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@@ -58,14 +82,15 @@ func (t *Opus) unmarshal(payloadType uint8, clock string, codec string, rtpmap s
|
|||||||
// Marshal implements Format.
|
// Marshal implements Format.
|
||||||
func (t *Opus) Marshal() (string, string) {
|
func (t *Opus) Marshal() (string, string) {
|
||||||
fmtp := "sprop-stereo=" + func() string {
|
fmtp := "sprop-stereo=" + func() string {
|
||||||
if t.ChannelCount == 2 {
|
if t.IsStereo {
|
||||||
return "1"
|
return "1"
|
||||||
}
|
}
|
||||||
return "0"
|
return "0"
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return "opus/" + strconv.FormatInt(int64(t.SampleRate), 10) +
|
// RFC7587: The RTP clock rate in "a=rtpmap" MUST be 48000, and the
|
||||||
"/" + strconv.FormatInt(int64(t.ChannelCount), 10), fmtp
|
// number of channels MUST be 2.
|
||||||
|
return "opus/48000/2", fmtp
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTSEqualsDTS implements Format.
|
// 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.
|
// CreateDecoder creates a decoder able to decode the content of the format.
|
||||||
func (t *Opus) CreateDecoder() *rtpsimpleaudio.Decoder {
|
func (t *Opus) CreateDecoder() *rtpsimpleaudio.Decoder {
|
||||||
d := &rtpsimpleaudio.Decoder{
|
d := &rtpsimpleaudio.Decoder{
|
||||||
SampleRate: t.SampleRate,
|
SampleRate: 48000,
|
||||||
}
|
}
|
||||||
d.Init()
|
d.Init()
|
||||||
return d
|
return d
|
||||||
@@ -86,7 +111,7 @@ func (t *Opus) CreateDecoder() *rtpsimpleaudio.Decoder {
|
|||||||
func (t *Opus) CreateEncoder() *rtpsimpleaudio.Encoder {
|
func (t *Opus) CreateEncoder() *rtpsimpleaudio.Encoder {
|
||||||
e := &rtpsimpleaudio.Encoder{
|
e := &rtpsimpleaudio.Encoder{
|
||||||
PayloadType: t.PayloadTyp,
|
PayloadType: t.PayloadTyp,
|
||||||
SampleRate: 8000,
|
SampleRate: 48000,
|
||||||
}
|
}
|
||||||
e.Init()
|
e.Init()
|
||||||
return e
|
return e
|
||||||
|
@@ -9,9 +9,8 @@ import (
|
|||||||
|
|
||||||
func TestOpusAttributes(t *testing.T) {
|
func TestOpusAttributes(t *testing.T) {
|
||||||
format := &Opus{
|
format := &Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SampleRate: 48000,
|
IsStereo: true,
|
||||||
ChannelCount: 2,
|
|
||||||
}
|
}
|
||||||
require.Equal(t, "Opus", format.String())
|
require.Equal(t, "Opus", format.String())
|
||||||
require.Equal(t, 48000, format.ClockRate())
|
require.Equal(t, 48000, format.ClockRate())
|
||||||
@@ -21,9 +20,8 @@ func TestOpusAttributes(t *testing.T) {
|
|||||||
|
|
||||||
func TestOpusMediaDescription(t *testing.T) {
|
func TestOpusMediaDescription(t *testing.T) {
|
||||||
format := &Opus{
|
format := &Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SampleRate: 48000,
|
IsStereo: true,
|
||||||
ChannelCount: 2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rtpmap, fmtp := format.Marshal()
|
rtpmap, fmtp := format.Marshal()
|
||||||
|
@@ -194,7 +194,7 @@ var casesMedias = []struct {
|
|||||||
"a=control\r\n" +
|
"a=control\r\n" +
|
||||||
"a=sendonly\r\n" +
|
"a=sendonly\r\n" +
|
||||||
"a=rtpmap:111 opus/48000/2\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:103 ISAC/16000\r\n" +
|
||||||
"a=rtpmap:104 ISAC/32000\r\n" +
|
"a=rtpmap:104 ISAC/32000\r\n" +
|
||||||
"a=rtpmap:9 G722/8000\r\n" +
|
"a=rtpmap:9 G722/8000\r\n" +
|
||||||
@@ -230,9 +230,8 @@ var casesMedias = []struct {
|
|||||||
Direction: DirectionSendonly,
|
Direction: DirectionSendonly,
|
||||||
Formats: []format.Format{
|
Formats: []format.Format{
|
||||||
&format.Opus{
|
&format.Opus{
|
||||||
PayloadTyp: 111,
|
PayloadTyp: 111,
|
||||||
SampleRate: 48000,
|
IsStereo: false,
|
||||||
ChannelCount: 2,
|
|
||||||
},
|
},
|
||||||
&format.Generic{
|
&format.Generic{
|
||||||
PayloadTyp: 103,
|
PayloadTyp: 103,
|
||||||
@@ -534,9 +533,8 @@ func TestMediasFindFormat(t *testing.T) {
|
|||||||
Type: TypeAudio,
|
Type: TypeAudio,
|
||||||
Formats: []format.Format{
|
Formats: []format.Format{
|
||||||
&format.Opus{
|
&format.Opus{
|
||||||
PayloadTyp: 111,
|
PayloadTyp: 111,
|
||||||
SampleRate: 48000,
|
IsStereo: true,
|
||||||
ChannelCount: 2,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user