diff --git a/client_play_test.go b/client_play_test.go index 64b4ed6a..cbd5c20a 100644 --- a/client_play_test.go +++ b/client_play_test.go @@ -3384,8 +3384,13 @@ func TestClientPlayBackChannel(t *testing.T) { Body: mediasToSDP([]*description.Media{ testH264Media, { - Type: description.MediaTypeAudio, - Formats: []format.Format{&format.G711{}}, + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.G711{ + PayloadTyp: 8, + MULaw: false, + SampleRate: 8000, + ChannelCount: 1, + }}, IsBackChannel: true, }, }), diff --git a/examples/client-record-format-g711/main.go b/examples/client-record-format-g711/main.go index 90ea7f46..7c9fcbf4 100644 --- a/examples/client-record-format-g711/main.go +++ b/examples/client-record-format-g711/main.go @@ -38,8 +38,13 @@ func main() { // create a description that contains a G711 format desc := &description.Session{ Medias: []*description.Media{{ - Type: description.MediaTypeVideo, - Formats: []format.Format{&format.G711{}}, + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.G711{ + PayloadTyp: 8, + MULaw: false, + SampleRate: 8000, + ChannelCount: 1, + }}, }}, } diff --git a/pkg/description/session_test.go b/pkg/description/session_test.go index b78ddde5..eca58382 100644 --- a/pkg/description/session_test.go +++ b/pkg/description/session_test.go @@ -72,7 +72,10 @@ var casesSession = []struct { Type: MediaTypeAudio, Control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a", Formats: []format.Format{&format.G711{ - MULaw: true, + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, }}, }, { @@ -140,7 +143,10 @@ var casesSession = []struct { Type: MediaTypeAudio, Control: "trackID=2", Formats: []format.Format{&format.G711{ - MULaw: true, + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, }}, }, { @@ -324,10 +330,16 @@ var casesSession = []struct { ClockRat: 8000, }, &format.G711{ - MULaw: true, + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, }, &format.G711{ - MULaw: false, + PayloadTyp: 8, + MULaw: false, + SampleRate: 8000, + ChannelCount: 1, }, &format.Generic{ PayloadTyp: 106, @@ -516,13 +528,23 @@ var casesSession = []struct { { Type: MediaTypeAudio, Control: "rtsp://192.168.0.1/audio", - Formats: []format.Format{&format.G711{MULaw: true}}, + Formats: []format.Format{&format.G711{ + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, + }}, }, { Type: MediaTypeAudio, IsBackChannel: true, Control: "rtsp://192.168.0.1/audioback", - Formats: []format.Format{&format.G711{MULaw: true}}, + Formats: []format.Format{&format.G711{ + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, + }}, }, }, }, @@ -671,9 +693,14 @@ var casesSession = []struct { }, Medias: []*Media{ { - ID: "1", - Type: MediaTypeAudio, - Formats: []format.Format{&format.G711{MULaw: true}}, + ID: "1", + Type: MediaTypeAudio, + Formats: []format.Format{&format.G711{ + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, + }}, }, { ID: "2", diff --git a/pkg/format/format.go b/pkg/format/format.go index 0938ceb5..d586785e 100644 --- a/pkg/format/format.go +++ b/pkg/format/format.go @@ -137,6 +137,9 @@ func Unmarshal(mediaType string, payloadType uint8, rtpMap string, fmtp map[stri codec == "aal2-g726-40") && clock == "8000": return &G726{} + case codec == "pcma", codec == "pcmu": + return &G711{} + case codec == "l8", codec == "l16", codec == "l24": return &LPCM{} } diff --git a/pkg/format/format_test.go b/pkg/format/format_test.go index aeafb6ab..4d3ba36d 100644 --- a/pkg/format/format_test.go +++ b/pkg/format/format_test.go @@ -27,27 +27,65 @@ var casesFormat = []struct { encFmtp map[string]string }{ { - "audio g711 pcma", + "audio g711 pcma static payload type", "audio", 8, "", nil, - &G711{}, + &G711{ + PayloadTyp: 8, + MULaw: false, + SampleRate: 8000, + ChannelCount: 1, + }, "PCMA/8000", nil, }, { - "audio g711 pcmu", + "audio g711 pcmu static payload type", "audio", 0, "", nil, &G711{ - MULaw: true, + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, }, "PCMU/8000", nil, }, + { + "audio g711 pcma dynamic payload type", + "audio", + 96, + "PCMA/16000/2", + nil, + &G711{ + PayloadTyp: 96, + MULaw: false, + SampleRate: 16000, + ChannelCount: 2, + }, + "PCMA/16000/2", + nil, + }, + { + "audio g711 pcmu dynamic payload type", + "audio", + 96, + "PCMU/16000/2", + nil, + &G711{ + PayloadTyp: 96, + MULaw: true, + SampleRate: 16000, + ChannelCount: 2, + }, + "PCMU/16000/2", + nil, + }, { "audio g722", "audio", @@ -125,7 +163,7 @@ var casesFormat = []struct { nil, }, { - "audio lpcm 8", + "audio lpcm 8 dynamic payload type", "audio", 97, "L8/48000/2", @@ -140,7 +178,7 @@ var casesFormat = []struct { nil, }, { - "audio lpcm 16", + "audio lpcm 16 dynamic payload type", "audio", 97, "L16/96000/2", @@ -155,7 +193,7 @@ var casesFormat = []struct { nil, }, { - "audio lpcm 16 rfc3551 stereo", + "audio lpcm 16 static payload type", "audio", 10, "", @@ -170,7 +208,7 @@ var casesFormat = []struct { nil, }, { - "audio lpcm 16 rfc3551 mono", + "audio lpcm 16 static payload type", "audio", 11, "", diff --git a/pkg/format/g711.go b/pkg/format/g711.go index 5ba858c6..ed154fc0 100644 --- a/pkg/format/g711.go +++ b/pkg/format/g711.go @@ -1,20 +1,60 @@ package format import ( + "strconv" + "strings" + "github.com/pion/rtp" - "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" ) // G711 is the RTP format for the G711 codec, encoded with mu-law or A-law. // Specification: https://datatracker.ietf.org/doc/html/rfc3551 type G711 struct { - // whether to use mu-law. Otherwise, A-law is used. - MULaw bool + PayloadTyp uint8 + MULaw bool + SampleRate int + ChannelCount int } func (f *G711) unmarshal(ctx *unmarshalContext) error { - f.MULaw = (ctx.payloadType == 0) + f.PayloadTyp = ctx.payloadType + + if ctx.payloadType == 0 { + f.MULaw = true + f.SampleRate = 8000 + f.ChannelCount = 1 + return nil + } + + if ctx.payloadType == 8 { + f.MULaw = false + f.SampleRate = 8000 + f.ChannelCount = 1 + return nil + } + + f.MULaw = (ctx.codec == "pcmu") + + tmp := strings.SplitN(ctx.clock, "/", 2) + + tmp1, err := strconv.ParseUint(tmp[0], 10, 31) + if err != nil { + return err + } + f.SampleRate = int(tmp1) + + if len(tmp) >= 2 { + tmp1, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil { + return err + } + f.ChannelCount = int(tmp1) + } else { + f.ChannelCount = 1 + } + return nil } @@ -30,18 +70,26 @@ func (f *G711) ClockRate() int { // PayloadType implements Format. func (f *G711) PayloadType() uint8 { - if f.MULaw { - return 0 - } - return 8 + return f.PayloadTyp } // RTPMap implements Format. func (f *G711) RTPMap() string { + ret := "" + if f.MULaw { - return "PCMU/8000" + ret += "PCMU" + } else { + ret += "PCMA" } - return "PCMA/8000" + + ret += "/" + strconv.FormatInt(int64(f.SampleRate), 10) + + if f.ChannelCount != 1 { + ret += "/" + strconv.FormatInt(int64(f.ChannelCount), 10) + } + + return ret } // FMTP implements Format. @@ -55,8 +103,11 @@ func (f *G711) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. -func (f *G711) CreateDecoder() (*rtpsimpleaudio.Decoder, error) { - d := &rtpsimpleaudio.Decoder{} +func (f *G711) CreateDecoder() (*rtplpcm.Decoder, error) { + d := &rtplpcm.Decoder{ + BitDepth: 8, + ChannelCount: f.ChannelCount, + } err := d.Init() if err != nil { @@ -67,9 +118,11 @@ func (f *G711) CreateDecoder() (*rtpsimpleaudio.Decoder, error) { } // CreateEncoder creates an encoder able to encode the content of the format. -func (f *G711) CreateEncoder() (*rtpsimpleaudio.Encoder, error) { - e := &rtpsimpleaudio.Encoder{ - PayloadType: f.PayloadType(), +func (f *G711) CreateEncoder() (*rtplpcm.Encoder, error) { + e := &rtplpcm.Encoder{ + PayloadType: f.PayloadType(), + BitDepth: 8, + ChannelCount: f.ChannelCount, } err := e.Init() diff --git a/pkg/format/g711_test.go b/pkg/format/g711_test.go index 2e6f717c..17c18e10 100644 --- a/pkg/format/g711_test.go +++ b/pkg/format/g711_test.go @@ -8,32 +8,49 @@ import ( ) func TestG711Attributes(t *testing.T) { - format := &G711{} - require.Equal(t, "G711", format.Codec()) - require.Equal(t, 8000, format.ClockRate()) - require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) - - format = &G711{ - MULaw: true, - } - require.Equal(t, "G711", format.Codec()) - require.Equal(t, 8000, format.ClockRate()) + t.Run("pcma", func(t *testing.T) { + format := &G711{ + PayloadTyp: 8, + MULaw: false, + SampleRate: 8000, + ChannelCount: 1, + } + require.Equal(t, "G711", format.Codec()) + require.Equal(t, 8000, format.ClockRate()) + require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) + }) + + t.Run("pcmu", func(t *testing.T) { + format := &G711{ + PayloadTyp: 0, + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, + } + require.Equal(t, "G711", format.Codec()) + require.Equal(t, 8000, format.ClockRate()) + }) } func TestG711DecEncoder(t *testing.T) { - format := &G711{} + format := &G711{ + PayloadTyp: 8, + MULaw: false, + SampleRate: 8000, + ChannelCount: 1, + } enc, err := format.CreateEncoder() require.NoError(t, err) - pkt, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}) + pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}) require.NoError(t, err) - require.Equal(t, format.PayloadType(), pkt.PayloadType) + require.Equal(t, format.PayloadType(), pkts[0].PayloadType) dec, err := format.CreateDecoder() require.NoError(t, err) - byts, err := dec.Decode(pkt) + byts, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) }